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
|
|
@ -43,18 +43,16 @@ abstract class AppRoutes {
|
||||||
static FutureOr<String?> loggedInRedirect(
|
static FutureOr<String?> loggedInRedirect(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
GoRouterState state,
|
GoRouterState state,
|
||||||
) =>
|
) => Matrix.of(context).widget.clients.any((client) => client.isLogged())
|
||||||
Matrix.of(context).widget.clients.any((client) => client.isLogged())
|
? '/rooms'
|
||||||
? '/rooms'
|
: null;
|
||||||
: null;
|
|
||||||
|
|
||||||
static FutureOr<String?> loggedOutRedirect(
|
static FutureOr<String?> loggedOutRedirect(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
GoRouterState state,
|
GoRouterState state,
|
||||||
) =>
|
) => Matrix.of(context).widget.clients.any((client) => client.isLogged())
|
||||||
Matrix.of(context).widget.clients.any((client) => client.isLogged())
|
? null
|
||||||
? null
|
: '/home';
|
||||||
: '/home';
|
|
||||||
|
|
||||||
AppRoutes();
|
AppRoutes();
|
||||||
|
|
||||||
|
|
@ -63,8 +61,8 @@ abstract class AppRoutes {
|
||||||
path: '/',
|
path: '/',
|
||||||
redirect: (context, state) =>
|
redirect: (context, state) =>
|
||||||
Matrix.of(context).widget.clients.any((client) => client.isLogged())
|
Matrix.of(context).widget.clients.any((client) => client.isLogged())
|
||||||
? '/rooms'
|
? '/rooms'
|
||||||
: '/home',
|
: '/home',
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/home',
|
path: '/home',
|
||||||
|
|
@ -88,19 +86,13 @@ abstract class AppRoutes {
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/logs',
|
path: '/logs',
|
||||||
pageBuilder: (context, state) => defaultPageBuilder(
|
pageBuilder: (context, state) =>
|
||||||
context,
|
defaultPageBuilder(context, state, const LogViewer()),
|
||||||
state,
|
|
||||||
const LogViewer(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/configs',
|
path: '/configs',
|
||||||
pageBuilder: (context, state) => defaultPageBuilder(
|
pageBuilder: (context, state) =>
|
||||||
context,
|
defaultPageBuilder(context, state, const ConfigViewer()),
|
||||||
state,
|
|
||||||
const ConfigViewer(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/backup',
|
path: '/backup',
|
||||||
|
|
@ -108,9 +100,7 @@ abstract class AppRoutes {
|
||||||
pageBuilder: (context, state) => defaultPageBuilder(
|
pageBuilder: (context, state) => defaultPageBuilder(
|
||||||
context,
|
context,
|
||||||
state,
|
state,
|
||||||
BootstrapDialog(
|
BootstrapDialog(wipe: state.uri.queryParameters['wipe'] == 'true'),
|
||||||
wipe: state.uri.queryParameters['wipe'] == 'true',
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
|
|
@ -150,11 +140,8 @@ abstract class AppRoutes {
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'archive',
|
path: 'archive',
|
||||||
pageBuilder: (context, state) => defaultPageBuilder(
|
pageBuilder: (context, state) =>
|
||||||
context,
|
defaultPageBuilder(context, state, const Archive()),
|
||||||
state,
|
|
||||||
const Archive(),
|
|
||||||
),
|
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: ':roomid',
|
path: ':roomid',
|
||||||
|
|
@ -173,20 +160,14 @@ abstract class AppRoutes {
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'newprivatechat',
|
path: 'newprivatechat',
|
||||||
pageBuilder: (context, state) => defaultPageBuilder(
|
pageBuilder: (context, state) =>
|
||||||
context,
|
defaultPageBuilder(context, state, const NewPrivateChat()),
|
||||||
state,
|
|
||||||
const NewPrivateChat(),
|
|
||||||
),
|
|
||||||
redirect: loggedOutRedirect,
|
redirect: loggedOutRedirect,
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'newgroup',
|
path: 'newgroup',
|
||||||
pageBuilder: (context, state) => defaultPageBuilder(
|
pageBuilder: (context, state) =>
|
||||||
context,
|
defaultPageBuilder(context, state, const NewGroup()),
|
||||||
state,
|
|
||||||
const NewGroup(),
|
|
||||||
),
|
|
||||||
redirect: loggedOutRedirect,
|
redirect: loggedOutRedirect,
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
|
|
@ -376,9 +357,7 @@ abstract class AppRoutes {
|
||||||
pageBuilder: (context, state) => defaultPageBuilder(
|
pageBuilder: (context, state) => defaultPageBuilder(
|
||||||
context,
|
context,
|
||||||
state,
|
state,
|
||||||
ChatSearchPage(
|
ChatSearchPage(roomId: state.pathParameters['roomid']!),
|
||||||
roomId: state.pathParameters['roomid']!,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
redirect: loggedOutRedirect,
|
redirect: loggedOutRedirect,
|
||||||
),
|
),
|
||||||
|
|
@ -407,9 +386,7 @@ abstract class AppRoutes {
|
||||||
pageBuilder: (context, state) => defaultPageBuilder(
|
pageBuilder: (context, state) => defaultPageBuilder(
|
||||||
context,
|
context,
|
||||||
state,
|
state,
|
||||||
ChatDetails(
|
ChatDetails(roomId: state.pathParameters['roomid']!),
|
||||||
roomId: state.pathParameters['roomid']!,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
|
|
@ -459,9 +436,7 @@ abstract class AppRoutes {
|
||||||
pageBuilder: (context, state) => defaultPageBuilder(
|
pageBuilder: (context, state) => defaultPageBuilder(
|
||||||
context,
|
context,
|
||||||
state,
|
state,
|
||||||
EmotesSettings(
|
EmotesSettings(roomId: state.pathParameters['roomid']),
|
||||||
roomId: state.pathParameters['roomid'],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
redirect: loggedOutRedirect,
|
redirect: loggedOutRedirect,
|
||||||
),
|
),
|
||||||
|
|
@ -480,23 +455,21 @@ abstract class AppRoutes {
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
GoRouterState state,
|
GoRouterState state,
|
||||||
Widget child,
|
Widget child,
|
||||||
) =>
|
) => NoTransitionPage(
|
||||||
NoTransitionPage(
|
key: state.pageKey,
|
||||||
key: state.pageKey,
|
restorationId: state.pageKey.value,
|
||||||
restorationId: state.pageKey.value,
|
child: child,
|
||||||
child: child,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
static Page defaultPageBuilder(
|
static Page defaultPageBuilder(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
GoRouterState state,
|
GoRouterState state,
|
||||||
Widget child,
|
Widget child,
|
||||||
) =>
|
) => FluffyThemes.isColumnMode(context)
|
||||||
FluffyThemes.isColumnMode(context)
|
? noTransitionPageBuilder(context, state, child)
|
||||||
? noTransitionPageBuilder(context, state, child)
|
: MaterialPage(
|
||||||
: MaterialPage(
|
key: state.pageKey,
|
||||||
key: state.pageKey,
|
restorationId: state.pageKey.value,
|
||||||
restorationId: state.pageKey.value,
|
child: child,
|
||||||
child: child,
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,18 +46,12 @@ enum AppSettings<T> {
|
||||||
'chat.fluffy.no_encryption_warning_shown',
|
'chat.fluffy.no_encryption_warning_shown',
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
displayChatDetailsColumn(
|
displayChatDetailsColumn('chat.fluffy.display_chat_details_column', false),
|
||||||
'chat.fluffy.display_chat_details_column',
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
// AppConfig-mirrored settings
|
// AppConfig-mirrored settings
|
||||||
applicationName<String>('chat.fluffy.application_name', 'FluffyChat'),
|
applicationName<String>('chat.fluffy.application_name', 'FluffyChat'),
|
||||||
defaultHomeserver<String>('chat.fluffy.default_homeserver', 'matrix.org'),
|
defaultHomeserver<String>('chat.fluffy.default_homeserver', 'matrix.org'),
|
||||||
// colorSchemeSeed stored as ARGB int
|
// colorSchemeSeed stored as ARGB int
|
||||||
colorSchemeSeedInt<int>(
|
colorSchemeSeedInt<int>('chat.fluffy.color_scheme_seed', 0xFF5625BA),
|
||||||
'chat.fluffy.color_scheme_seed',
|
|
||||||
0xFF5625BA,
|
|
||||||
),
|
|
||||||
emojiSuggestionLocale<String>('emoji_suggestion_locale', ''),
|
emojiSuggestionLocale<String>('emoji_suggestion_locale', ''),
|
||||||
enableSoftLogout<bool>('chat.fluffy.enable_soft_logout', false);
|
enableSoftLogout<bool>('chat.fluffy.enable_soft_logout', false);
|
||||||
|
|
||||||
|
|
@ -75,10 +69,9 @@ enum AppSettings<T> {
|
||||||
final store = AppSettings._store = await SharedPreferences.getInstance();
|
final store = AppSettings._store = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
// Migrate wrong datatype for fontSizeFactor
|
// Migrate wrong datatype for fontSizeFactor
|
||||||
final fontSizeFactorString =
|
final fontSizeFactorString = Result(
|
||||||
Result(() => store.getString(AppSettings.fontSizeFactor.key))
|
() => store.getString(AppSettings.fontSizeFactor.key),
|
||||||
.asValue
|
).asValue?.value;
|
||||||
?.value;
|
|
||||||
if (fontSizeFactorString != null) {
|
if (fontSizeFactorString != null) {
|
||||||
Logs().i('Migrate wrong datatype for fontSizeFactor!');
|
Logs().i('Migrate wrong datatype for fontSizeFactor!');
|
||||||
await store.remove(AppSettings.fontSizeFactor.key);
|
await store.remove(AppSettings.fontSizeFactor.key);
|
||||||
|
|
@ -93,8 +86,9 @@ enum AppSettings<T> {
|
||||||
}
|
}
|
||||||
if (kIsWeb && loadWebConfigFile) {
|
if (kIsWeb && loadWebConfigFile) {
|
||||||
try {
|
try {
|
||||||
final configJsonString =
|
final configJsonString = utf8.decode(
|
||||||
utf8.decode((await http.get(Uri.parse('config.json'))).bodyBytes);
|
(await http.get(Uri.parse('config.json'))).bodyBytes,
|
||||||
|
);
|
||||||
final configJson =
|
final configJson =
|
||||||
json.decode(configJsonString) as Map<String, Object?>;
|
json.decode(configJsonString) as Map<String, Object?>;
|
||||||
for (final setting in AppSettings.values) {
|
for (final setting in AppSettings.values) {
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,7 @@ abstract class FluffyThemes {
|
||||||
static bool isThreeColumnMode(BuildContext context) =>
|
static bool isThreeColumnMode(BuildContext context) =>
|
||||||
MediaQuery.sizeOf(context).width > FluffyThemes.columnWidth * 3.5;
|
MediaQuery.sizeOf(context).width > FluffyThemes.columnWidth * 3.5;
|
||||||
|
|
||||||
static LinearGradient backgroundGradient(
|
static LinearGradient backgroundGradient(BuildContext context, int alpha) {
|
||||||
BuildContext context,
|
|
||||||
int alpha,
|
|
||||||
) {
|
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
return LinearGradient(
|
return LinearGradient(
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
|
|
@ -91,12 +88,14 @@ abstract class FluffyThemes {
|
||||||
),
|
),
|
||||||
appBarTheme: AppBarTheme(
|
appBarTheme: AppBarTheme(
|
||||||
toolbarHeight: isColumnMode ? 72 : 56,
|
toolbarHeight: isColumnMode ? 72 : 56,
|
||||||
shadowColor:
|
shadowColor: isColumnMode
|
||||||
isColumnMode ? colorScheme.surfaceContainer.withAlpha(128) : null,
|
? colorScheme.surfaceContainer.withAlpha(128)
|
||||||
|
: null,
|
||||||
surfaceTintColor: isColumnMode ? colorScheme.surface : null,
|
surfaceTintColor: isColumnMode ? colorScheme.surface : null,
|
||||||
backgroundColor: isColumnMode ? colorScheme.surface : null,
|
backgroundColor: isColumnMode ? colorScheme.surface : null,
|
||||||
actionsPadding:
|
actionsPadding: isColumnMode
|
||||||
isColumnMode ? const EdgeInsets.symmetric(horizontal: 16.0) : null,
|
? const EdgeInsets.symmetric(horizontal: 16.0)
|
||||||
|
: null,
|
||||||
systemOverlayStyle: SystemUiOverlayStyle(
|
systemOverlayStyle: SystemUiOverlayStyle(
|
||||||
statusBarColor: Colors.transparent,
|
statusBarColor: Colors.transparent,
|
||||||
statusBarIconBrightness: brightness.reversed,
|
statusBarIconBrightness: brightness.reversed,
|
||||||
|
|
@ -107,10 +106,7 @@ abstract class FluffyThemes {
|
||||||
),
|
),
|
||||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
side: BorderSide(
|
side: BorderSide(width: 1, color: colorScheme.primary),
|
||||||
width: 1,
|
|
||||||
color: colorScheme.primary,
|
|
||||||
),
|
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
side: BorderSide(color: colorScheme.primary),
|
side: BorderSide(color: colorScheme.primary),
|
||||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||||
|
|
@ -157,8 +153,8 @@ extension BubbleColorTheme on ThemeData {
|
||||||
: colorScheme.onPrimaryContainer;
|
: colorScheme.onPrimaryContainer;
|
||||||
|
|
||||||
Color get secondaryBubbleColor => HSLColor.fromColor(
|
Color get secondaryBubbleColor => HSLColor.fromColor(
|
||||||
brightness == Brightness.light
|
brightness == Brightness.light
|
||||||
? colorScheme.tertiary
|
? colorScheme.tertiary
|
||||||
: colorScheme.tertiaryContainer,
|
: colorScheme.tertiaryContainer,
|
||||||
).withSaturation(0.5).toColor();
|
).withSaturation(0.5).toColor();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,8 +78,9 @@ Future<void> startGui(List<Client> clients, SharedPreferences store) async {
|
||||||
String? pin;
|
String? pin;
|
||||||
if (PlatformInfos.isMobile) {
|
if (PlatformInfos.isMobile) {
|
||||||
try {
|
try {
|
||||||
pin =
|
pin = await const FlutterSecureStorage().read(
|
||||||
await const FlutterSecureStorage().read(key: 'chat.fluffy.app_lock');
|
key: 'chat.fluffy.app_lock',
|
||||||
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().d('Unable to read PIN from Secure storage', e, s);
|
Logs().d('Unable to read PIN from Secure storage', e, s);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,9 @@ class ArchiveView extends StatelessWidget {
|
||||||
itemBuilder: (BuildContext context, int i) => ChatListItem(
|
itemBuilder: (BuildContext context, int i) => ChatListItem(
|
||||||
controller.archive[i],
|
controller.archive[i],
|
||||||
onForget: () => controller.forgetRoomAction(i),
|
onForget: () => controller.forgetRoomAction(i),
|
||||||
onTap: () => context
|
onTap: () => context.go(
|
||||||
.go('/rooms/archive/${controller.archive[i].id}'),
|
'/rooms/archive/${controller.archive[i].id}',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,7 @@ import '../key_verification/key_verification_dialog.dart';
|
||||||
class BootstrapDialog extends StatefulWidget {
|
class BootstrapDialog extends StatefulWidget {
|
||||||
final bool wipe;
|
final bool wipe;
|
||||||
|
|
||||||
const BootstrapDialog({
|
const BootstrapDialog({super.key, this.wipe = false});
|
||||||
super.key,
|
|
||||||
this.wipe = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BootstrapDialogState createState() => BootstrapDialogState();
|
BootstrapDialogState createState() => BootstrapDialogState();
|
||||||
|
|
@ -148,7 +145,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final status = snapshot.data;
|
final status = snapshot.data;
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
children: [
|
children: [
|
||||||
CircularProgressIndicator.adaptive(value: status?.progress),
|
CircularProgressIndicator.adaptive(value: status?.progress),
|
||||||
if (status != null) Text(status.calcLocalizedString(context)),
|
if (status != null) Text(status.calcLocalizedString(context)),
|
||||||
|
|
@ -177,8 +174,9 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
),
|
),
|
||||||
body: Center(
|
body: Center(
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints:
|
constraints: const BoxConstraints(
|
||||||
const BoxConstraints(maxWidth: FluffyThemes.columnWidth * 1.5),
|
maxWidth: FluffyThemes.columnWidth * 1.5,
|
||||||
|
),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -193,10 +191,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
),
|
),
|
||||||
subtitle: Text(L10n.of(context).chatBackupDescription),
|
subtitle: Text(L10n.of(context).chatBackupDescription),
|
||||||
),
|
),
|
||||||
const Divider(
|
const Divider(height: 32, thickness: 1),
|
||||||
height: 32,
|
|
||||||
thickness: 1,
|
|
||||||
),
|
|
||||||
TextField(
|
TextField(
|
||||||
minLines: 2,
|
minLines: 2,
|
||||||
maxLines: 4,
|
maxLines: 4,
|
||||||
|
|
@ -220,8 +215,9 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
title: Text(_getSecureStorageLocalizedName()),
|
title: Text(_getSecureStorageLocalizedName()),
|
||||||
subtitle:
|
subtitle: Text(
|
||||||
Text(L10n.of(context).storeInSecureStorageDescription),
|
L10n.of(context).storeInSecureStorageDescription,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
CheckboxListTile.adaptive(
|
CheckboxListTile.adaptive(
|
||||||
|
|
@ -241,16 +237,16 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
label: Text(L10n.of(context).next),
|
label: Text(L10n.of(context).next),
|
||||||
onPressed:
|
onPressed:
|
||||||
(_recoveryKeyCopied || _storeInSecureStorage == true)
|
(_recoveryKeyCopied || _storeInSecureStorage == true)
|
||||||
? () {
|
? () {
|
||||||
if (_storeInSecureStorage == true) {
|
if (_storeInSecureStorage == true) {
|
||||||
const FlutterSecureStorage().write(
|
const FlutterSecureStorage().write(
|
||||||
key: _secureStorageKey,
|
key: _secureStorageKey,
|
||||||
value: key,
|
value: key,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
setState(() => _recoveryKeyStored = true);
|
setState(() => _recoveryKeyStored = true);
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -303,8 +299,9 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
contentPadding:
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(horizontal: 8.0),
|
horizontal: 8.0,
|
||||||
|
),
|
||||||
trailing: Icon(
|
trailing: Icon(
|
||||||
Icons.info_outlined,
|
Icons.info_outlined,
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
|
|
@ -370,7 +367,9 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
await bootstrap
|
await bootstrap
|
||||||
.client.encryption!.crossSigning
|
.client
|
||||||
|
.encryption!
|
||||||
|
.crossSigning
|
||||||
.selfSign(recoveryKey: key);
|
.selfSign(recoveryKey: key);
|
||||||
Logs().d('Successful selfsigned');
|
Logs().d('Successful selfsigned');
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
|
@ -383,13 +382,14 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
}
|
}
|
||||||
} on InvalidPassphraseException catch (e) {
|
} on InvalidPassphraseException catch (e) {
|
||||||
setState(
|
setState(
|
||||||
() => _recoveryKeyInputError =
|
() => _recoveryKeyInputError = e
|
||||||
e.toLocalizedString(context),
|
.toLocalizedString(context),
|
||||||
);
|
);
|
||||||
} on FormatException catch (_) {
|
} on FormatException catch (_) {
|
||||||
setState(
|
setState(
|
||||||
() => _recoveryKeyInputError =
|
() => _recoveryKeyInputError = L10n.of(
|
||||||
L10n.of(context).wrongRecoveryKey,
|
context,
|
||||||
|
).wrongRecoveryKey,
|
||||||
);
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
ErrorReporter(
|
ErrorReporter(
|
||||||
|
|
@ -397,8 +397,8 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
'Unable to open SSSS with recovery key',
|
'Unable to open SSSS with recovery key',
|
||||||
).onErrorCallback(e, s);
|
).onErrorCallback(e, s);
|
||||||
setState(
|
setState(
|
||||||
() => _recoveryKeyInputError =
|
() => _recoveryKeyInputError = e
|
||||||
e.toLocalizedString(context),
|
.toLocalizedString(context),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setState(
|
setState(
|
||||||
|
|
@ -428,8 +428,9 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
final consent = await showOkCancelAlertDialog(
|
final consent = await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).verifyOtherDevice,
|
title: L10n.of(context).verifyOtherDevice,
|
||||||
message: L10n.of(context)
|
message: L10n.of(
|
||||||
.verifyOtherDeviceDescription,
|
context,
|
||||||
|
).verifyOtherDeviceDescription,
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: L10n.of(context).ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: L10n.of(context).cancel,
|
||||||
);
|
);
|
||||||
|
|
@ -452,17 +453,18 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
|
|
||||||
final waitForSecret = Completer();
|
final waitForSecret = Completer();
|
||||||
final secretsSub = client
|
final secretsSub = client
|
||||||
.encryption!.ssss.onSecretStored.stream
|
.encryption!
|
||||||
.listen((
|
.ssss
|
||||||
event,
|
.onSecretStored
|
||||||
) async {
|
.stream
|
||||||
if (await client.encryption!.keyManager
|
.listen((event) async {
|
||||||
.isCached() &&
|
if (await client.encryption!.keyManager
|
||||||
await client.encryption!.crossSigning
|
.isCached() &&
|
||||||
.isCached()) {
|
await client.encryption!.crossSigning
|
||||||
waitForSecret.complete();
|
.isCached()) {
|
||||||
}
|
waitForSecret.complete();
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
final result = await showFutureLoadingDialog(
|
final result = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -542,7 +544,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
case BootstrapState.done:
|
case BootstrapState.done:
|
||||||
titleText = L10n.of(context).everythingReady;
|
titleText = L10n.of(context).everythingReady;
|
||||||
body = Column(
|
body = Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(
|
const Icon(
|
||||||
Icons.check_circle_rounded,
|
Icons.check_circle_rounded,
|
||||||
|
|
@ -576,13 +578,9 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: .stretch,
|
||||||
children: [
|
children: [body, const SizedBox(height: 8), ...buttons],
|
||||||
body,
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
...buttons,
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -19,20 +19,21 @@ class AddWidgetTileView extends StatelessWidget {
|
||||||
CupertinoSegmentedControl(
|
CupertinoSegmentedControl(
|
||||||
groupValue: controller.widgetType,
|
groupValue: controller.widgetType,
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
children: {
|
children:
|
||||||
'm.etherpad': Text(L10n.of(context).widgetEtherpad),
|
{
|
||||||
'm.jitsi': Text(L10n.of(context).widgetJitsi),
|
'm.etherpad': Text(L10n.of(context).widgetEtherpad),
|
||||||
'm.video': Text(L10n.of(context).widgetVideo),
|
'm.jitsi': Text(L10n.of(context).widgetJitsi),
|
||||||
'm.custom': Text(L10n.of(context).widgetCustom),
|
'm.video': Text(L10n.of(context).widgetVideo),
|
||||||
}.map(
|
'm.custom': Text(L10n.of(context).widgetCustom),
|
||||||
(key, value) => MapEntry(
|
}.map(
|
||||||
key,
|
(key, value) => MapEntry(
|
||||||
Padding(
|
key,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
Padding(
|
||||||
child: value,
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
|
child: value,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
onValueChanged: controller.setWidgetType,
|
onValueChanged: controller.setWidgetType,
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
|
|
||||||
|
|
@ -182,14 +182,14 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
}
|
}
|
||||||
|
|
||||||
void enterThread(String eventId) => setState(() {
|
void enterThread(String eventId) => setState(() {
|
||||||
activeThreadId = eventId;
|
activeThreadId = eventId;
|
||||||
selectedEvents.clear();
|
selectedEvents.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
void closeThread() => setState(() {
|
void closeThread() => setState(() {
|
||||||
activeThreadId = null;
|
activeThreadId = null;
|
||||||
selectedEvents.clear();
|
selectedEvents.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
void recreateChat() async {
|
void recreateChat() async {
|
||||||
final room = this.room;
|
final room = this.room;
|
||||||
|
|
@ -266,9 +266,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
closeIconColor: theme.colorScheme.onErrorContainer,
|
closeIconColor: theme.colorScheme.onErrorContainer,
|
||||||
content: Text(
|
content: Text(
|
||||||
L10n.of(context).otherPartyNotLoggedIn,
|
L10n.of(context).otherPartyNotLoggedIn,
|
||||||
style: TextStyle(
|
style: TextStyle(color: theme.colorScheme.onErrorContainer),
|
||||||
color: theme.colorScheme.onErrorContainer,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
showCloseIcon: true,
|
showCloseIcon: true,
|
||||||
),
|
),
|
||||||
|
|
@ -306,11 +304,9 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
}
|
}
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
} else if (evt.logicalKey.keyLabel == 'Enter' && evt is KeyDownEvent) {
|
} else if (evt.logicalKey.keyLabel == 'Enter' && evt is KeyDownEvent) {
|
||||||
final currentLineNum = sendController.text
|
final currentLineNum =
|
||||||
.substring(
|
sendController.text
|
||||||
0,
|
.substring(0, sendController.selection.baseOffset)
|
||||||
sendController.selection.baseOffset,
|
|
||||||
)
|
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.length -
|
.length -
|
||||||
1;
|
1;
|
||||||
|
|
@ -357,10 +353,11 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
sendingClient = Matrix.of(context).client;
|
sendingClient = Matrix.of(context).client;
|
||||||
final lastEventThreadId =
|
final lastEventThreadId =
|
||||||
room.lastEvent?.relationshipType == RelationshipTypes.thread
|
room.lastEvent?.relationshipType == RelationshipTypes.thread
|
||||||
? room.lastEvent?.relationshipEventId
|
? room.lastEvent?.relationshipEventId
|
||||||
: null;
|
: null;
|
||||||
readMarkerEventId =
|
readMarkerEventId = room.hasNewMessages
|
||||||
room.hasNewMessages ? lastEventThreadId ?? room.fullyRead : '';
|
? lastEventThreadId ?? room.fullyRead
|
||||||
|
: '';
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
_tryLoadTimeline();
|
_tryLoadTimeline();
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
|
|
@ -402,11 +399,11 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
var readMarkerEventIndex = readMarkerEventId.isEmpty
|
var readMarkerEventIndex = readMarkerEventId.isEmpty
|
||||||
? -1
|
? -1
|
||||||
: timeline!.events
|
: timeline!.events
|
||||||
.filterByVisibleInGui(
|
.filterByVisibleInGui(
|
||||||
exceptionEventId: readMarkerEventId,
|
exceptionEventId: readMarkerEventId,
|
||||||
threadId: activeThreadId,
|
threadId: activeThreadId,
|
||||||
)
|
)
|
||||||
.indexWhere((e) => e.eventId == readMarkerEventId);
|
.indexWhere((e) => e.eventId == readMarkerEventId);
|
||||||
|
|
||||||
// Read marker is existing but not found in first events. Try a single
|
// Read marker is existing but not found in first events. Try a single
|
||||||
// requestHistory call before opening timeline on event context:
|
// requestHistory call before opening timeline on event context:
|
||||||
|
|
@ -441,12 +438,12 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
String? scrollUpBannerEventId;
|
String? scrollUpBannerEventId;
|
||||||
|
|
||||||
void discardScrollUpBannerEventId() => setState(() {
|
void discardScrollUpBannerEventId() => setState(() {
|
||||||
scrollUpBannerEventId = null;
|
scrollUpBannerEventId = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
void _showScrollUpMaterialBanner(String eventId) => setState(() {
|
void _showScrollUpMaterialBanner(String eventId) => setState(() {
|
||||||
scrollUpBannerEventId = eventId;
|
scrollUpBannerEventId = eventId;
|
||||||
});
|
});
|
||||||
|
|
||||||
void updateView() {
|
void updateView() {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
@ -463,9 +460,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
animateInEventIndex = i;
|
animateInEventIndex = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _getTimeline({
|
Future<void> _getTimeline({String? eventContextId}) async {
|
||||||
String? eventContextId,
|
|
||||||
}) async {
|
|
||||||
await Matrix.of(context).client.roomsLoading;
|
await Matrix.of(context).client.roomsLoading;
|
||||||
await Matrix.of(context).client.accountDataLoading;
|
await Matrix.of(context).client.accountDataLoading;
|
||||||
if (eventContextId != null &&
|
if (eventContextId != null &&
|
||||||
|
|
@ -534,12 +529,12 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
// ignore: unawaited_futures
|
// ignore: unawaited_futures
|
||||||
_setReadMarkerFuture = timeline
|
_setReadMarkerFuture = timeline
|
||||||
.setReadMarker(
|
.setReadMarker(
|
||||||
eventId: eventId,
|
eventId: eventId,
|
||||||
public: AppSettings.sendPublicReadReceipts.value,
|
public: AppSettings.sendPublicReadReceipts.value,
|
||||||
)
|
)
|
||||||
.then((_) {
|
.then((_) {
|
||||||
_setReadMarkerFuture = null;
|
_setReadMarkerFuture = null;
|
||||||
});
|
});
|
||||||
if (eventId == null || eventId == timeline.room.lastEvent?.eventId) {
|
if (eventId == null || eventId == timeline.room.lastEvent?.eventId) {
|
||||||
Matrix.of(context).backgroundPush?.cancelNotification(roomId);
|
Matrix.of(context).backgroundPush?.cancelNotification(roomId);
|
||||||
}
|
}
|
||||||
|
|
@ -579,8 +574,8 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
}
|
}
|
||||||
|
|
||||||
void setActiveClient(Client c) => setState(() {
|
void setActiveClient(Client c) => setState(() {
|
||||||
Matrix.of(context).setActiveClient(c);
|
Matrix.of(context).setActiveClient(c);
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<void> send() async {
|
Future<void> send() async {
|
||||||
if (sendController.text.trim().isEmpty) return;
|
if (sendController.text.trim().isEmpty) return;
|
||||||
|
|
@ -627,11 +622,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendFileAction({FileSelectorType type = FileSelectorType.any}) async {
|
void sendFileAction({FileSelectorType type = FileSelectorType.any}) async {
|
||||||
final files = await selectFiles(
|
final files = await selectFiles(context, allowMultiple: true, type: type);
|
||||||
context,
|
|
||||||
allowMultiple: true,
|
|
||||||
type: type,
|
|
||||||
);
|
|
||||||
if (files.isEmpty) return;
|
if (files.isEmpty) return;
|
||||||
await showAdaptiveDialog(
|
await showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -722,31 +713,26 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
setState(() {
|
setState(() {
|
||||||
replyEvent = null;
|
replyEvent = null;
|
||||||
});
|
});
|
||||||
room.sendFileEvent(
|
room
|
||||||
file,
|
.sendFileEvent(
|
||||||
inReplyTo: replyEvent,
|
file,
|
||||||
threadRootEventId: activeThreadId,
|
inReplyTo: replyEvent,
|
||||||
extraContent: {
|
threadRootEventId: activeThreadId,
|
||||||
'info': {
|
extraContent: {
|
||||||
...file.info,
|
'info': {...file.info, 'duration': duration},
|
||||||
'duration': duration,
|
'org.matrix.msc3245.voice': {},
|
||||||
},
|
'org.matrix.msc1767.audio': {
|
||||||
'org.matrix.msc3245.voice': {},
|
'duration': duration,
|
||||||
'org.matrix.msc1767.audio': {
|
'waveform': waveform,
|
||||||
'duration': duration,
|
},
|
||||||
'waveform': waveform,
|
},
|
||||||
},
|
)
|
||||||
},
|
.catchError((e) {
|
||||||
).catchError((e) {
|
scaffoldMessenger.showSnackBar(
|
||||||
scaffoldMessenger.showSnackBar(
|
SnackBar(content: Text((e as Object).toLocalizedString(context))),
|
||||||
SnackBar(
|
);
|
||||||
content: Text(
|
return null;
|
||||||
(e as Object).toLocalizedString(context),
|
});
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -785,7 +771,9 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
}
|
}
|
||||||
for (final event in selectedEvents) {
|
for (final event in selectedEvents) {
|
||||||
if (copyString.isNotEmpty) copyString += '\n\n';
|
if (copyString.isNotEmpty) copyString += '\n\n';
|
||||||
copyString += event.getDisplayEvent(timeline!).calcLocalizedBodyFallback(
|
copyString += event
|
||||||
|
.getDisplayEvent(timeline!)
|
||||||
|
.calcLocalizedBodyFallback(
|
||||||
MatrixLocals(L10n.of(context)),
|
MatrixLocals(L10n.of(context)),
|
||||||
withSenderNamePrefix: true,
|
withSenderNamePrefix: true,
|
||||||
);
|
);
|
||||||
|
|
@ -813,14 +801,8 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
value: -100,
|
value: -100,
|
||||||
label: L10n.of(context).extremeOffensive,
|
label: L10n.of(context).extremeOffensive,
|
||||||
),
|
),
|
||||||
AdaptiveModalAction(
|
AdaptiveModalAction(value: -50, label: L10n.of(context).offensive),
|
||||||
value: -50,
|
AdaptiveModalAction(value: 0, label: L10n.of(context).inoffensive),
|
||||||
label: L10n.of(context).offensive,
|
|
||||||
),
|
|
||||||
AdaptiveModalAction(
|
|
||||||
value: 0,
|
|
||||||
label: L10n.of(context).inoffensive,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
if (score == null) return;
|
if (score == null) return;
|
||||||
|
|
@ -835,11 +817,11 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
final result = await showFutureLoadingDialog(
|
final result = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => Matrix.of(context).client.reportEvent(
|
future: () => Matrix.of(context).client.reportEvent(
|
||||||
event.roomId!,
|
event.roomId!,
|
||||||
event.eventId,
|
event.eventId,
|
||||||
reason: reason,
|
reason: reason,
|
||||||
score: score,
|
score: score,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (result.error != null) return;
|
if (result.error != null) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -905,9 +887,10 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final room = client.getRoomById(roomId)!;
|
final room = client.getRoomById(roomId)!;
|
||||||
await Event.fromJson(event.toJson(), room).redactEvent(
|
await Event.fromJson(
|
||||||
reason: reason,
|
event.toJson(),
|
||||||
);
|
room,
|
||||||
|
).redactEvent(reason: reason);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await event.cancelSend();
|
await event.cancelSend();
|
||||||
|
|
@ -957,8 +940,9 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
!selectedEvents.first.status.isSent) {
|
!selectedEvents.first.status.isSent) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return currentRoomBundle
|
return currentRoomBundle.any(
|
||||||
.any((cl) => selectedEvents.first.senderId == cl!.userID);
|
(cl) => selectedEvents.first.senderId == cl!.userID,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void forwardEventsAction() async {
|
void forwardEventsAction() async {
|
||||||
|
|
@ -966,9 +950,9 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
final timeline = this.timeline;
|
final timeline = this.timeline;
|
||||||
if (timeline == null) return;
|
if (timeline == null) return;
|
||||||
|
|
||||||
final forwardEvents = List<Event>.from(selectedEvents)
|
final forwardEvents = List<Event>.from(
|
||||||
.map((event) => event.getDisplayEvent(timeline))
|
selectedEvents,
|
||||||
.toList();
|
).map((event) => event.getDisplayEvent(timeline)).toList();
|
||||||
|
|
||||||
await showScaffoldDialog(
|
await showScaffoldDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -1004,29 +988,29 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
inputFocus.requestFocus();
|
inputFocus.requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void scrollToEventId(
|
void scrollToEventId(String eventId, {bool highlightEvent = true}) async {
|
||||||
String eventId, {
|
final foundEvent = timeline!.events.firstWhereOrNull(
|
||||||
bool highlightEvent = true,
|
(event) => event.eventId == eventId,
|
||||||
}) async {
|
);
|
||||||
final foundEvent =
|
|
||||||
timeline!.events.firstWhereOrNull((event) => event.eventId == eventId);
|
|
||||||
|
|
||||||
final eventIndex = foundEvent == null
|
final eventIndex = foundEvent == null
|
||||||
? -1
|
? -1
|
||||||
: timeline!.events
|
: timeline!.events
|
||||||
.filterByVisibleInGui(
|
.filterByVisibleInGui(
|
||||||
exceptionEventId: eventId,
|
exceptionEventId: eventId,
|
||||||
threadId: activeThreadId,
|
threadId: activeThreadId,
|
||||||
)
|
)
|
||||||
.indexOf(foundEvent);
|
.indexOf(foundEvent);
|
||||||
|
|
||||||
if (eventIndex == -1) {
|
if (eventIndex == -1) {
|
||||||
setState(() {
|
setState(() {
|
||||||
timeline = null;
|
timeline = null;
|
||||||
_scrolledUp = false;
|
_scrolledUp = false;
|
||||||
loadTimelineFuture = _getTimeline(eventContextId: eventId).onError(
|
loadTimelineFuture = _getTimeline(eventContextId: eventId).onError(
|
||||||
ErrorReporter(context, 'Unable to load timeline after scroll to ID')
|
ErrorReporter(
|
||||||
.onErrorCallback,
|
context,
|
||||||
|
'Unable to load timeline after scroll to ID',
|
||||||
|
).onErrorCallback,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
await loadTimelineFuture;
|
await loadTimelineFuture;
|
||||||
|
|
@ -1054,8 +1038,10 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
timeline = null;
|
timeline = null;
|
||||||
_scrolledUp = false;
|
_scrolledUp = false;
|
||||||
loadTimelineFuture = _getTimeline().onError(
|
loadTimelineFuture = _getTimeline().onError(
|
||||||
ErrorReporter(context, 'Unable to load timeline after scroll down')
|
ErrorReporter(
|
||||||
.onErrorCallback,
|
context,
|
||||||
|
'Unable to load timeline after scroll down',
|
||||||
|
).onErrorCallback,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
await loadTimelineFuture;
|
await loadTimelineFuture;
|
||||||
|
|
@ -1093,9 +1079,9 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearSelectedEvents() => setState(() {
|
void clearSelectedEvents() => setState(() {
|
||||||
selectedEvents.clear();
|
selectedEvents.clear();
|
||||||
showEmojiPicker = false;
|
showEmojiPicker = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
void clearSingleSelectedEvent() {
|
void clearSingleSelectedEvent() {
|
||||||
if (selectedEvents.length <= 1) {
|
if (selectedEvents.length <= 1) {
|
||||||
|
|
@ -1115,12 +1101,13 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
setState(() {
|
setState(() {
|
||||||
pendingText = sendController.text;
|
pendingText = sendController.text;
|
||||||
editEvent = selectedEvents.first;
|
editEvent = selectedEvents.first;
|
||||||
sendController.text =
|
sendController.text = editEvent!
|
||||||
editEvent!.getDisplayEvent(timeline!).calcLocalizedBodyFallback(
|
.getDisplayEvent(timeline!)
|
||||||
MatrixLocals(L10n.of(context)),
|
.calcLocalizedBodyFallback(
|
||||||
withSenderNamePrefix: false,
|
MatrixLocals(L10n.of(context)),
|
||||||
hideReply: true,
|
withSenderNamePrefix: false,
|
||||||
);
|
hideReply: true,
|
||||||
|
);
|
||||||
selectedEvents.clear();
|
selectedEvents.clear();
|
||||||
});
|
});
|
||||||
inputFocus.requestFocus();
|
inputFocus.requestFocus();
|
||||||
|
|
@ -1145,22 +1132,15 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.go('/rooms/${result.result!}');
|
context.go('/rooms/${result.result!}');
|
||||||
|
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(context: context, future: room.leave);
|
||||||
context: context,
|
|
||||||
future: room.leave,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSelectMessage(Event event) {
|
void onSelectMessage(Event event) {
|
||||||
if (!event.redacted) {
|
if (!event.redacted) {
|
||||||
if (selectedEvents.contains(event)) {
|
if (selectedEvents.contains(event)) {
|
||||||
setState(
|
setState(() => selectedEvents.remove(event));
|
||||||
() => selectedEvents.remove(event),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
setState(
|
setState(() => selectedEvents.add(event));
|
||||||
() => selectedEvents.add(event),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
selectedEvents.sort(
|
selectedEvents.sort(
|
||||||
(a, b) => a.originServerTs.compareTo(b.originServerTs),
|
(a, b) => a.originServerTs.compareTo(b.originServerTs),
|
||||||
|
|
@ -1243,7 +1223,8 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
void pinEvent() {
|
void pinEvent() {
|
||||||
final pinnedEventIds = room.pinnedEventIds;
|
final pinnedEventIds = room.pinnedEventIds;
|
||||||
final selectedEventIds = selectedEvents.map((e) => e.eventId).toSet();
|
final selectedEventIds = selectedEvents.map((e) => e.eventId).toSet();
|
||||||
final unpin = selectedEventIds.length == 1 &&
|
final unpin =
|
||||||
|
selectedEventIds.length == 1 &&
|
||||||
pinnedEventIds.contains(selectedEventIds.single);
|
pinnedEventIds.contains(selectedEventIds.single);
|
||||||
if (unpin) {
|
if (unpin) {
|
||||||
pinnedEventIds.removeWhere(selectedEventIds.contains);
|
pinnedEventIds.removeWhere(selectedEventIds.contains);
|
||||||
|
|
@ -1353,20 +1334,20 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
try {
|
try {
|
||||||
await voipPlugin!.voip.inviteToCall(room, callType);
|
await voipPlugin!.voip.inviteToCall(room, callType);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text(e.toLocalizedString(context))),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cancelReplyEventAction() => setState(() {
|
void cancelReplyEventAction() => setState(() {
|
||||||
if (editEvent != null) {
|
if (editEvent != null) {
|
||||||
sendController.text = pendingText;
|
sendController.text = pendingText;
|
||||||
pendingText = '';
|
pendingText = '';
|
||||||
}
|
}
|
||||||
replyEvent = null;
|
replyEvent = null;
|
||||||
editEvent = null;
|
editEvent = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
late final ValueNotifier<bool> _displayChatDetailsColumn;
|
late final ValueNotifier<bool> _displayChatDetailsColumn;
|
||||||
|
|
||||||
|
|
@ -1382,38 +1363,30 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(child: ChatView(this)),
|
||||||
child: ChatView(this),
|
|
||||||
),
|
|
||||||
ValueListenableBuilder(
|
ValueListenableBuilder(
|
||||||
valueListenable: _displayChatDetailsColumn,
|
valueListenable: _displayChatDetailsColumn,
|
||||||
builder: (context, displayChatDetailsColumn, _) =>
|
builder: (context, displayChatDetailsColumn, _) =>
|
||||||
!FluffyThemes.isThreeColumnMode(context) ||
|
!FluffyThemes.isThreeColumnMode(context) ||
|
||||||
room.membership != Membership.join ||
|
room.membership != Membership.join ||
|
||||||
!displayChatDetailsColumn
|
!displayChatDetailsColumn
|
||||||
? const SizedBox(
|
? const SizedBox(height: double.infinity, width: 0)
|
||||||
height: double.infinity,
|
: Container(
|
||||||
width: 0,
|
width: FluffyThemes.columnWidth,
|
||||||
)
|
clipBehavior: Clip.hardEdge,
|
||||||
: Container(
|
decoration: BoxDecoration(
|
||||||
width: FluffyThemes.columnWidth,
|
border: Border(
|
||||||
clipBehavior: Clip.hardEdge,
|
left: BorderSide(width: 1, color: theme.dividerColor),
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
left: BorderSide(
|
|
||||||
width: 1,
|
|
||||||
color: theme.dividerColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ChatDetails(
|
|
||||||
roomId: roomId,
|
|
||||||
embeddedCloseButton: IconButton(
|
|
||||||
icon: const Icon(Icons.close),
|
|
||||||
onPressed: toggleDisplayChatDetailsColumn,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
child: ChatDetails(
|
||||||
|
roomId: roomId,
|
||||||
|
embeddedCloseButton: IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: toggleDisplayChatDetailsColumn,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ class ChatAppBarTitle extends StatelessWidget {
|
||||||
onTap: controller.isArchived
|
onTap: controller.isArchived
|
||||||
? null
|
? null
|
||||||
: () => FluffyThemes.isThreeColumnMode(context)
|
: () => FluffyThemes.isThreeColumnMode(context)
|
||||||
? controller.toggleDisplayChatDetailsColumn()
|
? controller.toggleDisplayChatDetailsColumn()
|
||||||
: context.go('/rooms/${room.id}/details'),
|
: context.go('/rooms/${room.id}/details'),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Hero(
|
Hero(
|
||||||
|
|
@ -51,22 +51,22 @@ class ChatAppBarTitle extends StatelessWidget {
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: .start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
|
room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 16),
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
StreamBuilder(
|
StreamBuilder(
|
||||||
stream: room.client.onSyncStatus.stream,
|
stream: room.client.onSyncStatus.stream,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final status = room.client.onSyncStatus.value ??
|
final status =
|
||||||
|
room.client.onSyncStatus.value ??
|
||||||
const SyncStatusUpdate(SyncStatus.waitingForResponse);
|
const SyncStatusUpdate(SyncStatus.waitingForResponse);
|
||||||
final hide = FluffyThemes.isColumnMode(context) ||
|
final hide =
|
||||||
|
FluffyThemes.isColumnMode(context) ||
|
||||||
(room.client.onSync.value != null &&
|
(room.client.onSync.value != null &&
|
||||||
status.status != SyncStatus.error &&
|
status.status != SyncStatus.error &&
|
||||||
room.client.prevBatch != null);
|
room.client.prevBatch != null);
|
||||||
|
|
@ -91,8 +91,9 @@ class ChatAppBarTitle extends StatelessWidget {
|
||||||
if (lastActiveTimestamp != null) {
|
if (lastActiveTimestamp != null) {
|
||||||
return Text(
|
return Text(
|
||||||
L10n.of(context).lastActiveAgo(
|
L10n.of(context).lastActiveAgo(
|
||||||
lastActiveTimestamp
|
lastActiveTimestamp.localizedTimeShort(
|
||||||
.localizedTimeShort(context),
|
context,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
style: style,
|
style: style,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,9 @@ class ChatEmojiPicker extends StatelessWidget {
|
||||||
),
|
),
|
||||||
categoryViewConfig: CategoryViewConfig(
|
categoryViewConfig: CategoryViewConfig(
|
||||||
backspaceColor: theme.colorScheme.primary,
|
backspaceColor: theme.colorScheme.primary,
|
||||||
iconColor:
|
iconColor: theme.colorScheme.primary.withAlpha(
|
||||||
theme.colorScheme.primary.withAlpha(128),
|
128,
|
||||||
|
),
|
||||||
iconColorSelected: theme.colorScheme.primary,
|
iconColorSelected: theme.colorScheme.primary,
|
||||||
indicatorColor: theme.colorScheme.primary,
|
indicatorColor: theme.colorScheme.primary,
|
||||||
backgroundColor: theme.colorScheme.surface,
|
backgroundColor: theme.colorScheme.surface,
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,7 @@ import 'package:fluffychat/utils/platform_infos.dart';
|
||||||
class ChatEventList extends StatelessWidget {
|
class ChatEventList extends StatelessWidget {
|
||||||
final ChatController controller;
|
final ChatController controller;
|
||||||
|
|
||||||
const ChatEventList({
|
const ChatEventList({super.key, required this.controller});
|
||||||
super.key,
|
|
||||||
required this.controller,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
@ -30,10 +27,7 @@ class ChatEventList extends StatelessWidget {
|
||||||
}
|
}
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
final colors = [
|
final colors = [theme.secondaryBubbleColor, theme.bubbleColor];
|
||||||
theme.secondaryBubbleColor,
|
|
||||||
theme.bubbleColor,
|
|
||||||
];
|
|
||||||
|
|
||||||
final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0;
|
final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0;
|
||||||
|
|
||||||
|
|
@ -83,11 +77,8 @@ class ChatEventList extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [SeenByRow(controller), TypingIndicators(controller)],
|
||||||
SeenByRow(controller),
|
|
||||||
TypingIndicators(controller),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,8 +92,9 @@ class ChatEventList extends StatelessWidget {
|
||||||
!event.isCollapsedState && event.isVisibleInGui,
|
!event.isCollapsedState && event.isVisibleInGui,
|
||||||
);
|
);
|
||||||
if (visibleIndex > timeline.events.length - 50) {
|
if (visibleIndex > timeline.events.length - 50) {
|
||||||
WidgetsBinding.instance
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
.addPostFrameCallback(controller.requestHistory);
|
controller.requestHistory,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return Center(
|
return Center(
|
||||||
child: AnimatedSwitcher(
|
child: AnimatedSwitcher(
|
||||||
|
|
@ -126,7 +118,8 @@ class ChatEventList extends StatelessWidget {
|
||||||
|
|
||||||
// The message at this index:
|
// The message at this index:
|
||||||
final event = events[i];
|
final event = events[i];
|
||||||
final animateIn = animateInEventIndex != null &&
|
final animateIn =
|
||||||
|
animateInEventIndex != null &&
|
||||||
timeline.events.length > animateInEventIndex &&
|
timeline.events.length > animateInEventIndex &&
|
||||||
event == timeline.events[animateInEventIndex];
|
event == timeline.events[animateInEventIndex];
|
||||||
|
|
||||||
|
|
@ -134,10 +127,12 @@ class ChatEventList extends StatelessWidget {
|
||||||
final previousEvent = i > 0 ? events[i - 1] : null;
|
final previousEvent = i > 0 ? events[i - 1] : null;
|
||||||
|
|
||||||
// Collapsed state event
|
// Collapsed state event
|
||||||
final canExpand = event.isCollapsedState &&
|
final canExpand =
|
||||||
|
event.isCollapsedState &&
|
||||||
nextEvent?.isCollapsedState == true &&
|
nextEvent?.isCollapsedState == true &&
|
||||||
previousEvent?.isCollapsedState != true;
|
previousEvent?.isCollapsedState != true;
|
||||||
final isCollapsed = event.isCollapsedState &&
|
final isCollapsed =
|
||||||
|
event.isCollapsedState &&
|
||||||
previousEvent?.isCollapsedState == true &&
|
previousEvent?.isCollapsedState == true &&
|
||||||
!controller.expandedEventIds.contains(event.eventId);
|
!controller.expandedEventIds.contains(event.eventId);
|
||||||
|
|
||||||
|
|
@ -161,11 +156,12 @@ class ChatEventList extends StatelessWidget {
|
||||||
scrollToEventId: (String eventId) =>
|
scrollToEventId: (String eventId) =>
|
||||||
controller.scrollToEventId(eventId),
|
controller.scrollToEventId(eventId),
|
||||||
longPressSelect: controller.selectedEvents.isNotEmpty,
|
longPressSelect: controller.selectedEvents.isNotEmpty,
|
||||||
selected: controller.selectedEvents
|
selected: controller.selectedEvents.any(
|
||||||
.any((e) => e.eventId == event.eventId),
|
(e) => e.eventId == event.eventId,
|
||||||
|
),
|
||||||
singleSelected:
|
singleSelected:
|
||||||
controller.selectedEvents.singleOrNull?.eventId ==
|
controller.selectedEvents.singleOrNull?.eventId ==
|
||||||
event.eventId,
|
event.eventId,
|
||||||
onEdit: () => controller.editSelectedEventAction(),
|
onEdit: () => controller.editSelectedEventAction(),
|
||||||
timeline: timeline,
|
timeline: timeline,
|
||||||
displayReadMarker:
|
displayReadMarker:
|
||||||
|
|
@ -181,9 +177,9 @@ class ChatEventList extends StatelessWidget {
|
||||||
: null,
|
: null,
|
||||||
onExpand: canExpand
|
onExpand: canExpand
|
||||||
? () => controller.expandEventsFrom(
|
? () => controller.expandEventsFrom(
|
||||||
event,
|
event,
|
||||||
!controller.expandedEventIds.contains(event.eventId),
|
!controller.expandedEventIds.contains(event.eventId),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -53,12 +53,13 @@ class ChatInputRow extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: .end,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: .spaceBetween,
|
||||||
children: controller.selectMode
|
children: controller.selectMode
|
||||||
? <Widget>[
|
? <Widget>[
|
||||||
if (controller.selectedEvents
|
if (controller.selectedEvents.every(
|
||||||
.every((event) => event.status == EventStatus.error))
|
(event) => event.status == EventStatus.error,
|
||||||
|
))
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: height,
|
height: height,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
|
|
@ -90,36 +91,36 @@ class ChatInputRow extends StatelessWidget {
|
||||||
),
|
),
|
||||||
controller.selectedEvents.length == 1
|
controller.selectedEvents.length == 1
|
||||||
? controller.selectedEvents.first
|
? controller.selectedEvents.first
|
||||||
.getDisplayEvent(controller.timeline!)
|
.getDisplayEvent(controller.timeline!)
|
||||||
.status
|
.status
|
||||||
.isSent
|
.isSent
|
||||||
? SizedBox(
|
? SizedBox(
|
||||||
height: height,
|
height: height,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
style: selectedTextButtonStyle,
|
style: selectedTextButtonStyle,
|
||||||
onPressed: controller.replyAction,
|
onPressed: controller.replyAction,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(L10n.of(context).reply),
|
Text(L10n.of(context).reply),
|
||||||
const Icon(Icons.keyboard_arrow_right),
|
const Icon(Icons.keyboard_arrow_right),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
: SizedBox(
|
||||||
: SizedBox(
|
height: height,
|
||||||
height: height,
|
child: TextButton(
|
||||||
child: TextButton(
|
style: selectedTextButtonStyle,
|
||||||
style: selectedTextButtonStyle,
|
onPressed: controller.sendAgainAction,
|
||||||
onPressed: controller.sendAgainAction,
|
child: Row(
|
||||||
child: Row(
|
children: <Widget>[
|
||||||
children: <Widget>[
|
Text(L10n.of(context).tryToSendAgain),
|
||||||
Text(L10n.of(context).tryToSendAgain),
|
const SizedBox(width: 4),
|
||||||
const SizedBox(width: 4),
|
const Icon(Icons.send_outlined, size: 16),
|
||||||
const Icon(Icons.send_outlined, size: 16),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
]
|
]
|
||||||
: <Widget>[
|
: <Widget>[
|
||||||
|
|
@ -127,8 +128,9 @@ class ChatInputRow extends StatelessWidget {
|
||||||
AnimatedContainer(
|
AnimatedContainer(
|
||||||
duration: FluffyThemes.animationDuration,
|
duration: FluffyThemes.animationDuration,
|
||||||
curve: FluffyThemes.animationCurve,
|
curve: FluffyThemes.animationCurve,
|
||||||
width:
|
width: controller.sendController.text.isNotEmpty
|
||||||
controller.sendController.text.isNotEmpty ? 0 : height,
|
? 0
|
||||||
|
: height,
|
||||||
height: height,
|
height: height,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
decoration: const BoxDecoration(),
|
decoration: const BoxDecoration(),
|
||||||
|
|
@ -190,8 +192,9 @@ class ChatInputRow extends StatelessWidget {
|
||||||
theme.colorScheme.onPrimaryContainer,
|
theme.colorScheme.onPrimaryContainer,
|
||||||
foregroundColor:
|
foregroundColor:
|
||||||
theme.colorScheme.primaryContainer,
|
theme.colorScheme.primaryContainer,
|
||||||
child:
|
child: const Icon(
|
||||||
const Icon(Icons.video_camera_back_outlined),
|
Icons.video_camera_back_outlined,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
title: Text(L10n.of(context).sendVideo),
|
title: Text(L10n.of(context).sendVideo),
|
||||||
contentPadding: const EdgeInsets.all(0),
|
contentPadding: const EdgeInsets.all(0),
|
||||||
|
|
@ -270,19 +273,20 @@ class ChatInputRow extends StatelessWidget {
|
||||||
tooltip: L10n.of(context).emojis,
|
tooltip: L10n.of(context).emojis,
|
||||||
color: theme.colorScheme.onPrimaryContainer,
|
color: theme.colorScheme.onPrimaryContainer,
|
||||||
icon: PageTransitionSwitcher(
|
icon: PageTransitionSwitcher(
|
||||||
transitionBuilder: (
|
transitionBuilder:
|
||||||
Widget child,
|
(
|
||||||
Animation<double> primaryAnimation,
|
Widget child,
|
||||||
Animation<double> secondaryAnimation,
|
Animation<double> primaryAnimation,
|
||||||
) {
|
Animation<double> secondaryAnimation,
|
||||||
return SharedAxisTransition(
|
) {
|
||||||
animation: primaryAnimation,
|
return SharedAxisTransition(
|
||||||
secondaryAnimation: secondaryAnimation,
|
animation: primaryAnimation,
|
||||||
transitionType: SharedAxisTransitionType.scaled,
|
secondaryAnimation: secondaryAnimation,
|
||||||
fillColor: Colors.transparent,
|
transitionType: SharedAxisTransitionType.scaled,
|
||||||
child: child,
|
fillColor: Colors.transparent,
|
||||||
);
|
child: child,
|
||||||
},
|
);
|
||||||
|
},
|
||||||
child: Icon(
|
child: Icon(
|
||||||
controller.showEmojiPicker
|
controller.showEmojiPicker
|
||||||
? Icons.keyboard
|
? Icons.keyboard
|
||||||
|
|
@ -313,9 +317,9 @@ class ChatInputRow extends StatelessWidget {
|
||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
textInputAction:
|
textInputAction:
|
||||||
AppSettings.sendOnEnter.value == true &&
|
AppSettings.sendOnEnter.value == true &&
|
||||||
PlatformInfos.isMobile
|
PlatformInfos.isMobile
|
||||||
? TextInputAction.send
|
? TextInputAction.send
|
||||||
: null,
|
: null,
|
||||||
onSubmitted: controller.onInputBarSubmitted,
|
onSubmitted: controller.onInputBarSubmitted,
|
||||||
onSubmitImage: controller.sendImageFromClipBoard,
|
onSubmitImage: controller.sendImageFromClipBoard,
|
||||||
focusNode: controller.inputFocus,
|
focusNode: controller.inputFocus,
|
||||||
|
|
@ -335,14 +339,18 @@ class ChatInputRow extends StatelessWidget {
|
||||||
filled: false,
|
filled: false,
|
||||||
),
|
),
|
||||||
onChanged: controller.onInputBarChanged,
|
onChanged: controller.onInputBarChanged,
|
||||||
suggestionEmojis: getDefaultEmojiLocale(
|
suggestionEmojis:
|
||||||
AppSettings.emojiSuggestionLocale.value.isNotEmpty
|
getDefaultEmojiLocale(
|
||||||
? Locale(AppSettings.emojiSuggestionLocale.value)
|
AppSettings.emojiSuggestionLocale.value.isNotEmpty
|
||||||
: Localizations.localeOf(context),
|
? Locale(
|
||||||
).fold(
|
AppSettings.emojiSuggestionLocale.value,
|
||||||
[],
|
)
|
||||||
(emojis, category) => emojis..addAll(category.emoji),
|
: Localizations.localeOf(context),
|
||||||
),
|
).fold(
|
||||||
|
[],
|
||||||
|
(emojis, category) =>
|
||||||
|
emojis..addAll(category.emoji),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -350,19 +358,21 @@ class ChatInputRow extends StatelessWidget {
|
||||||
height: height,
|
height: height,
|
||||||
width: height,
|
width: height,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: PlatformInfos.platformCanRecord &&
|
child:
|
||||||
|
PlatformInfos.platformCanRecord &&
|
||||||
controller.sendController.text.isEmpty
|
controller.sendController.text.isEmpty
|
||||||
? IconButton(
|
? IconButton(
|
||||||
tooltip: L10n.of(context).voiceMessage,
|
tooltip: L10n.of(context).voiceMessage,
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
L10n.of(context)
|
L10n.of(
|
||||||
.longPressToRecordVoiceMessage,
|
context,
|
||||||
|
).longPressToRecordVoiceMessage,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
onLongPress: () => recordingViewModel
|
onLongPress: () => recordingViewModel
|
||||||
.startRecording(controller.room),
|
.startRecording(controller.room),
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
|
|
@ -394,9 +404,9 @@ class _ChatAccountPicker extends StatelessWidget {
|
||||||
const _ChatAccountPicker(this.controller);
|
const _ChatAccountPicker(this.controller);
|
||||||
|
|
||||||
void _popupMenuButtonSelected(String mxid, BuildContext context) {
|
void _popupMenuButtonSelected(String mxid, BuildContext context) {
|
||||||
final client = Matrix.of(context)
|
final client = Matrix.of(
|
||||||
.currentBundle!
|
context,
|
||||||
.firstWhere((cl) => cl!.userID == mxid, orElse: () => null);
|
).currentBundle!.firstWhere((cl) => cl!.userID == mxid, orElse: () => null);
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
Logs().w('Attempted to switch to a non-existing client $mxid');
|
Logs().w('Attempted to switch to a non-existing client $mxid');
|
||||||
return;
|
return;
|
||||||
|
|
@ -423,7 +433,8 @@ class _ChatAccountPicker extends StatelessWidget {
|
||||||
builder: (context, snapshot) => ListTile(
|
builder: (context, snapshot) => ListTile(
|
||||||
leading: Avatar(
|
leading: Avatar(
|
||||||
mxContent: snapshot.data?.avatarUrl,
|
mxContent: snapshot.data?.avatarUrl,
|
||||||
name: snapshot.data?.displayName ??
|
name:
|
||||||
|
snapshot.data?.displayName ??
|
||||||
client.userID!.localpart,
|
client.userID!.localpart,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
|
|
@ -436,7 +447,8 @@ class _ChatAccountPicker extends StatelessWidget {
|
||||||
.toList(),
|
.toList(),
|
||||||
child: Avatar(
|
child: Avatar(
|
||||||
mxContent: snapshot.data?.avatarUrl,
|
mxContent: snapshot.data?.avatarUrl,
|
||||||
name: snapshot.data?.displayName ??
|
name:
|
||||||
|
snapshot.data?.displayName ??
|
||||||
Matrix.of(context).client.userID!.localpart,
|
Matrix.of(context).client.userID!.localpart,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,9 @@ class ChatView extends StatelessWidget {
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.message_outlined),
|
icon: const Icon(Icons.message_outlined),
|
||||||
tooltip: L10n.of(context).replyInThread,
|
tooltip: L10n.of(context).replyInThread,
|
||||||
onPressed: () => controller
|
onPressed: () => controller.enterThread(
|
||||||
.enterThread(controller.selectedEvents.single.eventId),
|
controller.selectedEvents.single.eventId,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.copy_outlined),
|
icon: const Icon(Icons.copy_outlined),
|
||||||
|
|
@ -83,7 +84,7 @@ class ChatView extends StatelessWidget {
|
||||||
onTap: controller.pinEvent,
|
onTap: controller.pinEvent,
|
||||||
value: null,
|
value: null,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.push_pin_outlined),
|
const Icon(Icons.push_pin_outlined),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
@ -96,7 +97,7 @@ class ChatView extends StatelessWidget {
|
||||||
onTap: () => controller.saveSelectedEvent(context),
|
onTap: () => controller.saveSelectedEvent(context),
|
||||||
value: null,
|
value: null,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.download_outlined),
|
const Icon(Icons.download_outlined),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
@ -107,7 +108,7 @@ class ChatView extends StatelessWidget {
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: _EventContextAction.info,
|
value: _EventContextAction.info,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.info_outlined),
|
const Icon(Icons.info_outlined),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
@ -119,12 +120,9 @@ class ChatView extends StatelessWidget {
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: _EventContextAction.report,
|
value: _EventContextAction.report,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(
|
const Icon(Icons.shield_outlined, color: Colors.red),
|
||||||
Icons.shield_outlined,
|
|
||||||
color: Colors.red,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Text(L10n.of(context).reportMessage),
|
Text(L10n.of(context).reportMessage),
|
||||||
],
|
],
|
||||||
|
|
@ -166,7 +164,8 @@ class ChatView extends StatelessWidget {
|
||||||
final accountConfig = Matrix.of(context).client.applicationAccountConfig;
|
final accountConfig = Matrix.of(context).client.applicationAccountConfig;
|
||||||
|
|
||||||
return PopScope(
|
return PopScope(
|
||||||
canPop: controller.selectedEvents.isEmpty &&
|
canPop:
|
||||||
|
controller.selectedEvents.isEmpty &&
|
||||||
!controller.showEmojiPicker &&
|
!controller.showEmojiPicker &&
|
||||||
controller.activeThreadId == null,
|
controller.activeThreadId == null,
|
||||||
onPopInvokedWithResult: (pop, _) async {
|
onPopInvokedWithResult: (pop, _) async {
|
||||||
|
|
@ -207,8 +206,8 @@ class ChatView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
backgroundColor: controller.selectedEvents.isEmpty
|
backgroundColor: controller.selectedEvents.isEmpty
|
||||||
? controller.activeThreadId != null
|
? controller.activeThreadId != null
|
||||||
? theme.colorScheme.secondaryContainer
|
? theme.colorScheme.secondaryContainer
|
||||||
: null
|
: null
|
||||||
: theme.colorScheme.tertiaryContainer,
|
: theme.colorScheme.tertiaryContainer,
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
leading: controller.selectMode
|
leading: controller.selectMode
|
||||||
|
|
@ -219,36 +218,31 @@ class ChatView extends StatelessWidget {
|
||||||
color: theme.colorScheme.onTertiaryContainer,
|
color: theme.colorScheme.onTertiaryContainer,
|
||||||
)
|
)
|
||||||
: activeThreadId != null
|
: activeThreadId != null
|
||||||
? IconButton(
|
? IconButton(
|
||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close),
|
||||||
onPressed: controller.closeThread,
|
onPressed: controller.closeThread,
|
||||||
tooltip: L10n.of(context).backToMainChat,
|
tooltip: L10n.of(context).backToMainChat,
|
||||||
color: theme.colorScheme.onSecondaryContainer,
|
color: theme.colorScheme.onSecondaryContainer,
|
||||||
)
|
)
|
||||||
: FluffyThemes.isColumnMode(context)
|
: FluffyThemes.isColumnMode(context)
|
||||||
? null
|
? null
|
||||||
: StreamBuilder<Object>(
|
: StreamBuilder<Object>(
|
||||||
stream: Matrix.of(context)
|
stream: Matrix.of(context).client.onSync.stream.where(
|
||||||
.client
|
(syncUpdate) => syncUpdate.hasRoomUpdate,
|
||||||
.onSync
|
),
|
||||||
.stream
|
builder: (context, _) => UnreadRoomsBadge(
|
||||||
.where(
|
filter: (r) => r.id != controller.roomId,
|
||||||
(syncUpdate) => syncUpdate.hasRoomUpdate,
|
badgePosition: BadgePosition.topEnd(end: 8, top: 4),
|
||||||
),
|
child: const Center(child: BackButton()),
|
||||||
builder: (context, _) => UnreadRoomsBadge(
|
),
|
||||||
filter: (r) => r.id != controller.roomId,
|
),
|
||||||
badgePosition:
|
|
||||||
BadgePosition.topEnd(end: 8, top: 4),
|
|
||||||
child: const Center(child: BackButton()),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
titleSpacing: FluffyThemes.isColumnMode(context) ? 24 : 0,
|
titleSpacing: FluffyThemes.isColumnMode(context) ? 24 : 0,
|
||||||
title: ChatAppBarTitle(controller),
|
title: ChatAppBarTitle(controller),
|
||||||
actions: _appBarActions(context),
|
actions: _appBarActions(context),
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
preferredSize: Size.fromHeight(appbarBottomHeight),
|
preferredSize: Size.fromHeight(appbarBottomHeight),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
PinnedEvents(controller),
|
PinnedEvents(controller),
|
||||||
if (activeThreadId != null)
|
if (activeThreadId != null)
|
||||||
|
|
@ -285,9 +279,7 @@ class ChatView extends StatelessWidget {
|
||||||
title: L10n.of(context).jumpToLastReadMessage,
|
title: L10n.of(context).jumpToLastReadMessage,
|
||||||
trailing: TextButton(
|
trailing: TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
controller.scrollToEventId(
|
controller.scrollToEventId(scrollUpBannerEventId);
|
||||||
scrollUpBannerEventId,
|
|
||||||
);
|
|
||||||
controller.discardScrollUpBannerEventId();
|
controller.discardScrollUpBannerEventId();
|
||||||
},
|
},
|
||||||
child: Text(L10n.of(context).jump),
|
child: Text(L10n.of(context).jump),
|
||||||
|
|
@ -299,7 +291,8 @@ class ChatView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation:
|
floatingActionButtonLocation:
|
||||||
FloatingActionButtonLocation.miniCenterFloat,
|
FloatingActionButtonLocation.miniCenterFloat,
|
||||||
floatingActionButton: controller.showScrollDownButton &&
|
floatingActionButton:
|
||||||
|
controller.showScrollDownButton &&
|
||||||
controller.selectedEvents.isEmpty
|
controller.selectedEvents.isEmpty
|
||||||
? Padding(
|
? Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 56.0),
|
padding: const EdgeInsets.only(bottom: 56.0),
|
||||||
|
|
@ -348,10 +341,7 @@ class ChatView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (controller.showScrollDownButton)
|
if (controller.showScrollDownButton)
|
||||||
Divider(
|
Divider(height: 1, color: theme.dividerColor),
|
||||||
height: 1,
|
|
||||||
color: theme.dividerColor,
|
|
||||||
),
|
|
||||||
if (controller.room.isExtinct)
|
if (controller.room.isExtinct)
|
||||||
Container(
|
Container(
|
||||||
margin: EdgeInsets.all(bottomSheetPadding),
|
margin: EdgeInsets.all(bottomSheetPadding),
|
||||||
|
|
@ -380,14 +370,11 @@ class ChatView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
child: controller.room.isAbandonedDMRoom == true
|
child: controller.room.isAbandonedDMRoom == true
|
||||||
? Row(
|
? Row(
|
||||||
mainAxisAlignment:
|
mainAxisAlignment: .spaceEvenly,
|
||||||
MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
children: [
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding: const EdgeInsets.all(
|
padding: const EdgeInsets.all(16),
|
||||||
16,
|
|
||||||
),
|
|
||||||
foregroundColor:
|
foregroundColor:
|
||||||
theme.colorScheme.error,
|
theme.colorScheme.error,
|
||||||
),
|
),
|
||||||
|
|
@ -395,15 +382,11 @@ class ChatView extends StatelessWidget {
|
||||||
Icons.archive_outlined,
|
Icons.archive_outlined,
|
||||||
),
|
),
|
||||||
onPressed: controller.leaveChat,
|
onPressed: controller.leaveChat,
|
||||||
label: Text(
|
label: Text(L10n.of(context).leave),
|
||||||
L10n.of(context).leave,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding: const EdgeInsets.all(
|
padding: const EdgeInsets.all(16),
|
||||||
16,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.forum_outlined,
|
Icons.forum_outlined,
|
||||||
|
|
@ -416,7 +399,7 @@ class ChatView extends StatelessWidget {
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: Column(
|
: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
ReplyDisplay(controller),
|
ReplyDisplay(controller),
|
||||||
ChatInputRow(controller),
|
ChatInputRow(controller),
|
||||||
|
|
@ -432,10 +415,7 @@ class ChatView extends StatelessWidget {
|
||||||
Container(
|
Container(
|
||||||
color: theme.scaffoldBackgroundColor.withAlpha(230),
|
color: theme.scaffoldBackgroundColor.withAlpha(230),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: const Icon(
|
child: const Icon(Icons.upload_outlined, size: 100),
|
||||||
Icons.upload_outlined,
|
|
||||||
size: 100,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,9 @@ class EncryptionButton extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return StreamBuilder<SyncUpdate>(
|
return StreamBuilder<SyncUpdate>(
|
||||||
stream: Matrix.of(context)
|
stream: Matrix.of(
|
||||||
.client
|
context,
|
||||||
.onSync
|
).client.onSync.stream.where((s) => s.deviceLists != null),
|
||||||
.stream
|
|
||||||
.where((s) => s.deviceLists != null),
|
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final shouldBeEncrypted = room.joinRules != JoinRules.public;
|
final shouldBeEncrypted = room.joinRules != JoinRules.public;
|
||||||
return FutureBuilder<EncryptionHealthState>(
|
return FutureBuilder<EncryptionHealthState>(
|
||||||
|
|
|
||||||
|
|
@ -12,21 +12,16 @@ import 'package:fluffychat/widgets/avatar.dart';
|
||||||
|
|
||||||
extension EventInfoDialogExtension on Event {
|
extension EventInfoDialogExtension on Event {
|
||||||
void showInfoDialog(BuildContext context) => showAdaptiveBottomSheet(
|
void showInfoDialog(BuildContext context) => showAdaptiveBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) =>
|
builder: (context) => EventInfoDialog(l10n: L10n.of(context), event: this),
|
||||||
EventInfoDialog(l10n: L10n.of(context), event: this),
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class EventInfoDialog extends StatelessWidget {
|
class EventInfoDialog extends StatelessWidget {
|
||||||
final Event event;
|
final Event event;
|
||||||
final L10n l10n;
|
final L10n l10n;
|
||||||
|
|
||||||
const EventInfoDialog({
|
const EventInfoDialog({required this.event, required this.l10n, super.key});
|
||||||
required this.event,
|
|
||||||
required this.l10n,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
String prettyJson(MatrixEvent event) {
|
String prettyJson(MatrixEvent event) {
|
||||||
const decoder = JsonDecoder();
|
const decoder = JsonDecoder();
|
||||||
|
|
@ -79,9 +74,7 @@ class EventInfoDialog extends StatelessWidget {
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
prettyJson(MatrixEvent.fromJson(event.toJson())),
|
prettyJson(MatrixEvent.fromJson(event.toJson())),
|
||||||
style: TextStyle(
|
style: TextStyle(color: theme.colorScheme.onSurface),
|
||||||
color: theme.colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -98,9 +91,7 @@ class EventInfoDialog extends StatelessWidget {
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
prettyJson(originalSource),
|
prettyJson(originalSource),
|
||||||
style: TextStyle(
|
style: TextStyle(color: theme.colorScheme.onSurface),
|
||||||
color: theme.colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -100,12 +100,13 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
audioPlayer.pause();
|
audioPlayer.pause();
|
||||||
audioPlayer.dispose();
|
audioPlayer.dispose();
|
||||||
matrix.voiceMessageEventId.value =
|
matrix.voiceMessageEventId.value = matrix.audioPlayer =
|
||||||
matrix.audioPlayer = null;
|
null;
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
ScaffoldMessenger.of(matrix.context)
|
ScaffoldMessenger.of(
|
||||||
.clearMaterialBanners();
|
matrix.context,
|
||||||
|
).clearMaterialBanners();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.close_outlined),
|
icon: const Icon(Icons.close_outlined),
|
||||||
|
|
@ -128,8 +129,8 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
});
|
});
|
||||||
final currentPlayer =
|
final currentPlayer =
|
||||||
matrix.voiceMessageEventId.value != widget.event.eventId
|
matrix.voiceMessageEventId.value != widget.event.eventId
|
||||||
? null
|
? null
|
||||||
: matrix.audioPlayer;
|
: matrix.audioPlayer;
|
||||||
if (currentPlayer != null) {
|
if (currentPlayer != null) {
|
||||||
if (currentPlayer.isAtEndPosition) {
|
if (currentPlayer.isAtEndPosition) {
|
||||||
currentPlayer.seek(Duration.zero);
|
currentPlayer.seek(Duration.zero);
|
||||||
|
|
@ -158,8 +159,9 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
? (progress) {
|
? (progress) {
|
||||||
final progressPercentage = progress / fileSize;
|
final progressPercentage = progress / fileSize;
|
||||||
setState(() {
|
setState(() {
|
||||||
_downloadProgress =
|
_downloadProgress = progressPercentage < 1
|
||||||
progressPercentage < 1 ? progressPercentage : null;
|
? progressPercentage
|
||||||
|
: null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
|
|
@ -190,11 +192,9 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
});
|
});
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().v('Could not download audio file', e, s);
|
Logs().v('Could not download audio file', e, s);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(e.toLocalizedString(context)),
|
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
||||||
),
|
|
||||||
);
|
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
@ -209,9 +209,8 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
}
|
}
|
||||||
|
|
||||||
audioPlayer.play().onError(
|
audioPlayer.play().onError(
|
||||||
ErrorReporter(context, 'Unable to play audio message')
|
ErrorReporter(context, 'Unable to play audio message').onErrorCallback,
|
||||||
.onErrorCallback,
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _toggleSpeed() async {
|
void _toggleSpeed() async {
|
||||||
|
|
@ -289,8 +288,9 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
return ValueListenableBuilder(
|
return ValueListenableBuilder(
|
||||||
valueListenable: matrix.voiceMessageEventId,
|
valueListenable: matrix.voiceMessageEventId,
|
||||||
builder: (context, eventId, _) {
|
builder: (context, eventId, _) {
|
||||||
final audioPlayer =
|
final audioPlayer = eventId != widget.event.eventId
|
||||||
eventId != widget.event.eventId ? null : matrix.audioPlayer;
|
? null
|
||||||
|
: matrix.audioPlayer;
|
||||||
|
|
||||||
final fileDescription = widget.event.fileDescription;
|
final fileDescription = widget.event.fileDescription;
|
||||||
|
|
||||||
|
|
@ -317,15 +317,15 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.all(12.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: .start,
|
||||||
children: [
|
children: [
|
||||||
ConstrainedBox(
|
ConstrainedBox(
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
maxWidth: FluffyThemes.columnWidth,
|
maxWidth: FluffyThemes.columnWidth,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: buttonSize,
|
width: buttonSize,
|
||||||
|
|
@ -366,9 +366,11 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
for (var i = 0;
|
for (
|
||||||
i < AudioPlayerWidget.wavesCount;
|
var i = 0;
|
||||||
i++)
|
i < AudioPlayerWidget.wavesCount;
|
||||||
|
i++
|
||||||
|
)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 32,
|
height: 32,
|
||||||
|
|
@ -376,13 +378,14 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
child: Container(
|
child: Container(
|
||||||
margin:
|
margin:
|
||||||
const EdgeInsets.symmetric(
|
const EdgeInsets.symmetric(
|
||||||
horizontal: 1,
|
horizontal: 1,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: i < wavePosition
|
color: i < wavePosition
|
||||||
? widget.color
|
? widget.color
|
||||||
: widget.color
|
: widget.color.withAlpha(
|
||||||
.withAlpha(128),
|
128,
|
||||||
|
),
|
||||||
borderRadius:
|
borderRadius:
|
||||||
BorderRadius.circular(64),
|
BorderRadius.circular(64),
|
||||||
),
|
),
|
||||||
|
|
@ -396,7 +399,8 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 32,
|
height: 32,
|
||||||
child: Slider(
|
child: Slider(
|
||||||
thumbColor: widget.event.senderId ==
|
thumbColor:
|
||||||
|
widget.event.senderId ==
|
||||||
widget.event.room.client.userID
|
widget.event.room.client.userID
|
||||||
? theme.colorScheme.onPrimary
|
? theme.colorScheme.onPrimary
|
||||||
: theme.colorScheme.primary,
|
: theme.colorScheme.primary,
|
||||||
|
|
@ -425,10 +429,7 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
width: 36,
|
width: 36,
|
||||||
child: Text(
|
child: Text(
|
||||||
statusText,
|
statusText,
|
||||||
style: TextStyle(
|
style: TextStyle(color: widget.color, fontSize: 12),
|
||||||
color: widget.color,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
|
@ -442,11 +443,13 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
),
|
),
|
||||||
secondChild: Material(
|
secondChild: Material(
|
||||||
color: widget.color.withAlpha(64),
|
color: widget.color.withAlpha(64),
|
||||||
borderRadius:
|
borderRadius: BorderRadius.circular(
|
||||||
BorderRadius.circular(AppConfig.borderRadius),
|
AppConfig.borderRadius,
|
||||||
|
),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius:
|
borderRadius: BorderRadius.circular(
|
||||||
BorderRadius.circular(AppConfig.borderRadius),
|
AppConfig.borderRadius,
|
||||||
|
),
|
||||||
onTap: _toggleSpeed,
|
onTap: _toggleSpeed,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 32,
|
width: 32,
|
||||||
|
|
@ -481,8 +484,9 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
),
|
),
|
||||||
child: Linkify(
|
child: Linkify(
|
||||||
text: fileDescription,
|
text: fileDescription,
|
||||||
textScaleFactor:
|
textScaleFactor: MediaQuery.textScalerOf(
|
||||||
MediaQuery.textScalerOf(context).scale(1),
|
context,
|
||||||
|
).scale(1),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: widget.color,
|
color: widget.color,
|
||||||
fontSize: widget.fontSize,
|
fontSize: widget.fontSize,
|
||||||
|
|
|
||||||
|
|
@ -37,13 +37,10 @@ class _CuteContentState extends State<CuteContent> {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: addOverlay,
|
onTap: addOverlay,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(widget.event.text, style: const TextStyle(fontSize: 150)),
|
||||||
widget.event.text,
|
|
||||||
style: const TextStyle(fontSize: 150),
|
|
||||||
),
|
|
||||||
if (label != null) Text(label),
|
if (label != null) Text(label),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -112,10 +109,7 @@ class _CuteEventOverlayState extends State<CuteEventOverlay>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
final List<Size> items = List.generate(
|
final List<Size> items = List.generate(
|
||||||
50,
|
50,
|
||||||
(index) => Size(
|
(index) => Size(Random().nextDouble(), 4 + (Random().nextDouble() * 4)),
|
||||||
Random().nextDouble(),
|
|
||||||
4 + (Random().nextDouble() * 4),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
AnimationController? controller;
|
AnimationController? controller;
|
||||||
|
|
@ -150,14 +144,13 @@ class _CuteEventOverlayState extends State<CuteEventOverlay>
|
||||||
.map(
|
.map(
|
||||||
(position) => Positioned(
|
(position) => Positioned(
|
||||||
left: position.width * width,
|
left: position.width * width,
|
||||||
bottom: (height *
|
bottom:
|
||||||
|
(height *
|
||||||
.25 *
|
.25 *
|
||||||
position.height *
|
position.height *
|
||||||
(controller?.value ?? 0)) -
|
(controller?.value ?? 0)) -
|
||||||
_CuteOverlayContent.size,
|
_CuteOverlayContent.size,
|
||||||
child: _CuteOverlayContent(
|
child: _CuteOverlayContent(emoji: widget.emoji),
|
||||||
emoji: widget.emoji,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
|
@ -186,10 +179,7 @@ class _CuteOverlayContent extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox.square(
|
return SizedBox.square(
|
||||||
dimension: size,
|
dimension: size,
|
||||||
child: Text(
|
child: Text(emoji, style: const TextStyle(fontSize: 48)),
|
||||||
emoji,
|
|
||||||
style: const TextStyle(fontSize: 48),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,8 @@ class HtmlMessage extends StatelessWidget {
|
||||||
if (node is! dom.Element) {
|
if (node is! dom.Element) {
|
||||||
return TextSpan(text: node.text);
|
return TextSpan(text: node.text);
|
||||||
}
|
}
|
||||||
final style = atomOneDarkTheme[node.className.split('-').last] ??
|
final style =
|
||||||
|
atomOneDarkTheme[node.className.split('-').last] ??
|
||||||
atomOneDarkTheme['root'];
|
atomOneDarkTheme['root'];
|
||||||
|
|
||||||
return TextSpan(
|
return TextSpan(
|
||||||
|
|
@ -151,11 +152,7 @@ class HtmlMessage extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transforms a Node to an InlineSpan.
|
/// Transforms a Node to an InlineSpan.
|
||||||
InlineSpan _renderHtml(
|
InlineSpan _renderHtml(dom.Node node, BuildContext context, {int depth = 1}) {
|
||||||
dom.Node node,
|
|
||||||
BuildContext context, {
|
|
||||||
int depth = 1,
|
|
||||||
}) {
|
|
||||||
// We must not render elements nested more than 100 elements deep:
|
// We must not render elements nested more than 100 elements deep:
|
||||||
if (depth >= 100) return const TextSpan();
|
if (depth >= 100) return const TextSpan();
|
||||||
|
|
||||||
|
|
@ -245,9 +242,9 @@ class HtmlMessage extends StatelessWidget {
|
||||||
final isCheckbox = node.className == 'task-list-item';
|
final isCheckbox = node.className == 'task-list-item';
|
||||||
final checkboxIndex = isCheckbox
|
final checkboxIndex = isCheckbox
|
||||||
? node.rootElement
|
? node.rootElement
|
||||||
.getElementsByClassName('task-list-item')
|
.getElementsByClassName('task-list-item')
|
||||||
.indexOf(node) +
|
.indexOf(node) +
|
||||||
1
|
1
|
||||||
: null;
|
: null;
|
||||||
final checkedByReaction = !isCheckbox
|
final checkedByReaction = !isCheckbox
|
||||||
? null
|
? null
|
||||||
|
|
@ -283,7 +280,8 @@ class HtmlMessage extends StatelessWidget {
|
||||||
activeColor: textColor.withAlpha(64),
|
activeColor: textColor.withAlpha(64),
|
||||||
value:
|
value:
|
||||||
staticallyChecked || checkedByReaction != null,
|
staticallyChecked || checkedByReaction != null,
|
||||||
onChanged: eventId == null ||
|
onChanged:
|
||||||
|
eventId == null ||
|
||||||
checkboxIndex == null ||
|
checkboxIndex == null ||
|
||||||
staticallyChecked ||
|
staticallyChecked ||
|
||||||
!room.canSendDefaultMessages ||
|
!room.canSendDefaultMessages ||
|
||||||
|
|
@ -292,25 +290,21 @@ class HtmlMessage extends StatelessWidget {
|
||||||
room.client.userID)
|
room.client.userID)
|
||||||
? null
|
? null
|
||||||
: (_) => showFutureLoadingDialog(
|
: (_) => showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => checkedByReaction != null
|
future: () => checkedByReaction != null
|
||||||
? room.redactEvent(
|
? room.redactEvent(
|
||||||
checkedByReaction.eventId,
|
checkedByReaction.eventId,
|
||||||
)
|
)
|
||||||
: room.checkCheckbox(
|
: room.checkCheckbox(
|
||||||
eventId,
|
eventId,
|
||||||
checkboxIndex,
|
checkboxIndex,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
..._renderWithLineBreaks(
|
..._renderWithLineBreaks(node.nodes, context, depth: depth),
|
||||||
node.nodes,
|
|
||||||
context,
|
|
||||||
depth: depth,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
style: TextStyle(fontSize: fontSize, color: textColor),
|
style: TextStyle(fontSize: fontSize, color: textColor),
|
||||||
),
|
),
|
||||||
|
|
@ -322,12 +316,7 @@ class HtmlMessage extends StatelessWidget {
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.only(left: 8.0),
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(left: BorderSide(color: textColor, width: 5)),
|
||||||
left: BorderSide(
|
|
||||||
color: textColor,
|
|
||||||
width: 5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Text.rich(
|
child: Text.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
|
|
@ -347,7 +336,8 @@ class HtmlMessage extends StatelessWidget {
|
||||||
);
|
);
|
||||||
case 'code':
|
case 'code':
|
||||||
final isInline = node.parent?.localName != 'pre';
|
final isInline = node.parent?.localName != 'pre';
|
||||||
final lang = node.className
|
final lang =
|
||||||
|
node.className
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.singleWhereOrNull(
|
.singleWhereOrNull(
|
||||||
(className) => className.startsWith('language-'),
|
(className) => className.startsWith('language-'),
|
||||||
|
|
@ -355,8 +345,9 @@ class HtmlMessage extends StatelessWidget {
|
||||||
?.split('language-')
|
?.split('language-')
|
||||||
.last ??
|
.last ??
|
||||||
'md';
|
'md';
|
||||||
final highlightedHtml =
|
final highlightedHtml = highlight
|
||||||
highlight.parse(node.text, language: lang).toHtml();
|
.parse(node.text, language: lang)
|
||||||
|
.toHtml();
|
||||||
final element = parser.parse(highlightedHtml).body;
|
final element = parser.parse(highlightedHtml).body;
|
||||||
if (element == null) {
|
if (element == null) {
|
||||||
return const TextSpan(text: 'Unable to render code block!');
|
return const TextSpan(text: 'Unable to render code block!');
|
||||||
|
|
@ -372,14 +363,9 @@ class HtmlMessage extends StatelessWidget {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: isInline
|
padding: isInline
|
||||||
? const EdgeInsets.symmetric(horizontal: 4.0)
|
? const EdgeInsets.symmetric(horizontal: 4.0)
|
||||||
: const EdgeInsets.symmetric(
|
: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||||
horizontal: 8.0,
|
|
||||||
vertical: 4.0,
|
|
||||||
),
|
|
||||||
child: Text.rich(
|
child: Text.rich(
|
||||||
TextSpan(
|
TextSpan(children: [_renderCodeBlockNode(element)]),
|
||||||
children: [_renderCodeBlockNode(element)],
|
|
||||||
),
|
|
||||||
selectionColor: hightlightTextColor.withAlpha(128),
|
selectionColor: hightlightTextColor.withAlpha(128),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -448,10 +434,7 @@ class HtmlMessage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
style: TextStyle(
|
style: TextStyle(fontSize: fontSize, color: textColor),
|
||||||
fontSize: fontSize,
|
|
||||||
color: textColor,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -489,17 +472,13 @@ class HtmlMessage extends StatelessWidget {
|
||||||
default:
|
default:
|
||||||
return TextSpan(
|
return TextSpan(
|
||||||
style: switch (node.localName) {
|
style: switch (node.localName) {
|
||||||
'body' => TextStyle(
|
'body' => TextStyle(fontSize: fontSize, color: textColor),
|
||||||
fontSize: fontSize,
|
|
||||||
color: textColor,
|
|
||||||
),
|
|
||||||
'a' => linkStyle,
|
'a' => linkStyle,
|
||||||
'strong' => const TextStyle(fontWeight: FontWeight.bold),
|
'strong' => const TextStyle(fontWeight: FontWeight.bold),
|
||||||
'em' || 'i' => const TextStyle(fontStyle: FontStyle.italic),
|
'em' || 'i' => const TextStyle(fontStyle: FontStyle.italic),
|
||||||
'del' ||
|
'del' || 's' || 'strikethrough' => const TextStyle(
|
||||||
's' ||
|
decoration: TextDecoration.lineThrough,
|
||||||
'strikethrough' =>
|
),
|
||||||
const TextStyle(decoration: TextDecoration.lineThrough),
|
|
||||||
'u' => const TextStyle(decoration: TextDecoration.underline),
|
'u' => const TextStyle(decoration: TextDecoration.underline),
|
||||||
'h1' => TextStyle(fontSize: fontSize * 1.6, height: 2),
|
'h1' => TextStyle(fontSize: fontSize * 1.6, height: 2),
|
||||||
'h2' => TextStyle(fontSize: fontSize * 1.5, height: 2),
|
'h2' => TextStyle(fontSize: fontSize * 1.5, height: 2),
|
||||||
|
|
@ -508,22 +487,19 @@ class HtmlMessage extends StatelessWidget {
|
||||||
'h5' => TextStyle(fontSize: fontSize * 1.2, height: 1.75),
|
'h5' => TextStyle(fontSize: fontSize * 1.2, height: 1.75),
|
||||||
'h6' => TextStyle(fontSize: fontSize * 1.1, height: 1.5),
|
'h6' => TextStyle(fontSize: fontSize * 1.1, height: 1.5),
|
||||||
'span' => TextStyle(
|
'span' => TextStyle(
|
||||||
color: node.attributes['color']?.hexToColor ??
|
color:
|
||||||
node.attributes['data-mx-color']?.hexToColor ??
|
node.attributes['color']?.hexToColor ??
|
||||||
textColor,
|
node.attributes['data-mx-color']?.hexToColor ??
|
||||||
backgroundColor:
|
textColor,
|
||||||
node.attributes['data-mx-bg-color']?.hexToColor,
|
backgroundColor: node.attributes['data-mx-bg-color']?.hexToColor,
|
||||||
),
|
),
|
||||||
'sup' =>
|
'sup' => const TextStyle(
|
||||||
const TextStyle(fontFeatures: [FontFeature.superscripts()]),
|
fontFeatures: [FontFeature.superscripts()],
|
||||||
|
),
|
||||||
'sub' => const TextStyle(fontFeatures: [FontFeature.subscripts()]),
|
'sub' => const TextStyle(fontFeatures: [FontFeature.subscripts()]),
|
||||||
_ => null,
|
_ => null,
|
||||||
},
|
},
|
||||||
children: _renderWithLineBreaks(
|
children: _renderWithLineBreaks(node.nodes, context, depth: depth),
|
||||||
node.nodes,
|
|
||||||
context,
|
|
||||||
depth: depth,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -533,10 +509,7 @@ class HtmlMessage extends StatelessWidget {
|
||||||
final element = parser.parse(html).body ?? dom.Element.html('');
|
final element = parser.parse(html).body ?? dom.Element.html('');
|
||||||
return Text.rich(
|
return Text.rich(
|
||||||
_renderHtml(element, context),
|
_renderHtml(element, context),
|
||||||
style: TextStyle(
|
style: TextStyle(fontSize: fontSize, color: textColor),
|
||||||
fontSize: fontSize,
|
|
||||||
color: textColor,
|
|
||||||
),
|
|
||||||
maxLines: limitHeight ? 64 : null,
|
maxLines: limitHeight ? 64 : null,
|
||||||
overflow: TextOverflow.fade,
|
overflow: TextOverflow.fade,
|
||||||
selectionColor: textColor.withAlpha(128),
|
selectionColor: textColor.withAlpha(128),
|
||||||
|
|
@ -568,13 +541,9 @@ class MatrixPill extends StatelessWidget {
|
||||||
splashColor: Colors.transparent,
|
splashColor: Colors.transparent,
|
||||||
onTap: UrlLauncher(outerContext, uri).launchUrl,
|
onTap: UrlLauncher(outerContext, uri).launchUrl,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
Avatar(
|
Avatar(mxContent: avatar, name: name, size: 16),
|
||||||
mxContent: avatar,
|
|
||||||
name: name,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
name,
|
name,
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ class ImageBubble extends StatelessWidget {
|
||||||
Widget _buildPlaceholder(BuildContext context) {
|
Widget _buildPlaceholder(BuildContext context) {
|
||||||
final String blurHashString =
|
final String blurHashString =
|
||||||
event.infoMap['xyz.amorgan.blurhash'] is String
|
event.infoMap['xyz.amorgan.blurhash'] is String
|
||||||
? event.infoMap['xyz.amorgan.blurhash']
|
? event.infoMap['xyz.amorgan.blurhash']
|
||||||
: 'LEHV6nWB2yk8pyo0adR*.7kCMdnj';
|
: 'LEHV6nWB2yk8pyo0adR*.7kCMdnj';
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
|
|
@ -68,11 +68,8 @@ class ImageBubble extends StatelessWidget {
|
||||||
if (!tapToView) return;
|
if (!tapToView) return;
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => ImageViewer(
|
builder: (_) =>
|
||||||
event,
|
ImageViewer(event, timeline: timeline, outerContext: context),
|
||||||
timeline: timeline,
|
|
||||||
outerContext: context,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,7 +91,7 @@ class ImageBubble extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
Material(
|
Material(
|
||||||
|
|
@ -131,22 +128,21 @@ class ImageBubble extends StatelessWidget {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: width,
|
width: width,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
horizontal: 16,
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
child: Linkify(
|
child: Linkify(
|
||||||
text: fileDescription,
|
text: fileDescription,
|
||||||
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: textColor,
|
color: textColor,
|
||||||
fontSize: AppSettings.fontSizeFactor.value *
|
fontSize:
|
||||||
|
AppSettings.fontSizeFactor.value *
|
||||||
AppConfig.messageFontSize,
|
AppConfig.messageFontSize,
|
||||||
),
|
),
|
||||||
options: const LinkifyOptions(humanize: false),
|
options: const LinkifyOptions(humanize: false),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: linkColor,
|
color: linkColor,
|
||||||
fontSize: AppSettings.fontSizeFactor.value *
|
fontSize:
|
||||||
|
AppSettings.fontSizeFactor.value *
|
||||||
AppConfig.messageFontSize,
|
AppConfig.messageFontSize,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
decorationColor: linkColor,
|
decorationColor: linkColor,
|
||||||
|
|
|
||||||
|
|
@ -107,10 +107,12 @@ class Message extends StatelessWidget {
|
||||||
final alignment = ownMessage ? Alignment.topRight : Alignment.topLeft;
|
final alignment = ownMessage ? Alignment.topRight : Alignment.topLeft;
|
||||||
|
|
||||||
var color = theme.colorScheme.surfaceContainerHigh;
|
var color = theme.colorScheme.surfaceContainerHigh;
|
||||||
final displayTime = event.type == EventTypes.RoomCreate ||
|
final displayTime =
|
||||||
|
event.type == EventTypes.RoomCreate ||
|
||||||
nextEvent == null ||
|
nextEvent == null ||
|
||||||
!event.originServerTs.sameEnvironment(nextEvent!.originServerTs);
|
!event.originServerTs.sameEnvironment(nextEvent!.originServerTs);
|
||||||
final nextEventSameSender = nextEvent != null &&
|
final nextEventSameSender =
|
||||||
|
nextEvent != null &&
|
||||||
{
|
{
|
||||||
EventTypes.Message,
|
EventTypes.Message,
|
||||||
EventTypes.Sticker,
|
EventTypes.Sticker,
|
||||||
|
|
@ -119,7 +121,8 @@ class Message extends StatelessWidget {
|
||||||
nextEvent!.senderId == event.senderId &&
|
nextEvent!.senderId == event.senderId &&
|
||||||
!displayTime;
|
!displayTime;
|
||||||
|
|
||||||
final previousEventSameSender = previousEvent != null &&
|
final previousEventSameSender =
|
||||||
|
previousEvent != null &&
|
||||||
{
|
{
|
||||||
EventTypes.Message,
|
EventTypes.Message,
|
||||||
EventTypes.Sticker,
|
EventTypes.Sticker,
|
||||||
|
|
@ -128,17 +131,19 @@ class Message extends StatelessWidget {
|
||||||
previousEvent!.senderId == event.senderId &&
|
previousEvent!.senderId == event.senderId &&
|
||||||
previousEvent!.originServerTs.sameEnvironment(event.originServerTs);
|
previousEvent!.originServerTs.sameEnvironment(event.originServerTs);
|
||||||
|
|
||||||
final textColor =
|
final textColor = ownMessage
|
||||||
ownMessage ? theme.onBubbleColor : theme.colorScheme.onSurface;
|
? theme.onBubbleColor
|
||||||
|
: theme.colorScheme.onSurface;
|
||||||
|
|
||||||
final linkColor = ownMessage
|
final linkColor = ownMessage
|
||||||
? theme.brightness == Brightness.light
|
? theme.brightness == Brightness.light
|
||||||
? theme.colorScheme.primaryFixed
|
? theme.colorScheme.primaryFixed
|
||||||
: theme.colorScheme.onTertiaryContainer
|
: theme.colorScheme.onTertiaryContainer
|
||||||
: theme.colorScheme.primary;
|
: theme.colorScheme.primary;
|
||||||
|
|
||||||
final rowMainAxisAlignment =
|
final rowMainAxisAlignment = ownMessage
|
||||||
ownMessage ? MainAxisAlignment.end : MainAxisAlignment.start;
|
? MainAxisAlignment.end
|
||||||
|
: MainAxisAlignment.start;
|
||||||
|
|
||||||
final displayEvent = event.getDisplayEvent(timeline);
|
final displayEvent = event.getDisplayEvent(timeline);
|
||||||
const hardCorner = Radius.circular(4);
|
const hardCorner = Radius.circular(4);
|
||||||
|
|
@ -146,12 +151,15 @@ class Message extends StatelessWidget {
|
||||||
final borderRadius = BorderRadius.only(
|
final borderRadius = BorderRadius.only(
|
||||||
topLeft: !ownMessage && nextEventSameSender ? hardCorner : roundedCorner,
|
topLeft: !ownMessage && nextEventSameSender ? hardCorner : roundedCorner,
|
||||||
topRight: ownMessage && nextEventSameSender ? hardCorner : roundedCorner,
|
topRight: ownMessage && nextEventSameSender ? hardCorner : roundedCorner,
|
||||||
bottomLeft:
|
bottomLeft: !ownMessage && previousEventSameSender
|
||||||
!ownMessage && previousEventSameSender ? hardCorner : roundedCorner,
|
? hardCorner
|
||||||
bottomRight:
|
: roundedCorner,
|
||||||
ownMessage && previousEventSameSender ? hardCorner : roundedCorner,
|
bottomRight: ownMessage && previousEventSameSender
|
||||||
|
? hardCorner
|
||||||
|
: roundedCorner,
|
||||||
);
|
);
|
||||||
final noBubble = ({
|
final noBubble =
|
||||||
|
({
|
||||||
MessageTypes.Video,
|
MessageTypes.Video,
|
||||||
MessageTypes.Image,
|
MessageTypes.Image,
|
||||||
MessageTypes.Sticker,
|
MessageTypes.Sticker,
|
||||||
|
|
@ -165,8 +173,9 @@ class Message extends StatelessWidget {
|
||||||
event.numberEmotes <= 3);
|
event.numberEmotes <= 3);
|
||||||
|
|
||||||
if (ownMessage) {
|
if (ownMessage) {
|
||||||
color =
|
color = displayEvent.status.isError
|
||||||
displayEvent.status.isError ? Colors.redAccent : theme.bubbleColor;
|
? Colors.redAccent
|
||||||
|
: theme.bubbleColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
final resetAnimateIn = this.resetAnimateIn;
|
final resetAnimateIn = this.resetAnimateIn;
|
||||||
|
|
@ -176,10 +185,7 @@ class Message extends StatelessWidget {
|
||||||
if (singleSelected) {
|
if (singleSelected) {
|
||||||
sentReactions.addAll(
|
sentReactions.addAll(
|
||||||
event
|
event
|
||||||
.aggregatedEvents(
|
.aggregatedEvents(timeline, RelationshipTypes.reaction)
|
||||||
timeline,
|
|
||||||
RelationshipTypes.reaction,
|
|
||||||
)
|
|
||||||
.where(
|
.where(
|
||||||
(event) =>
|
(event) =>
|
||||||
event.senderId == event.room.client.userID &&
|
event.senderId == event.room.client.userID &&
|
||||||
|
|
@ -194,11 +200,15 @@ class Message extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final showReceiptsRow =
|
final showReceiptsRow = event.hasAggregatedEvents(
|
||||||
event.hasAggregatedEvents(timeline, RelationshipTypes.reaction);
|
timeline,
|
||||||
|
RelationshipTypes.reaction,
|
||||||
|
);
|
||||||
|
|
||||||
final threadChildren =
|
final threadChildren = event.aggregatedEvents(
|
||||||
event.aggregatedEvents(timeline, RelationshipTypes.thread);
|
timeline,
|
||||||
|
RelationshipTypes.thread,
|
||||||
|
);
|
||||||
|
|
||||||
final showReactionPicker =
|
final showReactionPicker =
|
||||||
singleSelected && event.room.canSendDefaultMessages;
|
singleSelected && event.room.canSendDefaultMessages;
|
||||||
|
|
@ -210,9 +220,7 @@ class Message extends StatelessWidget {
|
||||||
key: ValueKey(event.eventId),
|
key: ValueKey(event.eventId),
|
||||||
background: const Padding(
|
background: const Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
||||||
child: Center(
|
child: Center(child: Icon(Icons.check_outlined)),
|
||||||
child: Icon(Icons.check_outlined),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
direction: AppSettings.swipeRightToLeftToReply.value
|
direction: AppSettings.swipeRightToLeftToReply.value
|
||||||
? SwipeDirection.endToStart
|
? SwipeDirection.endToStart
|
||||||
|
|
@ -229,9 +237,8 @@ class Message extends StatelessWidget {
|
||||||
bottom: previousEventSameSender ? 1.0 : 4.0,
|
bottom: previousEventSameSender ? 1.0 : 4.0,
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
crossAxisAlignment:
|
crossAxisAlignment: ownMessage ? .end : .start,
|
||||||
ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
if (displayTime || selected)
|
if (displayTime || selected)
|
||||||
Padding(
|
Padding(
|
||||||
|
|
@ -242,8 +249,9 @@ class Message extends StatelessWidget {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(top: 4.0),
|
padding: const EdgeInsets.only(top: 4.0),
|
||||||
child: Material(
|
child: Material(
|
||||||
borderRadius:
|
borderRadius: BorderRadius.circular(
|
||||||
BorderRadius.circular(AppConfig.borderRadius * 2),
|
AppConfig.borderRadius * 2,
|
||||||
|
),
|
||||||
color: theme.colorScheme.surface.withAlpha(128),
|
color: theme.colorScheme.surface.withAlpha(128),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
|
|
@ -305,13 +313,13 @@ class Message extends StatelessWidget {
|
||||||
),
|
),
|
||||||
color: selected || highlightMarker
|
color: selected || highlightMarker
|
||||||
? theme.colorScheme.secondaryContainer
|
? theme.colorScheme.secondaryContainer
|
||||||
.withAlpha(128)
|
.withAlpha(128)
|
||||||
: Colors.transparent,
|
: Colors.transparent,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: .start,
|
||||||
mainAxisAlignment: rowMainAxisAlignment,
|
mainAxisAlignment: rowMainAxisAlignment,
|
||||||
children: [
|
children: [
|
||||||
if (longPressSelect && !event.redacted)
|
if (longPressSelect && !event.redacted)
|
||||||
|
|
@ -336,18 +344,17 @@ class Message extends StatelessWidget {
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 16,
|
width: 16,
|
||||||
height: 16,
|
height: 16,
|
||||||
child: event.status ==
|
child:
|
||||||
EventStatus.error
|
event.status == EventStatus.error
|
||||||
? const Icon(
|
? const Icon(
|
||||||
Icons.error,
|
Icons.error,
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
)
|
)
|
||||||
: event.fileSendingStatus != null
|
: event.fileSendingStatus != null
|
||||||
? const CircularProgressIndicator
|
? const CircularProgressIndicator.adaptive(
|
||||||
.adaptive(
|
strokeWidth: 1,
|
||||||
strokeWidth: 1,
|
)
|
||||||
)
|
: null,
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -355,17 +362,18 @@ class Message extends StatelessWidget {
|
||||||
FutureBuilder<User?>(
|
FutureBuilder<User?>(
|
||||||
future: event.fetchSenderUser(),
|
future: event.fetchSenderUser(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final user = snapshot.data ??
|
final user =
|
||||||
|
snapshot.data ??
|
||||||
event.senderFromMemoryOrFallback;
|
event.senderFromMemoryOrFallback;
|
||||||
return Avatar(
|
return Avatar(
|
||||||
mxContent: user.avatarUrl,
|
mxContent: user.avatarUrl,
|
||||||
name: user.calcDisplayname(),
|
name: user.calcDisplayname(),
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
showMemberActionsPopupMenu(
|
showMemberActionsPopupMenu(
|
||||||
context: context,
|
context: context,
|
||||||
user: user,
|
user: user,
|
||||||
onMention: onMention,
|
onMention: onMention,
|
||||||
),
|
),
|
||||||
presenceUserId: user.stateKey,
|
presenceUserId: user.stateKey,
|
||||||
presenceBackgroundColor: wallpaperMode
|
presenceBackgroundColor: wallpaperMode
|
||||||
? Colors.transparent
|
? Colors.transparent
|
||||||
|
|
@ -375,9 +383,8 @@ class Message extends StatelessWidget {
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment:
|
crossAxisAlignment: .start,
|
||||||
CrossAxisAlignment.start,
|
mainAxisSize: .min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
children: [
|
||||||
if (!nextEventSameSender)
|
if (!nextEventSameSender)
|
||||||
Padding(
|
Padding(
|
||||||
|
|
@ -385,16 +392,16 @@ class Message extends StatelessWidget {
|
||||||
left: 8.0,
|
left: 8.0,
|
||||||
bottom: 4,
|
bottom: 4,
|
||||||
),
|
),
|
||||||
child: ownMessage ||
|
child:
|
||||||
|
ownMessage ||
|
||||||
event.room.isDirectChat
|
event.room.isDirectChat
|
||||||
? const SizedBox(height: 12)
|
? const SizedBox(height: 12)
|
||||||
: FutureBuilder<User?>(
|
: FutureBuilder<User?>(
|
||||||
future:
|
future: event
|
||||||
event.fetchSenderUser(),
|
.fetchSenderUser(),
|
||||||
builder:
|
builder: (context, snapshot) {
|
||||||
(context, snapshot) {
|
final displayname =
|
||||||
final displayname = snapshot
|
snapshot.data
|
||||||
.data
|
|
||||||
?.calcDisplayname() ??
|
?.calcDisplayname() ??
|
||||||
event
|
event
|
||||||
.senderFromMemoryOrFallback
|
.senderFromMemoryOrFallback
|
||||||
|
|
@ -405,29 +412,30 @@ class Message extends StatelessWidget {
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight:
|
fontWeight:
|
||||||
FontWeight.bold,
|
FontWeight.bold,
|
||||||
color: (theme.brightness ==
|
color:
|
||||||
|
(theme.brightness ==
|
||||||
Brightness
|
Brightness
|
||||||
.light
|
.light
|
||||||
? displayname
|
? displayname
|
||||||
.color
|
.color
|
||||||
: displayname
|
: displayname
|
||||||
.lightColorText),
|
.lightColorText),
|
||||||
shadows:
|
shadows:
|
||||||
!wallpaperMode
|
!wallpaperMode
|
||||||
? null
|
? null
|
||||||
: [
|
: [
|
||||||
const Shadow(
|
const Shadow(
|
||||||
offset:
|
offset:
|
||||||
Offset(
|
Offset(
|
||||||
0.0,
|
0.0,
|
||||||
0.0,
|
0.0,
|
||||||
),
|
),
|
||||||
blurRadius:
|
blurRadius:
|
||||||
3,
|
3,
|
||||||
color: Colors
|
color: Colors
|
||||||
.black,
|
.black,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow
|
overflow: TextOverflow
|
||||||
|
|
@ -438,25 +446,25 @@ class Message extends StatelessWidget {
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
alignment: alignment,
|
alignment: alignment,
|
||||||
padding:
|
padding: const EdgeInsets.only(
|
||||||
const EdgeInsets.only(left: 8),
|
left: 8,
|
||||||
|
),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onLongPress: longPressSelect
|
onLongPress: longPressSelect
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
HapticFeedback
|
HapticFeedback.heavyImpact();
|
||||||
.heavyImpact();
|
|
||||||
onSelect(event);
|
onSelect(event);
|
||||||
},
|
},
|
||||||
child: AnimatedOpacity(
|
child: AnimatedOpacity(
|
||||||
opacity: animateIn
|
opacity: animateIn
|
||||||
? 0
|
? 0
|
||||||
: event.messageType ==
|
: event.messageType ==
|
||||||
MessageTypes
|
MessageTypes
|
||||||
.BadEncrypted ||
|
.BadEncrypted ||
|
||||||
event.status.isSending
|
event.status.isSending
|
||||||
? 0.5
|
? 0.5
|
||||||
: 1,
|
: 1,
|
||||||
duration: FluffyThemes
|
duration: FluffyThemes
|
||||||
.animationDuration,
|
.animationDuration,
|
||||||
curve:
|
curve:
|
||||||
|
|
@ -471,7 +479,8 @@ class Message extends StatelessWidget {
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: BubbleBackground(
|
child: BubbleBackground(
|
||||||
colors: colors,
|
colors: colors,
|
||||||
ignore: noBubble ||
|
ignore:
|
||||||
|
noBubble ||
|
||||||
!ownMessage ||
|
!ownMessage ||
|
||||||
MediaQuery.highContrastOf(
|
MediaQuery.highContrastOf(
|
||||||
context,
|
context,
|
||||||
|
|
@ -482,24 +491,24 @@ class Message extends StatelessWidget {
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius:
|
borderRadius:
|
||||||
BorderRadius.circular(
|
BorderRadius.circular(
|
||||||
AppConfig.borderRadius,
|
AppConfig
|
||||||
),
|
.borderRadius,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
constraints:
|
constraints:
|
||||||
const BoxConstraints(
|
const BoxConstraints(
|
||||||
maxWidth: FluffyThemes
|
maxWidth:
|
||||||
.columnWidth *
|
FluffyThemes
|
||||||
1.5,
|
.columnWidth *
|
||||||
),
|
1.5,
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize:
|
mainAxisSize: .min,
|
||||||
MainAxisSize.min,
|
|
||||||
crossAxisAlignment:
|
crossAxisAlignment:
|
||||||
CrossAxisAlignment
|
CrossAxisAlignment
|
||||||
.start,
|
.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
if (event
|
if (event.inReplyToEventId(
|
||||||
.inReplyToEventId(
|
|
||||||
includingFallback:
|
includingFallback:
|
||||||
false,
|
false,
|
||||||
) !=
|
) !=
|
||||||
|
|
@ -507,21 +516,23 @@ class Message extends StatelessWidget {
|
||||||
FutureBuilder<Event?>(
|
FutureBuilder<Event?>(
|
||||||
future: event
|
future: event
|
||||||
.getReplyEvent(
|
.getReplyEvent(
|
||||||
timeline,
|
timeline,
|
||||||
),
|
),
|
||||||
builder: (
|
builder:
|
||||||
BuildContext
|
(
|
||||||
|
BuildContext
|
||||||
context,
|
context,
|
||||||
snapshot,
|
snapshot,
|
||||||
) {
|
) {
|
||||||
final replyEvent =
|
final replyEvent =
|
||||||
snapshot
|
snapshot
|
||||||
.hasData
|
.hasData
|
||||||
? snapshot
|
? snapshot
|
||||||
.data!
|
.data!
|
||||||
: Event(
|
: Event(
|
||||||
eventId:
|
eventId:
|
||||||
event.inReplyToEventId() ?? '\$fake_event_id',
|
event.inReplyToEventId() ??
|
||||||
|
'\$fake_event_id',
|
||||||
content: {
|
content: {
|
||||||
'msgtype':
|
'msgtype':
|
||||||
'm.text',
|
'm.text',
|
||||||
|
|
@ -539,45 +550,42 @@ class Message extends StatelessWidget {
|
||||||
originServerTs:
|
originServerTs:
|
||||||
DateTime.now(),
|
DateTime.now(),
|
||||||
);
|
);
|
||||||
return Padding(
|
return Padding(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets
|
const EdgeInsets.only(
|
||||||
.only(
|
left:
|
||||||
left: 16,
|
16,
|
||||||
right: 16,
|
right:
|
||||||
top: 8,
|
16,
|
||||||
),
|
top:
|
||||||
child: Material(
|
8,
|
||||||
color: Colors
|
),
|
||||||
.transparent,
|
child: Material(
|
||||||
borderRadius:
|
color: Colors
|
||||||
ReplyContent
|
.transparent,
|
||||||
.borderRadius,
|
borderRadius:
|
||||||
child:
|
ReplyContent
|
||||||
InkWell(
|
.borderRadius,
|
||||||
borderRadius:
|
child: InkWell(
|
||||||
ReplyContent
|
borderRadius:
|
||||||
.borderRadius,
|
ReplyContent.borderRadius,
|
||||||
onTap: () =>
|
onTap: () => scrollToEventId(
|
||||||
scrollToEventId(
|
replyEvent
|
||||||
replyEvent
|
.eventId,
|
||||||
.eventId,
|
),
|
||||||
),
|
child: AbsorbPointer(
|
||||||
child:
|
child: ReplyContent(
|
||||||
AbsorbPointer(
|
replyEvent,
|
||||||
child:
|
ownMessage:
|
||||||
ReplyContent(
|
ownMessage,
|
||||||
replyEvent,
|
timeline:
|
||||||
ownMessage:
|
timeline,
|
||||||
ownMessage,
|
),
|
||||||
timeline:
|
),
|
||||||
timeline,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
MessageContent(
|
MessageContent(
|
||||||
displayEvent,
|
displayEvent,
|
||||||
|
|
@ -591,18 +599,17 @@ class Message extends StatelessWidget {
|
||||||
),
|
),
|
||||||
if (event
|
if (event
|
||||||
.hasAggregatedEvents(
|
.hasAggregatedEvents(
|
||||||
timeline,
|
timeline,
|
||||||
RelationshipTypes
|
RelationshipTypes
|
||||||
.edit,
|
.edit,
|
||||||
))
|
))
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets
|
const EdgeInsets.only(
|
||||||
.only(
|
bottom: 8.0,
|
||||||
bottom: 8.0,
|
left: 16.0,
|
||||||
left: 16.0,
|
right: 16.0,
|
||||||
right: 16.0,
|
),
|
||||||
),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize:
|
mainAxisSize:
|
||||||
MainAxisSize
|
MainAxisSize
|
||||||
|
|
@ -614,22 +621,21 @@ class Message extends StatelessWidget {
|
||||||
.edit_outlined,
|
.edit_outlined,
|
||||||
color: textColor
|
color: textColor
|
||||||
.withAlpha(
|
.withAlpha(
|
||||||
164,
|
164,
|
||||||
),
|
),
|
||||||
size: 14,
|
size: 14,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
displayEvent
|
displayEvent
|
||||||
.originServerTs
|
.originServerTs
|
||||||
.localizedTimeShort(
|
.localizedTimeShort(
|
||||||
context,
|
context,
|
||||||
),
|
),
|
||||||
style:
|
style: TextStyle(
|
||||||
TextStyle(
|
|
||||||
color: textColor
|
color: textColor
|
||||||
.withAlpha(
|
.withAlpha(
|
||||||
164,
|
164,
|
||||||
),
|
),
|
||||||
fontSize:
|
fontSize:
|
||||||
11,
|
11,
|
||||||
),
|
),
|
||||||
|
|
@ -657,46 +663,43 @@ class Message extends StatelessWidget {
|
||||||
? Padding(
|
? Padding(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.all(
|
const EdgeInsets.all(
|
||||||
4.0,
|
4.0,
|
||||||
),
|
),
|
||||||
child: Material(
|
child: Material(
|
||||||
elevation: 4,
|
elevation: 4,
|
||||||
borderRadius:
|
borderRadius:
|
||||||
BorderRadius.circular(
|
BorderRadius.circular(
|
||||||
AppConfig.borderRadius,
|
AppConfig
|
||||||
),
|
.borderRadius,
|
||||||
|
),
|
||||||
shadowColor: theme
|
shadowColor: theme
|
||||||
.colorScheme.surface
|
.colorScheme
|
||||||
|
.surface
|
||||||
.withAlpha(128),
|
.withAlpha(128),
|
||||||
child:
|
child: SingleChildScrollView(
|
||||||
SingleChildScrollView(
|
|
||||||
scrollDirection:
|
scrollDirection:
|
||||||
Axis.horizontal,
|
Axis.horizontal,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize:
|
mainAxisSize: .min,
|
||||||
MainAxisSize.min,
|
|
||||||
children: [
|
children: [
|
||||||
...AppConfig
|
...AppConfig.defaultReactions.map(
|
||||||
.defaultReactions
|
(
|
||||||
.map(
|
emoji,
|
||||||
(emoji) =>
|
) => IconButton(
|
||||||
IconButton(
|
|
||||||
padding:
|
padding:
|
||||||
EdgeInsets
|
EdgeInsets
|
||||||
.zero,
|
.zero,
|
||||||
icon: Center(
|
icon: Center(
|
||||||
child:
|
child: Opacity(
|
||||||
Opacity(
|
opacity:
|
||||||
opacity: sentReactions
|
sentReactions.contains(
|
||||||
.contains(
|
emoji,
|
||||||
emoji,
|
)
|
||||||
)
|
|
||||||
? 0.33
|
? 0.33
|
||||||
: 1,
|
: 1,
|
||||||
child: Text(
|
child: Text(
|
||||||
emoji,
|
emoji,
|
||||||
style:
|
style: const TextStyle(
|
||||||
const TextStyle(
|
|
||||||
fontSize:
|
fontSize:
|
||||||
20,
|
20,
|
||||||
),
|
),
|
||||||
|
|
@ -708,19 +711,20 @@ class Message extends StatelessWidget {
|
||||||
),
|
),
|
||||||
onPressed:
|
onPressed:
|
||||||
sentReactions
|
sentReactions
|
||||||
.contains(
|
.contains(
|
||||||
emoji,
|
emoji,
|
||||||
)
|
)
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
onSelect(
|
onSelect(
|
||||||
event,
|
event,
|
||||||
);
|
);
|
||||||
event.room.sendReaction(
|
event.room.sendReaction(
|
||||||
event.eventId,
|
event
|
||||||
emoji,
|
.eventId,
|
||||||
);
|
emoji,
|
||||||
},
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|
@ -731,72 +735,55 @@ class Message extends StatelessWidget {
|
||||||
tooltip: L10n.of(
|
tooltip: L10n.of(
|
||||||
context,
|
context,
|
||||||
).customReaction,
|
).customReaction,
|
||||||
onPressed:
|
onPressed: () async {
|
||||||
() async {
|
final emoji = await showAdaptiveBottomSheet<String>(
|
||||||
final emoji =
|
|
||||||
await showAdaptiveBottomSheet<
|
|
||||||
String>(
|
|
||||||
context:
|
context:
|
||||||
context,
|
context,
|
||||||
builder:
|
builder: (context) => Scaffold(
|
||||||
(context) =>
|
appBar: AppBar(
|
||||||
Scaffold(
|
title: Text(
|
||||||
appBar:
|
L10n.of(
|
||||||
AppBar(
|
|
||||||
title:
|
|
||||||
Text(
|
|
||||||
L10n.of(context)
|
|
||||||
.customReaction,
|
|
||||||
),
|
|
||||||
leading:
|
|
||||||
CloseButton(
|
|
||||||
onPressed:
|
|
||||||
() =>
|
|
||||||
Navigator.of(
|
|
||||||
context,
|
context,
|
||||||
).pop(
|
).customReaction,
|
||||||
null,
|
),
|
||||||
),
|
leading: CloseButton(
|
||||||
|
onPressed: () => Navigator.of(
|
||||||
|
context,
|
||||||
|
).pop(null),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body:
|
body: SizedBox(
|
||||||
SizedBox(
|
|
||||||
height: double
|
height: double
|
||||||
.infinity,
|
.infinity,
|
||||||
child:
|
child: EmojiPicker(
|
||||||
EmojiPicker(
|
onEmojiSelected:
|
||||||
onEmojiSelected: (
|
(
|
||||||
_,
|
_,
|
||||||
emoji,
|
emoji,
|
||||||
) =>
|
) =>
|
||||||
Navigator.of(
|
Navigator.of(
|
||||||
context,
|
context,
|
||||||
).pop(
|
).pop(
|
||||||
emoji
|
emoji.emoji,
|
||||||
.emoji,
|
),
|
||||||
),
|
config: Config(
|
||||||
config:
|
locale: Localizations.localeOf(
|
||||||
Config(
|
context,
|
||||||
locale:
|
),
|
||||||
Localizations.localeOf(context),
|
emojiViewConfig: const EmojiViewConfig(
|
||||||
emojiViewConfig:
|
|
||||||
const EmojiViewConfig(
|
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
Colors.transparent,
|
Colors.transparent,
|
||||||
),
|
),
|
||||||
bottomActionBarConfig:
|
bottomActionBarConfig: const BottomActionBarConfig(
|
||||||
const BottomActionBarConfig(
|
|
||||||
enabled:
|
enabled:
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
categoryViewConfig:
|
categoryViewConfig: CategoryViewConfig(
|
||||||
CategoryViewConfig(
|
|
||||||
initCategory:
|
initCategory:
|
||||||
Category.SMILEYS,
|
Category.SMILEYS,
|
||||||
backspaceColor:
|
backspaceColor:
|
||||||
theme.colorScheme.primary,
|
theme.colorScheme.primary,
|
||||||
iconColor:
|
iconColor: theme.colorScheme.primary.withAlpha(
|
||||||
theme.colorScheme.primary.withAlpha(
|
|
||||||
128,
|
128,
|
||||||
),
|
),
|
||||||
iconColorSelected:
|
iconColorSelected:
|
||||||
|
|
@ -806,10 +793,8 @@ class Message extends StatelessWidget {
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.colorScheme.surface,
|
theme.colorScheme.surface,
|
||||||
),
|
),
|
||||||
skinToneConfig:
|
skinToneConfig: SkinToneConfig(
|
||||||
SkinToneConfig(
|
dialogBackgroundColor: Color.lerp(
|
||||||
dialogBackgroundColor:
|
|
||||||
Color.lerp(
|
|
||||||
theme.colorScheme.surface,
|
theme.colorScheme.surface,
|
||||||
theme.colorScheme.primaryContainer,
|
theme.colorScheme.primaryContainer,
|
||||||
0.75,
|
0.75,
|
||||||
|
|
@ -828,17 +813,18 @@ class Message extends StatelessWidget {
|
||||||
}
|
}
|
||||||
if (sentReactions
|
if (sentReactions
|
||||||
.contains(
|
.contains(
|
||||||
emoji,
|
emoji,
|
||||||
)) {
|
)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onSelect(event);
|
onSelect(event);
|
||||||
|
|
||||||
await event.room
|
await event.room
|
||||||
.sendReaction(
|
.sendReaction(
|
||||||
event.eventId,
|
event
|
||||||
emoji,
|
.eventId,
|
||||||
);
|
emoji,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -904,10 +890,7 @@ class Message extends StatelessWidget {
|
||||||
onPressed: () => enterThread(event.eventId),
|
onPressed: () => enterThread(event.eventId),
|
||||||
icon: const Icon(Icons.message),
|
icon: const Icon(Icons.message),
|
||||||
label: Text(
|
label: Text(
|
||||||
'${L10n.of(context).countReplies(threadChildren.length)} | ${threadChildren.first.calcLocalizedBodyFallback(
|
'${L10n.of(context).countReplies(threadChildren.length)} | ${threadChildren.first.calcLocalizedBodyFallback(MatrixLocals(L10n.of(context)), withSenderNamePrefix: true)}',
|
||||||
MatrixLocals(L10n.of(context)),
|
|
||||||
withSenderNamePrefix: true,
|
|
||||||
)}',
|
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
|
@ -933,8 +916,9 @@ class Message extends StatelessWidget {
|
||||||
vertical: 2,
|
vertical: 2,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius:
|
borderRadius: BorderRadius.circular(
|
||||||
BorderRadius.circular(AppConfig.borderRadius / 3),
|
AppConfig.borderRadius / 3,
|
||||||
|
),
|
||||||
color: theme.colorScheme.surface.withAlpha(128),
|
color: theme.colorScheme.surface.withAlpha(128),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
@ -1005,8 +989,10 @@ class BubblePainter extends CustomPainter {
|
||||||
final scrollableRect = Offset.zero & scrollableBox.size;
|
final scrollableRect = Offset.zero & scrollableBox.size;
|
||||||
final bubbleBox = context.findRenderObject() as RenderBox;
|
final bubbleBox = context.findRenderObject() as RenderBox;
|
||||||
|
|
||||||
final origin =
|
final origin = bubbleBox.localToGlobal(
|
||||||
bubbleBox.localToGlobal(Offset.zero, ancestor: scrollableBox);
|
Offset.zero,
|
||||||
|
ancestor: scrollableBox,
|
||||||
|
);
|
||||||
final paint = Paint()
|
final paint = Paint()
|
||||||
..shader = ui.Gradient.linear(
|
..shader = ui.Gradient.linear(
|
||||||
scrollableRect.topCenter,
|
scrollableRect.topCenter,
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,7 @@ class MessageContent extends StatelessWidget {
|
||||||
if (event.content['can_request_session'] != true) {
|
if (event.content['can_request_session'] != true) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(event.calcLocalizedBodyFallback(MatrixLocals(l10n))),
|
||||||
event.calcLocalizedBodyFallback(MatrixLocals(l10n)),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|
@ -91,11 +89,7 @@ class MessageContent extends StatelessWidget {
|
||||||
trailing: const Icon(Icons.lock_outlined),
|
trailing: const Icon(Icons.lock_outlined),
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
Text(
|
Text(event.calcLocalizedBodyFallback(MatrixLocals(l10n))),
|
||||||
event.calcLocalizedBodyFallback(
|
|
||||||
MatrixLocals(l10n),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -116,8 +110,9 @@ class MessageContent extends StatelessWidget {
|
||||||
case MessageTypes.Image:
|
case MessageTypes.Image:
|
||||||
case MessageTypes.Sticker:
|
case MessageTypes.Sticker:
|
||||||
if (event.redacted) continue textmessage;
|
if (event.redacted) continue textmessage;
|
||||||
final maxSize =
|
final maxSize = event.messageType == MessageTypes.Sticker
|
||||||
event.messageType == MessageTypes.Sticker ? 128.0 : 256.0;
|
? 128.0
|
||||||
|
: 256.0;
|
||||||
final w = event.content
|
final w = event.content
|
||||||
.tryGetMap<String, Object?>('info')
|
.tryGetMap<String, Object?>('info')
|
||||||
?.tryGet<int>('w');
|
?.tryGet<int>('w');
|
||||||
|
|
@ -152,12 +147,12 @@ class MessageContent extends StatelessWidget {
|
||||||
return CuteContent(event);
|
return CuteContent(event);
|
||||||
case MessageTypes.Audio:
|
case MessageTypes.Audio:
|
||||||
if (PlatformInfos.isMobile ||
|
if (PlatformInfos.isMobile ||
|
||||||
PlatformInfos.isMacOS ||
|
PlatformInfos.isMacOS ||
|
||||||
PlatformInfos.isWeb
|
PlatformInfos.isWeb
|
||||||
// Disabled until https://github.com/bleonard252/just_audio_mpv/issues/3
|
// Disabled until https://github.com/bleonard252/just_audio_mpv/issues/3
|
||||||
// is fixed
|
// is fixed
|
||||||
// || PlatformInfos.isLinux
|
// || PlatformInfos.isLinux
|
||||||
) {
|
) {
|
||||||
return AudioPlayerWidget(
|
return AudioPlayerWidget(
|
||||||
event,
|
event,
|
||||||
color: textColor,
|
color: textColor,
|
||||||
|
|
@ -193,8 +188,9 @@ class MessageContent extends StatelessWidget {
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
);
|
);
|
||||||
case MessageTypes.Location:
|
case MessageTypes.Location:
|
||||||
final geoUri =
|
final geoUri = Uri.tryParse(
|
||||||
Uri.tryParse(event.content.tryGet<String>('geo_uri')!);
|
event.content.tryGet<String>('geo_uri')!,
|
||||||
|
);
|
||||||
if (geoUri != null && geoUri.scheme == 'geo') {
|
if (geoUri != null && geoUri.scheme == 'geo') {
|
||||||
final latlong = geoUri.path
|
final latlong = geoUri.path
|
||||||
.split(';')
|
.split(';')
|
||||||
|
|
@ -206,7 +202,7 @@ class MessageContent extends StatelessWidget {
|
||||||
latlong.first != null &&
|
latlong.first != null &&
|
||||||
latlong.last != null) {
|
latlong.last != null) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
MapBubble(
|
MapBubble(
|
||||||
latitude: latlong.first!,
|
latitude: latlong.first!,
|
||||||
|
|
@ -215,8 +211,10 @@ class MessageContent extends StatelessWidget {
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
icon: Icon(Icons.location_on_outlined, color: textColor),
|
icon: Icon(Icons.location_on_outlined, color: textColor),
|
||||||
onPressed:
|
onPressed: UrlLauncher(
|
||||||
UrlLauncher(context, geoUri.toString()).launchUrl,
|
context,
|
||||||
|
geoUri.toString(),
|
||||||
|
).launchUrl,
|
||||||
label: Text(
|
label: Text(
|
||||||
L10n.of(context).openInMaps,
|
L10n.of(context).openInMaps,
|
||||||
style: TextStyle(color: textColor),
|
style: TextStyle(color: textColor),
|
||||||
|
|
@ -248,25 +246,25 @@ class MessageContent extends StatelessWidget {
|
||||||
html = '* $html';
|
html = '* $html';
|
||||||
}
|
}
|
||||||
|
|
||||||
final bigEmotes = event.onlyEmotes &&
|
final bigEmotes =
|
||||||
|
event.onlyEmotes &&
|
||||||
event.numberEmotes > 0 &&
|
event.numberEmotes > 0 &&
|
||||||
event.numberEmotes <= 3;
|
event.numberEmotes <= 3;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
horizontal: 16,
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
child: HtmlMessage(
|
child: HtmlMessage(
|
||||||
html: html,
|
html: html,
|
||||||
textColor: textColor,
|
textColor: textColor,
|
||||||
room: event.room,
|
room: event.room,
|
||||||
fontSize: AppSettings.fontSizeFactor.value *
|
fontSize:
|
||||||
|
AppSettings.fontSizeFactor.value *
|
||||||
AppConfig.messageFontSize *
|
AppConfig.messageFontSize *
|
||||||
(bigEmotes ? 5 : 1),
|
(bigEmotes ? 5 : 1),
|
||||||
limitHeight: !selected,
|
limitHeight: !selected,
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: linkColor,
|
color: linkColor,
|
||||||
fontSize: AppSettings.fontSizeFactor.value *
|
fontSize:
|
||||||
|
AppSettings.fontSizeFactor.value *
|
||||||
AppConfig.messageFontSize,
|
AppConfig.messageFontSize,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
decorationColor: linkColor,
|
decorationColor: linkColor,
|
||||||
|
|
@ -352,16 +350,14 @@ class RedactionWidget extends StatelessWidget {
|
||||||
future: event.redactedBecause?.fetchSenderUser(),
|
future: event.redactedBecause?.fetchSenderUser(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final reason = event.redactedBecause?.content.tryGet<String>('reason');
|
final reason = event.redactedBecause?.content.tryGet<String>('reason');
|
||||||
final redactedBy = snapshot.data?.calcDisplayname() ??
|
final redactedBy =
|
||||||
|
snapshot.data?.calcDisplayname() ??
|
||||||
event.redactedBecause?.senderId.localpart ??
|
event.redactedBecause?.senderId.localpart ??
|
||||||
L10n.of(context).user;
|
L10n.of(context).user;
|
||||||
return _ButtonContent(
|
return _ButtonContent(
|
||||||
label: reason == null
|
label: reason == null
|
||||||
? L10n.of(context).redactedBy(redactedBy)
|
? L10n.of(context).redactedBy(redactedBy)
|
||||||
: L10n.of(context).redactedByBecause(
|
: L10n.of(context).redactedByBecause(redactedBy, reason),
|
||||||
redactedBy,
|
|
||||||
reason,
|
|
||||||
),
|
|
||||||
icon: '🗑️',
|
icon: '🗑️',
|
||||||
textColor: buttonTextColor.withAlpha(128),
|
textColor: buttonTextColor.withAlpha(128),
|
||||||
onPressed: () => onInfoTab!(event),
|
onPressed: () => onInfoTab!(event),
|
||||||
|
|
@ -390,18 +386,12 @@ class _ButtonContent extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
horizontal: 16,
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: onPressed,
|
onTap: onPressed,
|
||||||
child: Text(
|
child: Text(
|
||||||
'$icon $label',
|
'$icon $label',
|
||||||
style: TextStyle(
|
style: TextStyle(color: textColor, fontSize: fontSize),
|
||||||
color: textColor,
|
|
||||||
fontSize: fontSize,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -27,15 +27,15 @@ class MessageDownloadContent extends StatelessWidget {
|
||||||
final filetype = (filename.contains('.')
|
final filetype = (filename.contains('.')
|
||||||
? filename.split('.').last.toUpperCase()
|
? filename.split('.').last.toUpperCase()
|
||||||
: event.content
|
: event.content
|
||||||
.tryGetMap<String, dynamic>('info')
|
.tryGetMap<String, dynamic>('info')
|
||||||
?.tryGet<String>('mimetype')
|
?.tryGet<String>('mimetype')
|
||||||
?.toUpperCase() ??
|
?.toUpperCase() ??
|
||||||
'UNKNOWN');
|
'UNKNOWN');
|
||||||
final sizeString = event.sizeString ?? '?MB';
|
final sizeString = event.sizeString ?? '?MB';
|
||||||
final fileDescription = event.fileDescription;
|
final fileDescription = event.fileDescription;
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: .start,
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
Material(
|
Material(
|
||||||
|
|
@ -47,7 +47,7 @@ class MessageDownloadContent extends StatelessWidget {
|
||||||
width: 400,
|
width: 400,
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
spacing: 16,
|
spacing: 16,
|
||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
|
|
@ -56,8 +56,8 @@ class MessageDownloadContent extends StatelessWidget {
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: .start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
filename,
|
filename,
|
||||||
|
|
@ -93,13 +93,15 @@ class MessageDownloadContent extends StatelessWidget {
|
||||||
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: textColor,
|
color: textColor,
|
||||||
fontSize: AppSettings.fontSizeFactor.value *
|
fontSize:
|
||||||
|
AppSettings.fontSizeFactor.value *
|
||||||
AppConfig.messageFontSize,
|
AppConfig.messageFontSize,
|
||||||
),
|
),
|
||||||
options: const LinkifyOptions(humanize: false),
|
options: const LinkifyOptions(humanize: false),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: linkColor,
|
color: linkColor,
|
||||||
fontSize: AppSettings.fontSizeFactor.value *
|
fontSize:
|
||||||
|
AppSettings.fontSizeFactor.value *
|
||||||
AppConfig.messageFontSize,
|
AppConfig.messageFontSize,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
decorationColor: linkColor,
|
decorationColor: linkColor,
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,10 @@ class MessageReactions extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final allReactionEvents =
|
final allReactionEvents = event.aggregatedEvents(
|
||||||
event.aggregatedEvents(timeline, RelationshipTypes.reaction);
|
timeline,
|
||||||
|
RelationshipTypes.reaction,
|
||||||
|
);
|
||||||
final reactionMap = <String, _ReactionEntry>{};
|
final reactionMap = <String, _ReactionEntry>{};
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
|
|
||||||
|
|
@ -113,7 +115,7 @@ class _Reaction extends StatelessWidget {
|
||||||
Widget content;
|
Widget content;
|
||||||
if (reactionKey.startsWith('mxc://')) {
|
if (reactionKey.startsWith('mxc://')) {
|
||||||
content = Row(
|
content = Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
MxcImage(
|
MxcImage(
|
||||||
uri: Uri.parse(reactionKey),
|
uri: Uri.parse(reactionKey),
|
||||||
|
|
@ -190,17 +192,14 @@ class _AdaptableReactorsDialog extends StatelessWidget {
|
||||||
final Client? client;
|
final Client? client;
|
||||||
final _ReactionEntry? reactionEntry;
|
final _ReactionEntry? reactionEntry;
|
||||||
|
|
||||||
const _AdaptableReactorsDialog({
|
const _AdaptableReactorsDialog({this.client, this.reactionEntry});
|
||||||
this.client,
|
|
||||||
this.reactionEntry,
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<bool?> show(BuildContext context) => showAdaptiveDialog(
|
Future<bool?> show(BuildContext context) => showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => this,
|
builder: (context) => this,
|
||||||
barrierDismissible: true,
|
barrierDismissible: true,
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
@ -226,9 +225,6 @@ class _AdaptableReactorsDialog extends StatelessWidget {
|
||||||
|
|
||||||
final title = Center(child: Text(reactionEntry!.key));
|
final title = Center(child: Text(reactionEntry!.key));
|
||||||
|
|
||||||
return AlertDialog.adaptive(
|
return AlertDialog.adaptive(title: title, content: body);
|
||||||
title: title,
|
|
||||||
content: body,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,8 @@ class PollWidget extends StatelessWidget {
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
void _endPoll(BuildContext context) => showFutureLoadingDialog(
|
void _endPoll(BuildContext context) =>
|
||||||
context: context,
|
showFutureLoadingDialog(context: context, future: () => event.endPoll());
|
||||||
future: () => event.endPoll(),
|
|
||||||
);
|
|
||||||
|
|
||||||
void _toggleVote(
|
void _toggleVote(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
|
|
@ -60,20 +58,19 @@ class PollWidget extends StatelessWidget {
|
||||||
}
|
}
|
||||||
final responses = event.getPollResponses(timeline);
|
final responses = event.getPollResponses(timeline);
|
||||||
final pollHasBeenEnded = event.getPollHasBeenEnded(timeline);
|
final pollHasBeenEnded = event.getPollHasBeenEnded(timeline);
|
||||||
final canVote = event.room.canSendEvent(PollEventContent.responseType) &&
|
final canVote =
|
||||||
|
event.room.canSendEvent(PollEventContent.responseType) &&
|
||||||
!pollHasBeenEnded;
|
!pollHasBeenEnded;
|
||||||
final maxPolls = responses.length;
|
final maxPolls = responses.length;
|
||||||
final answersVisible =
|
final answersVisible =
|
||||||
eventContent.pollStartContent.kind == PollKind.disclosed ||
|
eventContent.pollStartContent.kind == PollKind.disclosed ||
|
||||||
pollHasBeenEnded;
|
pollHasBeenEnded;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: .start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
|
@ -82,13 +79,15 @@ class PollWidget extends StatelessWidget {
|
||||||
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: textColor,
|
color: textColor,
|
||||||
fontSize: AppSettings.fontSizeFactor.value *
|
fontSize:
|
||||||
|
AppSettings.fontSizeFactor.value *
|
||||||
AppConfig.messageFontSize,
|
AppConfig.messageFontSize,
|
||||||
),
|
),
|
||||||
options: const LinkifyOptions(humanize: false),
|
options: const LinkifyOptions(humanize: false),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: linkColor,
|
color: linkColor,
|
||||||
fontSize: AppSettings.fontSizeFactor.value *
|
fontSize:
|
||||||
|
AppSettings.fontSizeFactor.value *
|
||||||
AppConfig.messageFontSize,
|
AppConfig.messageFontSize,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
decorationColor: linkColor,
|
decorationColor: linkColor,
|
||||||
|
|
@ -97,99 +96,101 @@ class PollWidget extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Divider(color: linkColor.withAlpha(64)),
|
Divider(color: linkColor.withAlpha(64)),
|
||||||
...eventContent.pollStartContent.answers.map(
|
...eventContent.pollStartContent.answers.map((answer) {
|
||||||
(answer) {
|
final votedUserIds = responses.entries
|
||||||
final votedUserIds = responses.entries
|
.where((entry) => entry.value.contains(answer.id))
|
||||||
.where((entry) => entry.value.contains(answer.id))
|
.map((entry) => entry.key)
|
||||||
.map((entry) => entry.key)
|
.toSet();
|
||||||
.toSet();
|
return Material(
|
||||||
return Material(
|
color: Colors.transparent,
|
||||||
color: Colors.transparent,
|
clipBehavior: Clip.hardEdge,
|
||||||
clipBehavior: Clip.hardEdge,
|
child: CheckboxListTile.adaptive(
|
||||||
child: CheckboxListTile.adaptive(
|
value:
|
||||||
value: responses[event.room.client.userID!]
|
responses[event.room.client.userID!]?.contains(answer.id) ??
|
||||||
?.contains(answer.id) ??
|
false,
|
||||||
false,
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
checkboxScaleFactor: 1.5,
|
||||||
checkboxScaleFactor: 1.5,
|
checkboxShape: RoundedRectangleBorder(
|
||||||
checkboxShape: RoundedRectangleBorder(
|
borderRadius: BorderRadius.circular(32),
|
||||||
borderRadius: BorderRadius.circular(32),
|
),
|
||||||
|
onChanged: !canVote
|
||||||
|
? null
|
||||||
|
: (_) => _toggleVote(
|
||||||
|
context,
|
||||||
|
answer.id,
|
||||||
|
eventContent.pollStartContent.maxSelections,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
answer.mText,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: textColor,
|
||||||
|
fontSize:
|
||||||
|
AppConfig.messageFontSize *
|
||||||
|
AppSettings.fontSizeFactor.value,
|
||||||
),
|
),
|
||||||
onChanged: !canVote
|
),
|
||||||
? null
|
subtitle: answersVisible
|
||||||
: (_) => _toggleVote(
|
? Column(
|
||||||
context,
|
crossAxisAlignment: .start,
|
||||||
answer.id,
|
mainAxisSize: .min,
|
||||||
eventContent.pollStartContent.maxSelections,
|
children: [
|
||||||
),
|
SingleChildScrollView(
|
||||||
title: Text(
|
scrollDirection: Axis.horizontal,
|
||||||
answer.mText,
|
child: Row(
|
||||||
maxLines: 1,
|
children: [
|
||||||
overflow: TextOverflow.ellipsis,
|
Text(
|
||||||
style: TextStyle(
|
L10n.of(
|
||||||
color: textColor,
|
context,
|
||||||
fontSize: AppConfig.messageFontSize *
|
).countVotes(votedUserIds.length),
|
||||||
AppSettings.fontSizeFactor.value,
|
maxLines: 1,
|
||||||
),
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
style: TextStyle(
|
||||||
subtitle: answersVisible
|
color: linkColor,
|
||||||
? Column(
|
fontSize:
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
12 * AppSettings.fontSizeFactor.value,
|
||||||
mainAxisSize: MainAxisSize.min,
|
),
|
||||||
children: [
|
),
|
||||||
SingleChildScrollView(
|
const SizedBox(width: 2),
|
||||||
scrollDirection: Axis.horizontal,
|
...votedUserIds.map((userId) {
|
||||||
child: Row(
|
final user = event.room
|
||||||
children: [
|
.getState(EventTypes.RoomMember, userId)
|
||||||
Text(
|
?.asUser(event.room);
|
||||||
L10n.of(context)
|
return Padding(
|
||||||
.countVotes(votedUserIds.length),
|
padding: const EdgeInsets.symmetric(
|
||||||
maxLines: 1,
|
horizontal: 2.0,
|
||||||
overflow: TextOverflow.ellipsis,
|
),
|
||||||
style: TextStyle(
|
child: Avatar(
|
||||||
color: linkColor,
|
mxContent: user?.avatarUrl,
|
||||||
fontSize:
|
name:
|
||||||
|
user?.calcDisplayname() ??
|
||||||
|
userId.localpart,
|
||||||
|
size:
|
||||||
12 * AppSettings.fontSizeFactor.value,
|
12 * AppSettings.fontSizeFactor.value,
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
const SizedBox(width: 2),
|
}),
|
||||||
...votedUserIds.map((userId) {
|
const SizedBox(width: 2),
|
||||||
final user = event.room
|
],
|
||||||
.getState(EventTypes.RoomMember, userId)
|
|
||||||
?.asUser(event.room);
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 2.0,
|
|
||||||
),
|
|
||||||
child: Avatar(
|
|
||||||
mxContent: user?.avatarUrl,
|
|
||||||
name: user?.calcDisplayname() ??
|
|
||||||
userId.localpart,
|
|
||||||
size: 12 *
|
|
||||||
AppSettings.fontSizeFactor.value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
const SizedBox(width: 2),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
LinearProgressIndicator(
|
),
|
||||||
color: linkColor,
|
LinearProgressIndicator(
|
||||||
backgroundColor: linkColor.withAlpha(128),
|
color: linkColor,
|
||||||
borderRadius:
|
backgroundColor: linkColor.withAlpha(128),
|
||||||
BorderRadius.circular(AppConfig.borderRadius),
|
borderRadius: BorderRadius.circular(
|
||||||
value: maxPolls == 0
|
AppConfig.borderRadius,
|
||||||
? 0
|
|
||||||
: votedUserIds.length / maxPolls,
|
|
||||||
),
|
),
|
||||||
],
|
value: maxPolls == 0
|
||||||
)
|
? 0
|
||||||
: null,
|
: votedUserIds.length / maxPolls,
|
||||||
),
|
),
|
||||||
);
|
],
|
||||||
},
|
)
|
||||||
),
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
if (!pollHasBeenEnded && event.senderId == event.room.client.userID)
|
if (!pollHasBeenEnded && event.senderId == event.room.client.userID)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
|
|
||||||
|
|
@ -29,21 +29,22 @@ class ReplyContent extends StatelessWidget {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
final timeline = this.timeline;
|
final timeline = this.timeline;
|
||||||
final displayEvent =
|
final displayEvent = timeline != null
|
||||||
timeline != null ? replyEvent.getDisplayEvent(timeline) : replyEvent;
|
? replyEvent.getDisplayEvent(timeline)
|
||||||
|
: replyEvent;
|
||||||
final fontSize =
|
final fontSize =
|
||||||
AppConfig.messageFontSize * AppSettings.fontSizeFactor.value;
|
AppConfig.messageFontSize * AppSettings.fontSizeFactor.value;
|
||||||
final color = theme.brightness == Brightness.dark
|
final color = theme.brightness == Brightness.dark
|
||||||
? theme.colorScheme.onTertiaryContainer
|
? theme.colorScheme.onTertiaryContainer
|
||||||
: ownMessage
|
: ownMessage
|
||||||
? theme.colorScheme.tertiaryContainer
|
? theme.colorScheme.tertiaryContainer
|
||||||
: theme.colorScheme.tertiary;
|
: theme.colorScheme.tertiary;
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
width: 5,
|
width: 5,
|
||||||
|
|
@ -56,8 +57,8 @@ class ReplyContent extends StatelessWidget {
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: .start,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
FutureBuilder<User?>(
|
FutureBuilder<User?>(
|
||||||
initialData: displayEvent.senderFromMemoryOrFallback,
|
initialData: displayEvent.senderFromMemoryOrFallback,
|
||||||
|
|
@ -87,8 +88,8 @@ class ReplyContent extends StatelessWidget {
|
||||||
color: theme.brightness == Brightness.dark
|
color: theme.brightness == Brightness.dark
|
||||||
? theme.colorScheme.onSurface
|
? theme.colorScheme.onSurface
|
||||||
: ownMessage
|
: ownMessage
|
||||||
? theme.colorScheme.onTertiary
|
? theme.colorScheme.onTertiary
|
||||||
: theme.colorScheme.onSurface,
|
: theme.colorScheme.onSurface,
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class RoomCreationStateEvent extends StatelessWidget {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
Avatar(
|
Avatar(
|
||||||
mxContent: event.room.avatar,
|
mxContent: event.room.avatar,
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,9 @@ class StateMessage extends StatelessWidget {
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
child: Material(
|
child: Material(
|
||||||
color: theme.colorScheme.surface.withAlpha(128),
|
color: theme.colorScheme.surface.withAlpha(128),
|
||||||
borderRadius:
|
borderRadius: BorderRadius.circular(
|
||||||
BorderRadius.circular(AppConfig.borderRadius / 3),
|
AppConfig.borderRadius / 3,
|
||||||
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 8.0,
|
horizontal: 8.0,
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,10 @@ class EventVideoPlayer extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final supportsVideoPlayer = PlatformInfos.supportsVideoPlayer;
|
final supportsVideoPlayer = PlatformInfos.supportsVideoPlayer;
|
||||||
|
|
||||||
final blurHash = (event.infoMap as Map<String, dynamic>)
|
final blurHash =
|
||||||
.tryGet<String>('xyz.amorgan.blurhash') ??
|
(event.infoMap as Map<String, dynamic>).tryGet<String>(
|
||||||
|
'xyz.amorgan.blurhash',
|
||||||
|
) ??
|
||||||
fallbackBlurHash;
|
fallbackBlurHash;
|
||||||
final fileDescription = event.fileDescription;
|
final fileDescription = event.fileDescription;
|
||||||
const maxDimension = 300.0;
|
const maxDimension = 300.0;
|
||||||
|
|
@ -49,11 +51,12 @@ class EventVideoPlayer extends StatelessWidget {
|
||||||
final height = videoHeight / modifier;
|
final height = videoHeight / modifier;
|
||||||
|
|
||||||
final durationInt = infoMap?.tryGet<int>('duration');
|
final durationInt = infoMap?.tryGet<int>('duration');
|
||||||
final duration =
|
final duration = durationInt == null
|
||||||
durationInt == null ? null : Duration(milliseconds: durationInt);
|
? null
|
||||||
|
: Duration(milliseconds: durationInt);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
Material(
|
Material(
|
||||||
|
|
@ -128,22 +131,21 @@ class EventVideoPlayer extends StatelessWidget {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: width,
|
width: width,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
horizontal: 16,
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
child: Linkify(
|
child: Linkify(
|
||||||
text: fileDescription,
|
text: fileDescription,
|
||||||
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: textColor,
|
color: textColor,
|
||||||
fontSize: AppSettings.fontSizeFactor.value *
|
fontSize:
|
||||||
|
AppSettings.fontSizeFactor.value *
|
||||||
AppConfig.messageFontSize,
|
AppConfig.messageFontSize,
|
||||||
),
|
),
|
||||||
options: const LinkifyOptions(humanize: false),
|
options: const LinkifyOptions(humanize: false),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: linkColor,
|
color: linkColor,
|
||||||
fontSize: AppSettings.fontSizeFactor.value *
|
fontSize:
|
||||||
|
AppSettings.fontSizeFactor.value *
|
||||||
AppConfig.messageFontSize,
|
AppConfig.messageFontSize,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
decorationColor: linkColor,
|
decorationColor: linkColor,
|
||||||
|
|
|
||||||
|
|
@ -62,10 +62,7 @@ class InputBar extends StatelessWidget {
|
||||||
final commandSearch = commandMatch[1]!.toLowerCase();
|
final commandSearch = commandMatch[1]!.toLowerCase();
|
||||||
for (final command in room.client.commands.keys) {
|
for (final command in room.client.commands.keys) {
|
||||||
if (command.contains(commandSearch)) {
|
if (command.contains(commandSearch)) {
|
||||||
ret.add({
|
ret.add({'type': 'command', 'name': command});
|
||||||
'type': 'command',
|
|
||||||
'name': command,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret.length > maxResults) return ret;
|
if (ret.length > maxResults) return ret;
|
||||||
|
|
@ -107,8 +104,8 @@ class InputBar extends StatelessWidget {
|
||||||
'type': 'emote',
|
'type': 'emote',
|
||||||
'name': emote.key,
|
'name': emote.key,
|
||||||
'pack': packSearch,
|
'pack': packSearch,
|
||||||
'pack_avatar_url':
|
'pack_avatar_url': emotePacks[packSearch]!.pack.avatarUrl
|
||||||
emotePacks[packSearch]!.pack.avatarUrl?.toString(),
|
?.toString(),
|
||||||
'pack_display_name':
|
'pack_display_name':
|
||||||
emotePacks[packSearch]!.pack.displayName ?? packSearch,
|
emotePacks[packSearch]!.pack.displayName ?? packSearch,
|
||||||
'mxc': emote.value.url.toString(),
|
'mxc': emote.value.url.toString(),
|
||||||
|
|
@ -159,8 +156,9 @@ class InputBar extends StatelessWidget {
|
||||||
for (final user in room.getParticipants()) {
|
for (final user in room.getParticipants()) {
|
||||||
if ((user.displayName != null &&
|
if ((user.displayName != null &&
|
||||||
(user.displayName!.toLowerCase().contains(userSearch) ||
|
(user.displayName!.toLowerCase().contains(userSearch) ||
|
||||||
slugify(user.displayName!.toLowerCase())
|
slugify(
|
||||||
.contains(userSearch))) ||
|
user.displayName!.toLowerCase(),
|
||||||
|
).contains(userSearch))) ||
|
||||||
user.id.split(':')[0].toLowerCase().contains(userSearch)) {
|
user.id.split(':')[0].toLowerCase().contains(userSearch)) {
|
||||||
ret.add({
|
ret.add({
|
||||||
'type': 'user',
|
'type': 'user',
|
||||||
|
|
@ -258,11 +256,7 @@ class InputBar extends StatelessWidget {
|
||||||
style: const TextStyle(fontSize: 16),
|
style: const TextStyle(fontSize: 16),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(label, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||||
label,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -280,7 +274,7 @@ class InputBar extends StatelessWidget {
|
||||||
isThumbnail: false,
|
isThumbnail: false,
|
||||||
),
|
),
|
||||||
title: Row(
|
title: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: .center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(suggestion['name']!),
|
Text(suggestion['name']!),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|
@ -311,7 +305,8 @@ class InputBar extends StatelessWidget {
|
||||||
onTap: () => onSelected(suggestion),
|
onTap: () => onSelected(suggestion),
|
||||||
leading: Avatar(
|
leading: Avatar(
|
||||||
mxContent: url,
|
mxContent: url,
|
||||||
name: suggestion.tryGet<String>('displayname') ??
|
name:
|
||||||
|
suggestion.tryGet<String>('displayname') ??
|
||||||
suggestion.tryGet<String>('mxid'),
|
suggestion.tryGet<String>('mxid'),
|
||||||
size: size,
|
size: size,
|
||||||
client: client,
|
client: client,
|
||||||
|
|
@ -323,8 +318,10 @@ class InputBar extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
String insertSuggestion(Map<String, String?> suggestion) {
|
String insertSuggestion(Map<String, String?> suggestion) {
|
||||||
final replaceText =
|
final replaceText = controller!.text.substring(
|
||||||
controller!.text.substring(0, controller!.selection.baseOffset);
|
0,
|
||||||
|
controller!.selection.baseOffset,
|
||||||
|
);
|
||||||
var startText = '';
|
var startText = '';
|
||||||
final afterText = replaceText == controller!.text
|
final afterText = replaceText == controller!.text
|
||||||
? ''
|
? ''
|
||||||
|
|
@ -409,10 +406,7 @@ class InputBar extends StatelessWidget {
|
||||||
bytes: data,
|
bytes: data,
|
||||||
name: content.uri.split('/').last,
|
name: content.uri.split('/').last,
|
||||||
);
|
);
|
||||||
room.sendFileEvent(
|
room.sendFileEvent(file, shrinkImageMaxDimension: 1600);
|
||||||
file,
|
|
||||||
shrinkImageMaxDimension: 1600,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
minLines: minLines,
|
minLines: minLines,
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,8 @@ class PinnedEvents extends StatelessWidget {
|
||||||
(event) => AdaptiveModalAction(
|
(event) => AdaptiveModalAction(
|
||||||
value: event?.eventId ?? '',
|
value: event?.eventId ?? '',
|
||||||
icon: const Icon(Icons.push_pin_outlined),
|
icon: const Icon(Icons.push_pin_outlined),
|
||||||
label: event?.calcLocalizedBodyFallback(
|
label:
|
||||||
|
event?.calcLocalizedBodyFallback(
|
||||||
MatrixLocals(L10n.of(context)),
|
MatrixLocals(L10n.of(context)),
|
||||||
withSenderNamePrefix: true,
|
withSenderNamePrefix: true,
|
||||||
hideReply: true,
|
hideReply: true,
|
||||||
|
|
@ -68,7 +69,8 @@ class PinnedEvents extends StatelessWidget {
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final event = snapshot.data;
|
final event = snapshot.data;
|
||||||
return ChatAppBarListTile(
|
return ChatAppBarListTile(
|
||||||
title: event?.calcLocalizedBodyFallback(
|
title:
|
||||||
|
event?.calcLocalizedBodyFallback(
|
||||||
MatrixLocals(L10n.of(context)),
|
MatrixLocals(L10n.of(context)),
|
||||||
withSenderNamePrefix: true,
|
withSenderNamePrefix: true,
|
||||||
hideReply: true,
|
hideReply: true,
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ class RecordingInputRow extends StatelessWidget {
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
const width = 4;
|
const width = 4;
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: .end,
|
||||||
children: state.amplitudeTimeline.reversed
|
children: state.amplitudeTimeline.reversed
|
||||||
.take((constraints.maxWidth / (width + 2)).floor())
|
.take((constraints.maxWidth / (width + 2)).floor())
|
||||||
.toList()
|
.toList()
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,7 @@ import 'events/audio_player.dart';
|
||||||
class RecordingViewModel extends StatefulWidget {
|
class RecordingViewModel extends StatefulWidget {
|
||||||
final Widget Function(BuildContext, RecordingViewModelState) builder;
|
final Widget Function(BuildContext, RecordingViewModelState) builder;
|
||||||
|
|
||||||
const RecordingViewModel({
|
const RecordingViewModel({required this.builder, super.key});
|
||||||
required this.builder,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RecordingViewModelState createState() => RecordingViewModelState();
|
RecordingViewModelState createState() => RecordingViewModelState();
|
||||||
|
|
@ -70,10 +67,10 @@ class RecordingViewModelState extends State<RecordingViewModel> {
|
||||||
? AudioEncoder.wav
|
? AudioEncoder.wav
|
||||||
// Everywhere else we use opus if supported by the platform:
|
// Everywhere else we use opus if supported by the platform:
|
||||||
: !PlatformInfos
|
: !PlatformInfos
|
||||||
.isIOS && // Blocked by https://github.com/llfbandit/record/issues/560
|
.isIOS && // Blocked by https://github.com/llfbandit/record/issues/560
|
||||||
await audioRecorder.isEncoderSupported(AudioEncoder.opus)
|
await audioRecorder.isEncoderSupported(AudioEncoder.opus)
|
||||||
? AudioEncoder.opus
|
? AudioEncoder.opus
|
||||||
: AudioEncoder.aacLc;
|
: AudioEncoder.aacLc;
|
||||||
fileName =
|
fileName =
|
||||||
'recording${DateTime.now().microsecondsSinceEpoch}.${codec.fileExtension}';
|
'recording${DateTime.now().microsecondsSinceEpoch}.${codec.fileExtension}';
|
||||||
String? path;
|
String? path;
|
||||||
|
|
@ -126,8 +123,9 @@ class RecordingViewModelState extends State<RecordingViewModel> {
|
||||||
|
|
||||||
void _subscribe() {
|
void _subscribe() {
|
||||||
_recorderSubscription?.cancel();
|
_recorderSubscription?.cancel();
|
||||||
_recorderSubscription =
|
_recorderSubscription = Timer.periodic(const Duration(milliseconds: 100), (
|
||||||
Timer.periodic(const Duration(milliseconds: 100), (_) async {
|
_,
|
||||||
|
) async {
|
||||||
final amplitude = await _audioRecorder!.getAmplitude();
|
final amplitude = await _audioRecorder!.getAmplitude();
|
||||||
var value = 100 + amplitude.current * 2;
|
var value = 100 + amplitude.current * 2;
|
||||||
value = value < 1 ? 1 : value;
|
value = value < 1 ? 1 : value;
|
||||||
|
|
@ -178,7 +176,8 @@ class RecordingViewModelState extends State<RecordingViewModel> {
|
||||||
int duration,
|
int duration,
|
||||||
List<int> waveform,
|
List<int> waveform,
|
||||||
String? fileName,
|
String? fileName,
|
||||||
) onSend,
|
)
|
||||||
|
onSend,
|
||||||
) async {
|
) async {
|
||||||
_recorderSubscription?.cancel();
|
_recorderSubscription?.cancel();
|
||||||
final path = await _audioRecorder?.stop();
|
final path = await _audioRecorder?.stop();
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,7 @@ class ReplyDisplay extends StatelessWidget {
|
||||||
? 56
|
? 56
|
||||||
: 0,
|
: 0,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(color: theme.colorScheme.onInverseSurface),
|
||||||
color: theme.colorScheme.onInverseSurface,
|
|
||||||
),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|
@ -63,10 +61,7 @@ class _EditContent extends StatelessWidget {
|
||||||
}
|
}
|
||||||
return Row(
|
return Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Icon(
|
Icon(Icons.edit, color: theme.colorScheme.primary),
|
||||||
Icons.edit,
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
Container(width: 15.0),
|
Container(width: 15.0),
|
||||||
Text(
|
Text(
|
||||||
event.calcLocalizedBodyFallback(
|
event.calcLocalizedBodyFallback(
|
||||||
|
|
@ -76,9 +71,7 @@ class _EditContent extends StatelessWidget {
|
||||||
),
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
style: TextStyle(
|
style: TextStyle(color: theme.textTheme.bodyMedium!.color),
|
||||||
color: theme.textTheme.bodyMedium!.color,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,16 @@ class SeenByRow extends StatelessWidget {
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
constraints:
|
constraints: const BoxConstraints(
|
||||||
const BoxConstraints(maxWidth: FluffyThemes.maxTimelineWidth),
|
maxWidth: FluffyThemes.maxTimelineWidth,
|
||||||
|
),
|
||||||
height: seenByUsers.isEmpty ? 0 : 24,
|
height: seenByUsers.isEmpty ? 0 : 24,
|
||||||
duration: seenByUsers.isEmpty
|
duration: seenByUsers.isEmpty
|
||||||
? Duration.zero
|
? Duration.zero
|
||||||
: FluffyThemes.animationDuration,
|
: FluffyThemes.animationDuration,
|
||||||
curve: FluffyThemes.animationCurve,
|
curve: FluffyThemes.animationCurve,
|
||||||
alignment: controller.timeline!.events.isNotEmpty &&
|
alignment:
|
||||||
|
controller.timeline!.events.isNotEmpty &&
|
||||||
controller.timeline!.events.first.senderId ==
|
controller.timeline!.events.first.senderId ==
|
||||||
Matrix.of(context).client.userID
|
Matrix.of(context).client.userID
|
||||||
? Alignment.topRight
|
? Alignment.topRight
|
||||||
|
|
@ -40,12 +42,12 @@ class SeenByRow extends StatelessWidget {
|
||||||
? seenByUsers.sublist(0, maxAvatars)
|
? seenByUsers.sublist(0, maxAvatars)
|
||||||
: seenByUsers)
|
: seenByUsers)
|
||||||
.map(
|
.map(
|
||||||
(user) => Avatar(
|
(user) => Avatar(
|
||||||
mxContent: user.avatarUrl,
|
mxContent: user.avatarUrl,
|
||||||
name: user.calcDisplayname(),
|
name: user.calcDisplayname(),
|
||||||
size: 16,
|
size: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (seenByUsers.length > maxAvatars)
|
if (seenByUsers.length > maxAvatars)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 16,
|
width: 16,
|
||||||
|
|
|
||||||
|
|
@ -119,8 +119,9 @@ class SendFileDialogState extends State<SendFileDialog> {
|
||||||
if (e.error != MatrixError.M_LIMIT_EXCEEDED || retryAfterMs == null) {
|
if (e.error != MatrixError.M_LIMIT_EXCEEDED || retryAfterMs == null) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
final retryAfterDuration =
|
final retryAfterDuration = Duration(
|
||||||
Duration(milliseconds: retryAfterMs + 1000);
|
milliseconds: retryAfterMs + 1000,
|
||||||
|
);
|
||||||
|
|
||||||
scaffoldMessenger.showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
|
|
@ -164,8 +165,9 @@ class SendFileDialogState extends State<SendFileDialog> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _calcCombinedFileSize() async {
|
Future<String> _calcCombinedFileSize() async {
|
||||||
final lengths =
|
final lengths = await Future.wait(
|
||||||
await Future.wait(widget.files.map((file) => file.length()));
|
widget.files.map((file) => file.length()),
|
||||||
|
);
|
||||||
return lengths.fold<double>(0, (p, length) => p + length).sizeString;
|
return lengths.fold<double>(0, (p, length) => p + length).sizeString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,7 +218,7 @@ class SendFileDialogState extends State<SendFileDialog> {
|
||||||
width: 256,
|
width: 256,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
if (uniqueFileType == 'image')
|
if (uniqueFileType == 'image')
|
||||||
|
|
@ -243,8 +245,8 @@ class SendFileDialogState extends State<SendFileDialog> {
|
||||||
final bytes = snapshot.data;
|
final bytes = snapshot.data;
|
||||||
if (bytes == null) {
|
if (bytes == null) {
|
||||||
return const Center(
|
return const Center(
|
||||||
child: CircularProgressIndicator
|
child:
|
||||||
.adaptive(),
|
CircularProgressIndicator.adaptive(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (snapshot.error != null) {
|
if (snapshot.error != null) {
|
||||||
|
|
@ -272,8 +274,11 @@ class SendFileDialogState extends State<SendFileDialog> {
|
||||||
: null,
|
: null,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
errorBuilder: (context, e, s) {
|
errorBuilder: (context, e, s) {
|
||||||
Logs()
|
Logs().w(
|
||||||
.w('Unable to preview image', e, s);
|
'Unable to preview image',
|
||||||
|
e,
|
||||||
|
s,
|
||||||
|
);
|
||||||
return const Center(
|
return const Center(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 256,
|
width: 256,
|
||||||
|
|
@ -303,17 +308,17 @@ class SendFileDialogState extends State<SendFileDialog> {
|
||||||
uniqueFileType == null
|
uniqueFileType == null
|
||||||
? Icons.description_outlined
|
? Icons.description_outlined
|
||||||
: uniqueFileType == 'video'
|
: uniqueFileType == 'video'
|
||||||
? Icons.video_file_outlined
|
? Icons.video_file_outlined
|
||||||
: uniqueFileType == 'audio'
|
: uniqueFileType == 'audio'
|
||||||
? Icons.audio_file_outlined
|
? Icons.audio_file_outlined
|
||||||
: Icons.description_outlined,
|
: Icons.description_outlined,
|
||||||
size: 32,
|
size: 32,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: .start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
fileName,
|
fileName,
|
||||||
|
|
@ -347,10 +352,12 @@ class SendFileDialogState extends State<SendFileDialog> {
|
||||||
// Workaround for SwitchListTile.adaptive crashes in CupertinoDialog
|
// Workaround for SwitchListTile.adaptive crashes in CupertinoDialog
|
||||||
if ({'image', 'video'}.contains(uniqueFileType))
|
if ({'image', 'video'}.contains(uniqueFileType))
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: .center,
|
||||||
children: [
|
children: [
|
||||||
if ({TargetPlatform.iOS, TargetPlatform.macOS}
|
if ({
|
||||||
.contains(theme.platform))
|
TargetPlatform.iOS,
|
||||||
|
TargetPlatform.macOS,
|
||||||
|
}.contains(theme.platform))
|
||||||
CupertinoSwitch(
|
CupertinoSwitch(
|
||||||
value: compressionSupported && compress,
|
value: compressionSupported && compress,
|
||||||
onChanged: compressionSupported
|
onChanged: compressionSupported
|
||||||
|
|
@ -367,11 +374,11 @@ class SendFileDialogState extends State<SendFileDialog> {
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: .start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
L10n.of(context).compress,
|
L10n.of(context).compress,
|
||||||
|
|
@ -430,9 +437,7 @@ extension on ScaffoldMessengerState {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 16,
|
width: 16,
|
||||||
height: 16,
|
height: 16,
|
||||||
child: CircularProgressIndicator.adaptive(
|
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Text(title),
|
Text(title),
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,7 @@ import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||||
class SendLocationDialog extends StatefulWidget {
|
class SendLocationDialog extends StatefulWidget {
|
||||||
final Room room;
|
final Room room;
|
||||||
|
|
||||||
const SendLocationDialog({
|
const SendLocationDialog({required this.room, super.key});
|
||||||
required this.room,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SendLocationDialogState createState() => SendLocationDialogState();
|
SendLocationDialogState createState() => SendLocationDialogState();
|
||||||
|
|
@ -102,12 +99,13 @@ class SendLocationDialogState extends State<SendLocationDialog> {
|
||||||
} else if (denied) {
|
} else if (denied) {
|
||||||
contentWidget = Text(L10n.of(context).locationPermissionDeniedNotice);
|
contentWidget = Text(L10n.of(context).locationPermissionDeniedNotice);
|
||||||
} else if (error != null) {
|
} else if (error != null) {
|
||||||
contentWidget =
|
contentWidget = Text(
|
||||||
Text(L10n.of(context).errorObtainingLocation(error.toString()));
|
L10n.of(context).errorObtainingLocation(error.toString()),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
contentWidget = Row(
|
contentWidget = Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
children: [
|
children: [
|
||||||
const CupertinoActivityIndicator(),
|
const CupertinoActivityIndicator(),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
|
||||||
|
|
@ -50,14 +50,15 @@ class _StartPollBottomSheetState extends State<StartPollBottomSheet> {
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().w('Unable to create poll', e, s);
|
Logs().w('Unable to create poll', e, s);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text(e.toLocalizedString(context))),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateCanCreate([dynamic _]) {
|
void _updateCanCreate([dynamic _]) {
|
||||||
final newCanCreate = _bodyController.text.trim().isNotEmpty &&
|
final newCanCreate =
|
||||||
|
_bodyController.text.trim().isNotEmpty &&
|
||||||
!_answers.any((controller) => controller.text.trim().isEmpty);
|
!_answers.any((controller) => controller.text.trim().isEmpty);
|
||||||
if (_canCreate != newCanCreate) {
|
if (_canCreate != newCanCreate) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -71,9 +72,7 @@ class _StartPollBottomSheetState extends State<StartPollBottomSheet> {
|
||||||
const maxAnswers = 10;
|
const maxAnswers = 10;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: CloseButton(
|
leading: CloseButton(onPressed: Navigator.of(context).pop),
|
||||||
onPressed: Navigator.of(context).pop,
|
|
||||||
),
|
|
||||||
title: Text(L10n.of(context).startPoll),
|
title: Text(L10n.of(context).startPoll),
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
|
|
@ -119,8 +118,8 @@ class _StartPollBottomSheetState extends State<StartPollBottomSheet> {
|
||||||
icon: const Icon(Icons.add_outlined),
|
icon: const Icon(Icons.add_outlined),
|
||||||
onPressed: _answers.length < maxAnswers
|
onPressed: _answers.length < maxAnswers
|
||||||
? () => setState(() {
|
? () => setState(() {
|
||||||
_answers.add(TextEditingController());
|
_answers.add(TextEditingController());
|
||||||
})
|
})
|
||||||
: null,
|
: null,
|
||||||
label: Text(L10n.of(context).addAnswerOption),
|
label: Text(L10n.of(context).addAnswerOption),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -38,15 +38,17 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
|
||||||
final filteredImagePackImageEntried = pack.images.entries.toList();
|
final filteredImagePackImageEntried = pack.images.entries.toList();
|
||||||
if (searchFilter?.isNotEmpty ?? false) {
|
if (searchFilter?.isNotEmpty ?? false) {
|
||||||
filteredImagePackImageEntried.removeWhere(
|
filteredImagePackImageEntried.removeWhere(
|
||||||
(e) => !(e.key.toLowerCase().contains(searchFilter!.toLowerCase()) ||
|
(e) =>
|
||||||
(e.value.body
|
!(e.key.toLowerCase().contains(searchFilter!.toLowerCase()) ||
|
||||||
?.toLowerCase()
|
(e.value.body?.toLowerCase().contains(
|
||||||
.contains(searchFilter!.toLowerCase()) ??
|
searchFilter!.toLowerCase(),
|
||||||
false)),
|
) ??
|
||||||
|
false)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final imageKeys =
|
final imageKeys = filteredImagePackImageEntried
|
||||||
filteredImagePackImageEntried.map((e) => e.key).toList();
|
.map((e) => e.key)
|
||||||
|
.toList();
|
||||||
if (imageKeys.isEmpty) {
|
if (imageKeys.isEmpty) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
@ -82,8 +84,9 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
|
||||||
key: ValueKey(image.url.toString()),
|
key: ValueKey(image.url.toString()),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// copy the image
|
// copy the image
|
||||||
final imageCopy =
|
final imageCopy = ImagePackImageContent.fromJson(
|
||||||
ImagePackImageContent.fromJson(image.toJson().copy());
|
image.toJson().copy(),
|
||||||
|
);
|
||||||
// set the body, if it doesn't exist, to the key
|
// set the body, if it doesn't exist, to the key
|
||||||
imageCopy.body ??= imageKeys[imageIndex];
|
imageCopy.body ??= imageKeys[imageIndex];
|
||||||
widget.onSelected(imageCopy);
|
widget.onSelected(imageCopy);
|
||||||
|
|
@ -137,7 +140,7 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
|
||||||
SliverFillRemaining(
|
SliverFillRemaining(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
Text(L10n.of(context).noEmotesFound),
|
Text(L10n.of(context).noEmotesFound),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,9 @@ class TypingIndicators extends StatelessWidget {
|
||||||
return StreamBuilder<Object>(
|
return StreamBuilder<Object>(
|
||||||
stream: controller.room.client.onSync.stream.where(
|
stream: controller.room.client.onSync.stream.where(
|
||||||
(syncUpdate) =>
|
(syncUpdate) =>
|
||||||
syncUpdate.rooms?.join?[controller.room.id]?.ephemeral
|
syncUpdate.rooms?.join?[controller.room.id]?.ephemeral?.any(
|
||||||
?.any((ephemeral) => ephemeral.type == 'm.typing') ??
|
(ephemeral) => ephemeral.type == 'm.typing',
|
||||||
|
) ??
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
|
|
@ -33,22 +34,21 @@ class TypingIndicators extends StatelessWidget {
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
constraints:
|
constraints: const BoxConstraints(
|
||||||
const BoxConstraints(maxWidth: FluffyThemes.maxTimelineWidth),
|
maxWidth: FluffyThemes.maxTimelineWidth,
|
||||||
|
),
|
||||||
height: typingUsers.isEmpty ? 0 : avatarSize + 8,
|
height: typingUsers.isEmpty ? 0 : avatarSize + 8,
|
||||||
duration: FluffyThemes.animationDuration,
|
duration: FluffyThemes.animationDuration,
|
||||||
curve: FluffyThemes.animationCurve,
|
curve: FluffyThemes.animationCurve,
|
||||||
alignment: controller.timeline!.events.isNotEmpty &&
|
alignment:
|
||||||
|
controller.timeline!.events.isNotEmpty &&
|
||||||
controller.timeline!.events.first.senderId ==
|
controller.timeline!.events.first.senderId ==
|
||||||
Matrix.of(context).client.userID
|
Matrix.of(context).client.userID
|
||||||
? Alignment.topRight
|
? Alignment.topRight
|
||||||
: Alignment.topLeft,
|
: Alignment.topLeft,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
decoration: const BoxDecoration(),
|
decoration: const BoxDecoration(),
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||||
horizontal: 8.0,
|
|
||||||
vertical: 4.0,
|
|
||||||
),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
|
|
@ -115,17 +115,14 @@ class __TypingDotsState extends State<_TypingDots> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_timer = Timer.periodic(
|
_timer = Timer.periodic(animationDuration, (_) {
|
||||||
animationDuration,
|
if (!mounted) {
|
||||||
(_) {
|
return;
|
||||||
if (!mounted) {
|
}
|
||||||
return;
|
setState(() {
|
||||||
}
|
_tick = (_tick + 1) % 4;
|
||||||
setState(() {
|
});
|
||||||
_tick = (_tick + 1) % 4;
|
});
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,7 +138,7 @@ class __TypingDotsState extends State<_TypingDots> {
|
||||||
const size = 8.0;
|
const size = 8.0;
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
for (var i = 1; i <= 3; i++)
|
for (var i = 1; i <= 3; i++)
|
||||||
AnimatedContainer(
|
AnimatedContainer(
|
||||||
|
|
|
||||||
|
|
@ -27,15 +27,15 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
||||||
bool guestAccessLoading = false;
|
bool guestAccessLoading = false;
|
||||||
Room get room => Matrix.of(context).client.getRoomById(widget.roomId)!;
|
Room get room => Matrix.of(context).client.getRoomById(widget.roomId)!;
|
||||||
Set<Room> get knownSpaceParents => {
|
Set<Room> get knownSpaceParents => {
|
||||||
...room.client.rooms.where(
|
...room.client.rooms.where(
|
||||||
(space) =>
|
(space) =>
|
||||||
space.isSpace &&
|
space.isSpace &&
|
||||||
space.spaceChildren.any((child) => child.roomId == room.id),
|
space.spaceChildren.any((child) => child.roomId == room.id),
|
||||||
),
|
),
|
||||||
...room.spaceParents
|
...room.spaceParents
|
||||||
.map((parent) => room.client.getRoomById(parent.roomId ?? ''))
|
.map((parent) => room.client.getRoomById(parent.roomId ?? ''))
|
||||||
.whereType<Room>(),
|
.whereType<Room>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
String get roomVersion =>
|
String get roomVersion =>
|
||||||
room
|
room
|
||||||
|
|
@ -87,21 +87,20 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
||||||
try {
|
try {
|
||||||
await room.setJoinRules(
|
await room.setJoinRules(
|
||||||
newJoinRules,
|
newJoinRules,
|
||||||
allowConditionRoomIds: {JoinRules.restricted, JoinRules.knockRestricted}
|
allowConditionRoomIds:
|
||||||
.contains(newJoinRules)
|
{
|
||||||
|
JoinRules.restricted,
|
||||||
|
JoinRules.knockRestricted,
|
||||||
|
}.contains(newJoinRules)
|
||||||
? knownSpaceParents.map((parent) => parent.id).toList()
|
? knownSpaceParents.map((parent) => parent.id).toList()
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().w('Unable to change join rules', e, s);
|
Logs().w('Unable to change join rules', e, s);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
||||||
e.toLocalizedString(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|
@ -123,13 +122,9 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().w('Unable to change history visibility', e, s);
|
Logs().w('Unable to change history visibility', e, s);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
||||||
e.toLocalizedString(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|
@ -151,13 +146,9 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().w('Unable to change guest access', e, s);
|
Logs().w('Unable to change guest access', e, s);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
||||||
e.toLocalizedString(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|
@ -216,8 +207,11 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
||||||
newRoom = room.client.getRoomById(newRoomId);
|
newRoom = room.client.getRoomById(newRoomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ({JoinRules.invite, JoinRules.knock, JoinRules.knockRestricted}
|
if ({
|
||||||
.contains(room.joinRules)) {
|
JoinRules.invite,
|
||||||
|
JoinRules.knock,
|
||||||
|
JoinRules.knockRestricted,
|
||||||
|
}.contains(room.joinRules)) {
|
||||||
final users = await room.requestParticipants([
|
final users = await room.requestParticipants([
|
||||||
Membership.join,
|
Membership.join,
|
||||||
Membership.invite,
|
Membership.invite,
|
||||||
|
|
@ -282,7 +276,8 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
||||||
cancelLabel: L10n.of(context).no,
|
cancelLabel: L10n.of(context).no,
|
||||||
);
|
);
|
||||||
|
|
||||||
final altAliases = room
|
final altAliases =
|
||||||
|
room
|
||||||
.getState(EventTypes.RoomCanonicalAlias)
|
.getState(EventTypes.RoomCanonicalAlias)
|
||||||
?.content
|
?.content
|
||||||
.tryGetList<String>('alt_aliases')
|
.tryGetList<String>('alt_aliases')
|
||||||
|
|
@ -298,17 +293,13 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
||||||
|
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => room.client.setRoomStateWithKey(
|
future: () => room.client
|
||||||
room.id,
|
.setRoomStateWithKey(room.id, EventTypes.RoomCanonicalAlias, '', {
|
||||||
EventTypes.RoomCanonicalAlias,
|
'alias': canonicalAliasConsent == OkCancelResult.ok
|
||||||
'',
|
? alias
|
||||||
{
|
: room.canonicalAlias,
|
||||||
'alias': canonicalAliasConsent == OkCancelResult.ok
|
if (altAliases.isNotEmpty) 'alt_aliases': altAliases.toList(),
|
||||||
? alias
|
}),
|
||||||
: room.canonicalAlias,
|
|
||||||
if (altAliases.isNotEmpty) 'alt_aliases': altAliases.toList(),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,13 +326,9 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().w('Unable to change visibility', e, s);
|
Logs().w('Unable to change visibility', e, s);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
||||||
e.toLocalizedString(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|
|
||||||
|
|
@ -24,17 +24,19 @@ class ChatAccessSettingsPageView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
body: MaxWidthBody(
|
body: MaxWidthBody(
|
||||||
child: StreamBuilder<Object>(
|
child: StreamBuilder<Object>(
|
||||||
stream: room.client.onRoomState.stream
|
stream: room.client.onRoomState.stream.where(
|
||||||
.where((update) => update.roomId == controller.room.id),
|
(update) => update.roomId == controller.room.id,
|
||||||
|
),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final canonicalAlias = room.canonicalAlias;
|
final canonicalAlias = room.canonicalAlias;
|
||||||
final altAliases = room
|
final altAliases =
|
||||||
|
room
|
||||||
.getState(EventTypes.RoomCanonicalAlias)
|
.getState(EventTypes.RoomCanonicalAlias)
|
||||||
?.content
|
?.content
|
||||||
.tryGetList<String>('alt_aliases') ??
|
.tryGetList<String>('alt_aliases') ??
|
||||||
[];
|
[];
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
|
|
@ -47,12 +49,13 @@ class ChatAccessSettingsPageView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
RadioGroup<HistoryVisibility>(
|
RadioGroup<HistoryVisibility>(
|
||||||
groupValue: room.historyVisibility,
|
groupValue: room.historyVisibility,
|
||||||
onChanged: controller.historyVisibilityLoading ||
|
onChanged:
|
||||||
|
controller.historyVisibilityLoading ||
|
||||||
!room.canChangeHistoryVisibility
|
!room.canChangeHistoryVisibility
|
||||||
? (_) {}
|
? (_) {}
|
||||||
: controller.setHistoryVisibility,
|
: controller.setHistoryVisibility,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
for (final historyVisibility in HistoryVisibility.values)
|
for (final historyVisibility in HistoryVisibility.values)
|
||||||
RadioListTile<HistoryVisibility>.adaptive(
|
RadioListTile<HistoryVisibility>.adaptive(
|
||||||
|
|
@ -80,12 +83,13 @@ class ChatAccessSettingsPageView extends StatelessWidget {
|
||||||
groupValue: room.joinRules,
|
groupValue: room.joinRules,
|
||||||
onChanged: controller.setJoinRule,
|
onChanged: controller.setJoinRule,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
for (final joinRule in controller.availableJoinRules)
|
for (final joinRule in controller.availableJoinRules)
|
||||||
if (joinRule != JoinRules.private)
|
if (joinRule != JoinRules.private)
|
||||||
RadioListTile<JoinRules>.adaptive(
|
RadioListTile<JoinRules>.adaptive(
|
||||||
enabled: !controller.joinRulesLoading &&
|
enabled:
|
||||||
|
!controller.joinRulesLoading &&
|
||||||
room.canChangeJoinRules,
|
room.canChangeJoinRules,
|
||||||
title: Text(
|
title: Text(
|
||||||
joinRule.localizedString(
|
joinRule.localizedString(
|
||||||
|
|
@ -99,8 +103,10 @@ class ChatAccessSettingsPageView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Divider(color: theme.dividerColor),
|
Divider(color: theme.dividerColor),
|
||||||
if ({JoinRules.public, JoinRules.knock}
|
if ({
|
||||||
.contains(room.joinRules)) ...[
|
JoinRules.public,
|
||||||
|
JoinRules.knock,
|
||||||
|
}.contains(room.joinRules)) ...[
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
L10n.of(context).areGuestsAllowedToJoin,
|
L10n.of(context).areGuestsAllowedToJoin,
|
||||||
|
|
@ -114,11 +120,12 @@ class ChatAccessSettingsPageView extends StatelessWidget {
|
||||||
groupValue: room.guestAccess,
|
groupValue: room.guestAccess,
|
||||||
onChanged: controller.setGuestAccess,
|
onChanged: controller.setGuestAccess,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
for (final guestAccess in GuestAccess.values)
|
for (final guestAccess in GuestAccess.values)
|
||||||
RadioListTile<GuestAccess>.adaptive(
|
RadioListTile<GuestAccess>.adaptive(
|
||||||
enabled: !controller.guestAccessLoading &&
|
enabled:
|
||||||
|
!controller.guestAccessLoading &&
|
||||||
room.canChangeGuestAccess,
|
room.canChangeGuestAccess,
|
||||||
title: Text(
|
title: Text(
|
||||||
guestAccess.getLocalizedString(
|
guestAccess.getLocalizedString(
|
||||||
|
|
@ -148,9 +155,10 @@ class ChatAccessSettingsPageView extends StatelessWidget {
|
||||||
if (canonicalAlias.isNotEmpty)
|
if (canonicalAlias.isNotEmpty)
|
||||||
_AliasListTile(
|
_AliasListTile(
|
||||||
alias: canonicalAlias,
|
alias: canonicalAlias,
|
||||||
onDelete: room.canChangeStateEvent(
|
onDelete:
|
||||||
EventTypes.RoomCanonicalAlias,
|
room.canChangeStateEvent(
|
||||||
)
|
EventTypes.RoomCanonicalAlias,
|
||||||
|
)
|
||||||
? () => controller.deleteAlias(canonicalAlias)
|
? () => controller.deleteAlias(canonicalAlias)
|
||||||
: null,
|
: null,
|
||||||
isCanonicalAlias: true,
|
isCanonicalAlias: true,
|
||||||
|
|
@ -158,9 +166,10 @@ class ChatAccessSettingsPageView extends StatelessWidget {
|
||||||
for (final alias in altAliases)
|
for (final alias in altAliases)
|
||||||
_AliasListTile(
|
_AliasListTile(
|
||||||
alias: alias,
|
alias: alias,
|
||||||
onDelete: room.canChangeStateEvent(
|
onDelete:
|
||||||
EventTypes.RoomCanonicalAlias,
|
room.canChangeStateEvent(
|
||||||
)
|
EventTypes.RoomCanonicalAlias,
|
||||||
|
)
|
||||||
? () => controller.deleteAlias(alias)
|
? () => controller.deleteAlias(alias)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
|
|
@ -172,10 +181,11 @@ class ChatAccessSettingsPageView extends StatelessWidget {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
localAddresses.remove(room.canonicalAlias);
|
localAddresses.remove(room.canonicalAlias);
|
||||||
localAddresses
|
localAddresses.removeWhere(
|
||||||
.removeWhere((alias) => altAliases.contains(alias));
|
(alias) => altAliases.contains(alias),
|
||||||
|
);
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: localAddresses
|
children: localAddresses
|
||||||
.map(
|
.map(
|
||||||
(alias) => _AliasListTile(
|
(alias) => _AliasListTile(
|
||||||
|
|
@ -257,10 +267,7 @@ class _AliasListTile extends StatelessWidget {
|
||||||
? const Icon(Icons.star)
|
? const Icon(Icons.star)
|
||||||
: const Icon(Icons.link_outlined),
|
: const Icon(Icons.link_outlined),
|
||||||
title: InkWell(
|
title: InkWell(
|
||||||
onTap: () => FluffyShare.share(
|
onTap: () => FluffyShare.share('https://matrix.to/#/$alias', context),
|
||||||
'https://matrix.to/#/$alias',
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
alias,
|
alias,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|
|
||||||
|
|
@ -46,11 +46,7 @@ class ChatDetailsController extends State<ChatDetails> {
|
||||||
title: L10n.of(context).changeTheNameOfTheGroup,
|
title: L10n.of(context).changeTheNameOfTheGroup,
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: L10n.of(context).ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: L10n.of(context).cancel,
|
||||||
initialText: room.getLocalizedDisplayname(
|
initialText: room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
|
||||||
MatrixLocals(
|
|
||||||
L10n.of(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (input == null) return;
|
if (input == null) return;
|
||||||
final success = await showFutureLoadingDialog(
|
final success = await showFutureLoadingDialog(
|
||||||
|
|
@ -83,9 +79,7 @@ class ChatDetailsController extends State<ChatDetails> {
|
||||||
);
|
);
|
||||||
if (success.error == null) {
|
if (success.error == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(content: Text(L10n.of(context).chatDescriptionHasBeenChanged)),
|
||||||
content: Text(L10n.of(context).chatDescriptionHasBeenChanged),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -138,10 +132,7 @@ class ChatDetailsController extends State<ChatDetails> {
|
||||||
imageQuality: 50,
|
imageQuality: 50,
|
||||||
);
|
);
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
file = MatrixFile(
|
file = MatrixFile(bytes: await result.readAsBytes(), name: result.path);
|
||||||
bytes: await result.readAsBytes(),
|
|
||||||
name: result.path,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
final picked = await selectFiles(
|
final picked = await selectFiles(
|
||||||
context,
|
context,
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,7 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
final room = Matrix.of(context).client.getRoomById(controller.roomId!);
|
final room = Matrix.of(context).client.getRoomById(controller.roomId!);
|
||||||
if (room == null) {
|
if (room == null) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(title: Text(L10n.of(context).oopsSomethingWentWrong)),
|
||||||
title: Text(L10n.of(context).oopsSomethingWentWrong),
|
|
||||||
),
|
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat),
|
child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat),
|
||||||
),
|
),
|
||||||
|
|
@ -42,13 +40,15 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
final roomAvatar = room.avatar;
|
final roomAvatar = room.avatar;
|
||||||
|
|
||||||
return StreamBuilder(
|
return StreamBuilder(
|
||||||
stream: room.client.onRoomState.stream
|
stream: room.client.onRoomState.stream.where(
|
||||||
.where((update) => update.roomId == room.id),
|
(update) => update.roomId == room.id,
|
||||||
|
),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
var members = room.getParticipants().toList()
|
var members = room.getParticipants().toList()
|
||||||
..sort((b, a) => a.powerLevel.compareTo(b.powerLevel));
|
..sort((b, a) => a.powerLevel.compareTo(b.powerLevel));
|
||||||
members = members.take(10).toList();
|
members = members.take(10).toList();
|
||||||
final actualMembersCount = (room.summary.mInvitedMemberCount ?? 0) +
|
final actualMembersCount =
|
||||||
|
(room.summary.mInvitedMemberCount ?? 0) +
|
||||||
(room.summary.mJoinedMemberCount ?? 0);
|
(room.summary.mJoinedMemberCount ?? 0);
|
||||||
final canRequestMoreMembers = members.length < actualMembersCount;
|
final canRequestMoreMembers = members.length < actualMembersCount;
|
||||||
final iconColor = theme.textTheme.bodyLarge!.color;
|
final iconColor = theme.textTheme.bodyLarge!.color;
|
||||||
|
|
@ -57,7 +57,8 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
);
|
);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: controller.widget.embeddedCloseButton ??
|
leading:
|
||||||
|
controller.widget.embeddedCloseButton ??
|
||||||
const Center(child: BackButton()),
|
const Center(child: BackButton()),
|
||||||
elevation: theme.appBarTheme.elevation,
|
elevation: theme.appBarTheme.elevation,
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
|
|
@ -65,19 +66,15 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: L10n.of(context).share,
|
tooltip: L10n.of(context).share,
|
||||||
icon: const Icon(Icons.qr_code_rounded),
|
icon: const Icon(Icons.qr_code_rounded),
|
||||||
onPressed: () => showQrCodeViewer(
|
onPressed: () =>
|
||||||
context,
|
showQrCodeViewer(context, room.canonicalAlias),
|
||||||
room.canonicalAlias,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
else if (directChatMatrixID != null)
|
else if (directChatMatrixID != null)
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: L10n.of(context).share,
|
tooltip: L10n.of(context).share,
|
||||||
icon: const Icon(Icons.qr_code_rounded),
|
icon: const Icon(Icons.qr_code_rounded),
|
||||||
onPressed: () => showQrCodeViewer(
|
onPressed: () =>
|
||||||
context,
|
showQrCodeViewer(context, directChatMatrixID),
|
||||||
directChatMatrixID,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (controller.widget.embeddedCloseButton == null)
|
if (controller.widget.embeddedCloseButton == null)
|
||||||
ChatSettingsPopupMenu(room, false),
|
ChatSettingsPopupMenu(room, false),
|
||||||
|
|
@ -92,7 +89,7 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
itemCount: members.length + 1 + (canRequestMoreMembers ? 1 : 0),
|
itemCount: members.length + 1 + (canRequestMoreMembers ? 1 : 0),
|
||||||
itemBuilder: (BuildContext context, int i) => i == 0
|
itemBuilder: (BuildContext context, int i) => i == 0
|
||||||
? Column(
|
? Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: .stretch,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -103,19 +100,19 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
Hero(
|
Hero(
|
||||||
tag:
|
tag:
|
||||||
controller.widget.embeddedCloseButton !=
|
controller.widget.embeddedCloseButton !=
|
||||||
null
|
null
|
||||||
? 'embedded_content_banner'
|
? 'embedded_content_banner'
|
||||||
: 'content_banner',
|
: 'content_banner',
|
||||||
child: Avatar(
|
child: Avatar(
|
||||||
mxContent: room.avatar,
|
mxContent: room.avatar,
|
||||||
name: displayname,
|
name: displayname,
|
||||||
size: Avatar.defaultSize * 2.5,
|
size: Avatar.defaultSize * 2.5,
|
||||||
onTap: roomAvatar != null
|
onTap: roomAvatar != null
|
||||||
? () => showDialog(
|
? () => showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) =>
|
builder: (_) =>
|
||||||
MxcImageViewer(roomAvatar),
|
MxcImageViewer(roomAvatar),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -139,8 +136,8 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: .start,
|
||||||
children: [
|
children: [
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () => room.isDirectChat
|
onPressed: () => room.isDirectChat
|
||||||
|
|
@ -148,20 +145,20 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
: room.canChangeStateEvent(
|
: room.canChangeStateEvent(
|
||||||
EventTypes.RoomName,
|
EventTypes.RoomName,
|
||||||
)
|
)
|
||||||
? controller.setDisplaynameAction()
|
? controller.setDisplaynameAction()
|
||||||
: FluffyShare.share(
|
: FluffyShare.share(
|
||||||
displayname,
|
displayname,
|
||||||
context,
|
context,
|
||||||
copyOnly: true,
|
copyOnly: true,
|
||||||
),
|
),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
room.isDirectChat
|
room.isDirectChat
|
||||||
? Icons.chat_bubble_outline
|
? Icons.chat_bubble_outline
|
||||||
: room.canChangeStateEvent(
|
: room.canChangeStateEvent(
|
||||||
EventTypes.RoomName,
|
EventTypes.RoomName,
|
||||||
)
|
)
|
||||||
? Icons.edit_outlined
|
? Icons.edit_outlined
|
||||||
: Icons.copy_outlined,
|
: Icons.copy_outlined,
|
||||||
size: 16,
|
size: 16,
|
||||||
),
|
),
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
|
|
@ -194,9 +191,9 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
iconColor: theme.colorScheme.secondary,
|
iconColor: theme.colorScheme.secondary,
|
||||||
),
|
),
|
||||||
label: Text(
|
label: Text(
|
||||||
L10n.of(context).countParticipants(
|
L10n.of(
|
||||||
actualMembersCount,
|
context,
|
||||||
),
|
).countParticipants(actualMembersCount),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
// style: const TextStyle(fontSize: 12),
|
// style: const TextStyle(fontSize: 12),
|
||||||
|
|
@ -220,13 +217,14 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
trailing:
|
trailing:
|
||||||
room.canChangeStateEvent(EventTypes.RoomTopic)
|
room.canChangeStateEvent(EventTypes.RoomTopic)
|
||||||
? IconButton(
|
? IconButton(
|
||||||
onPressed: controller.setTopicAction,
|
onPressed: controller.setTopicAction,
|
||||||
tooltip:
|
tooltip: L10n.of(
|
||||||
L10n.of(context).setChatDescription,
|
context,
|
||||||
icon: const Icon(Icons.edit_outlined),
|
).setChatDescription,
|
||||||
)
|
icon: const Icon(Icons.edit_outlined),
|
||||||
: null,
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
|
|
@ -236,8 +234,9 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
text: room.topic.isEmpty
|
text: room.topic.isEmpty
|
||||||
? L10n.of(context).noChatDescriptionYet
|
? L10n.of(context).noChatDescriptionYet
|
||||||
: room.topic,
|
: room.topic,
|
||||||
textScaleFactor:
|
textScaleFactor: MediaQuery.textScalerOf(
|
||||||
MediaQuery.textScalerOf(context).scale(1),
|
context,
|
||||||
|
).scale(1),
|
||||||
options: const LinkifyOptions(humanize: false),
|
options: const LinkifyOptions(humanize: false),
|
||||||
linkStyle: const TextStyle(
|
linkStyle: const TextStyle(
|
||||||
color: Colors.blueAccent,
|
color: Colors.blueAccent,
|
||||||
|
|
@ -269,14 +268,13 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
Icons.admin_panel_settings_outlined,
|
Icons.admin_panel_settings_outlined,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(L10n.of(context).accessAndVisibility),
|
||||||
L10n.of(context).accessAndVisibility,
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
L10n.of(context).accessAndVisibilityDescription,
|
L10n.of(context).accessAndVisibilityDescription,
|
||||||
),
|
),
|
||||||
onTap: () => context
|
onTap: () => context.push(
|
||||||
.push('/rooms/${room.id}/details/access'),
|
'/rooms/${room.id}/details/access',
|
||||||
|
),
|
||||||
trailing: const Icon(Icons.chevron_right_outlined),
|
trailing: const Icon(Icons.chevron_right_outlined),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|
@ -288,21 +286,20 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.colorScheme.surfaceContainer,
|
theme.colorScheme.surfaceContainer,
|
||||||
foregroundColor: iconColor,
|
foregroundColor: iconColor,
|
||||||
child: const Icon(
|
child: const Icon(Icons.tune_outlined),
|
||||||
Icons.tune_outlined,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
trailing: const Icon(Icons.chevron_right_outlined),
|
trailing: const Icon(Icons.chevron_right_outlined),
|
||||||
onTap: () => context
|
onTap: () => context.push(
|
||||||
.push('/rooms/${room.id}/details/permissions'),
|
'/rooms/${room.id}/details/permissions',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
Divider(color: theme.dividerColor),
|
Divider(color: theme.dividerColor),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
L10n.of(context).countParticipants(
|
L10n.of(
|
||||||
actualMembersCount,
|
context,
|
||||||
),
|
).countParticipants(actualMembersCount),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: theme.colorScheme.secondary,
|
color: theme.colorScheme.secondary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|
@ -326,25 +323,25 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: i < members.length + 1
|
: i < members.length + 1
|
||||||
? ParticipantListItem(members[i - 1])
|
? ParticipantListItem(members[i - 1])
|
||||||
: ListTile(
|
: ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
L10n.of(context).loadCountMoreParticipants(
|
L10n.of(context).loadCountMoreParticipants(
|
||||||
(actualMembersCount - members.length),
|
(actualMembersCount - members.length),
|
||||||
),
|
|
||||||
),
|
|
||||||
leading: CircleAvatar(
|
|
||||||
backgroundColor: theme.scaffoldBackgroundColor,
|
|
||||||
child: const Icon(
|
|
||||||
Icons.group_outlined,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () => context.push(
|
|
||||||
'/rooms/${controller.roomId!}/details/members',
|
|
||||||
),
|
|
||||||
trailing: const Icon(Icons.chevron_right_outlined),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
leading: CircleAvatar(
|
||||||
|
backgroundColor: theme.scaffoldBackgroundColor,
|
||||||
|
child: const Icon(
|
||||||
|
Icons.group_outlined,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () => context.push(
|
||||||
|
'/rooms/${controller.roomId!}/details/members',
|
||||||
|
),
|
||||||
|
trailing: const Icon(Icons.chevron_right_outlined),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ class ParticipantListItem extends StatelessWidget {
|
||||||
final permissionBatch = user.powerLevel >= 100
|
final permissionBatch = user.powerLevel >= 100
|
||||||
? L10n.of(context).admin
|
? L10n.of(context).admin
|
||||||
: user.powerLevel >= 50
|
: user.powerLevel >= 50
|
||||||
? L10n.of(context).moderator
|
? L10n.of(context).moderator
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
onTap: () => showMemberActionsPopupMenu(context: context, user: user),
|
onTap: () => showMemberActionsPopupMenu(context: context, user: user),
|
||||||
|
|
@ -42,17 +42,12 @@ class ParticipantListItem extends StatelessWidget {
|
||||||
),
|
),
|
||||||
if (permissionBatch.isNotEmpty)
|
if (permissionBatch.isNotEmpty)
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
horizontal: 12,
|
|
||||||
vertical: 6,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: user.powerLevel >= 100
|
color: user.powerLevel >= 100
|
||||||
? theme.colorScheme.tertiary
|
? theme.colorScheme.tertiary
|
||||||
: theme.colorScheme.tertiaryContainer,
|
: theme.colorScheme.tertiaryContainer,
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||||
AppConfig.borderRadius,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
permissionBatch,
|
permissionBatch,
|
||||||
|
|
@ -66,8 +61,10 @@ class ParticipantListItem extends StatelessWidget {
|
||||||
membershipBatch == null
|
membershipBatch == null
|
||||||
? const SizedBox.shrink()
|
? const SizedBox.shrink()
|
||||||
: Container(
|
: Container(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
vertical: 4,
|
||||||
|
horizontal: 8,
|
||||||
|
),
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.secondaryContainer,
|
color: theme.colorScheme.secondaryContainer,
|
||||||
|
|
@ -84,11 +81,7 @@ class ParticipantListItem extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(user.id, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||||
user.id,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
leading: Opacity(
|
leading: Opacity(
|
||||||
opacity: user.membership == Membership.join ? 1 : 0.5,
|
opacity: user.membership == Membership.join ? 1 : 0.5,
|
||||||
child: Avatar(
|
child: Avatar(
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ class ChatEncryptionSettingsView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
body: MaxWidthBody(
|
body: MaxWidthBody(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
secondary: CircleAvatar(
|
secondary: CircleAvatar(
|
||||||
|
|
@ -76,16 +76,14 @@ class ChatEncryptionSettingsView extends StatelessWidget {
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
L10n.of(context).deviceKeys,
|
L10n.of(context).deviceKeys,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
StreamBuilder(
|
StreamBuilder(
|
||||||
stream: room.client.onRoomState.stream
|
stream: room.client.onRoomState.stream.where(
|
||||||
.where((update) => update.roomId == controller.room.id),
|
(update) => update.roomId == controller.room.id,
|
||||||
builder: (context, snapshot) =>
|
),
|
||||||
FutureBuilder<List<DeviceKeys>>(
|
builder: (context, snapshot) => FutureBuilder<List<DeviceKeys>>(
|
||||||
future: room.getUserDeviceKeys(),
|
future: room.getUserDeviceKeys(),
|
||||||
builder: (BuildContext context, snapshot) {
|
builder: (BuildContext context, snapshot) {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
|
|
@ -108,20 +106,21 @@ class ChatEncryptionSettingsView extends StatelessWidget {
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemCount: deviceKeys.length,
|
itemCount: deviceKeys.length,
|
||||||
itemBuilder: (BuildContext context, int i) => Column(
|
itemBuilder: (BuildContext context, int i) => Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
if (i == 0 ||
|
if (i == 0 ||
|
||||||
deviceKeys[i].userId !=
|
deviceKeys[i].userId !=
|
||||||
deviceKeys[i - 1].userId) ...[
|
deviceKeys[i - 1].userId) ...[
|
||||||
const Divider(),
|
const Divider(),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: room.client
|
future: room.client.getUserProfile(
|
||||||
.getUserProfile(deviceKeys[i].userId),
|
deviceKeys[i].userId,
|
||||||
|
),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final displayname =
|
final displayname =
|
||||||
snapshot.data?.displayname ??
|
snapshot.data?.displayname ??
|
||||||
deviceKeys[i].userId.localpart ??
|
deviceKeys[i].userId.localpart ??
|
||||||
deviceKeys[i].userId;
|
deviceKeys[i].userId;
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: Avatar(
|
leading: Avatar(
|
||||||
name: displayname,
|
name: displayname,
|
||||||
|
|
@ -146,14 +145,14 @@ class ChatEncryptionSettingsView extends StatelessWidget {
|
||||||
deviceKeys[i].verified
|
deviceKeys[i].verified
|
||||||
? L10n.of(context).verified
|
? L10n.of(context).verified
|
||||||
: deviceKeys[i].blocked
|
: deviceKeys[i].blocked
|
||||||
? L10n.of(context).blocked
|
? L10n.of(context).blocked
|
||||||
: L10n.of(context).unverified,
|
: L10n.of(context).unverified,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: deviceKeys[i].verified
|
color: deviceKeys[i].verified
|
||||||
? Colors.green
|
? Colors.green
|
||||||
: deviceKeys[i].blocked
|
: deviceKeys[i].blocked
|
||||||
? Colors.red
|
? Colors.red
|
||||||
: Colors.orange,
|
: Colors.orange,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Text(' | ID: '),
|
const Text(' | ID: '),
|
||||||
|
|
@ -185,9 +184,7 @@ class ChatEncryptionSettingsView extends StatelessWidget {
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
L10n.of(context).encryptionNotEnabled,
|
L10n.of(context).encryptionNotEnabled,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||||
fontStyle: FontStyle.italic,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -38,13 +38,7 @@ enum PopupMenuAction {
|
||||||
archive,
|
archive,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ActiveFilter {
|
enum ActiveFilter { allChats, messages, groups, unread, spaces }
|
||||||
allChats,
|
|
||||||
messages,
|
|
||||||
groups,
|
|
||||||
unread,
|
|
||||||
spaces,
|
|
||||||
}
|
|
||||||
|
|
||||||
extension LocalizedActiveFilter on ActiveFilter {
|
extension LocalizedActiveFilter on ActiveFilter {
|
||||||
String toLocalizedString(BuildContext context) {
|
String toLocalizedString(BuildContext context) {
|
||||||
|
|
@ -102,8 +96,8 @@ class ChatListController extends State<ChatList>
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearActiveSpace() => setState(() {
|
void clearActiveSpace() => setState(() {
|
||||||
_activeSpaceId = null;
|
_activeSpaceId = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
void onChatTap(Room room) async {
|
void onChatTap(Room room) async {
|
||||||
if (room.membership == Membership.invite) {
|
if (room.membership == Membership.invite) {
|
||||||
|
|
@ -124,9 +118,7 @@ class ChatListController extends State<ChatList>
|
||||||
|
|
||||||
if (room.membership == Membership.ban) {
|
if (room.membership == Membership.ban) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(content: Text(L10n.of(context).youHaveBeenBannedFromThisChat)),
|
||||||
content: Text(L10n.of(context).youHaveBeenBannedFromThisChat),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -159,11 +151,9 @@ class ChatListController extends State<ChatList>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Room> get filteredRooms => Matrix.of(context)
|
List<Room> get filteredRooms => Matrix.of(
|
||||||
.client
|
context,
|
||||||
.rooms
|
).client.rooms.where(getRoomFilterByActiveFilter(activeFilter)).toList();
|
||||||
.where(getRoomFilterByActiveFilter(activeFilter))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
bool isSearchMode = false;
|
bool isSearchMode = false;
|
||||||
Future<QueryPublicRoomsResponse>? publicRoomsResponse;
|
Future<QueryPublicRoomsResponse>? publicRoomsResponse;
|
||||||
|
|
@ -222,8 +212,9 @@ class ChatListController extends State<ChatList>
|
||||||
|
|
||||||
if (searchQuery.isValidMatrixId &&
|
if (searchQuery.isValidMatrixId &&
|
||||||
searchQuery.sigil == '#' &&
|
searchQuery.sigil == '#' &&
|
||||||
roomSearchResult.chunk
|
roomSearchResult.chunk.any(
|
||||||
.any((room) => room.canonicalAlias == searchQuery) ==
|
(room) => room.canonicalAlias == searchQuery,
|
||||||
|
) ==
|
||||||
false) {
|
false) {
|
||||||
final response = await client.getRoomIdByAlias(searchQuery);
|
final response = await client.getRoomIdByAlias(searchQuery);
|
||||||
final roomId = response.roomId;
|
final roomId = response.roomId;
|
||||||
|
|
@ -246,13 +237,9 @@ class ChatListController extends State<ChatList>
|
||||||
);
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().w('Searching has crashed', e, s);
|
Logs().w('Searching has crashed', e, s);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
||||||
e.toLocalizedString(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (!isSearchMode) return;
|
if (!isSearchMode) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -333,22 +320,17 @@ class ChatListController extends State<ChatList>
|
||||||
showScaffoldDialog(
|
showScaffoldDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => ShareScaffoldDialog(
|
builder: (context) => ShareScaffoldDialog(
|
||||||
items: files.map(
|
items: files.map((file) {
|
||||||
(file) {
|
if ({SharedMediaType.text, SharedMediaType.url}.contains(file.type)) {
|
||||||
if ({
|
return TextShareItem(file.path);
|
||||||
SharedMediaType.text,
|
}
|
||||||
SharedMediaType.url,
|
return FileShareItem(
|
||||||
}.contains(file.type)) {
|
XFile(
|
||||||
return TextShareItem(file.path);
|
file.path.replaceFirst('file://', ''),
|
||||||
}
|
mimeType: file.mimeType,
|
||||||
return FileShareItem(
|
),
|
||||||
XFile(
|
);
|
||||||
file.path.replaceFirst('file://', ''),
|
}).toList(),
|
||||||
mimeType: file.mimeType,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
).toList(),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -370,22 +352,23 @@ class ChatListController extends State<ChatList>
|
||||||
.listen(_processIncomingSharedMedia, onError: print);
|
.listen(_processIncomingSharedMedia, onError: print);
|
||||||
|
|
||||||
// For sharing images coming from outside the app while the app is closed
|
// For sharing images coming from outside the app while the app is closed
|
||||||
ReceiveSharingIntent.instance
|
ReceiveSharingIntent.instance.getInitialMedia().then(
|
||||||
.getInitialMedia()
|
_processIncomingSharedMedia,
|
||||||
.then(_processIncomingSharedMedia);
|
);
|
||||||
|
|
||||||
// For receiving shared Uris
|
// For receiving shared Uris
|
||||||
_intentUriStreamSubscription =
|
_intentUriStreamSubscription = AppLinks().uriLinkStream.listen(
|
||||||
AppLinks().uriLinkStream.listen(_processIncomingUris);
|
_processIncomingUris,
|
||||||
|
);
|
||||||
|
|
||||||
if (PlatformInfos.isAndroid) {
|
if (PlatformInfos.isAndroid) {
|
||||||
final shortcuts = FlutterShortcuts();
|
final shortcuts = FlutterShortcuts();
|
||||||
shortcuts.initialize().then(
|
shortcuts.initialize().then(
|
||||||
(_) => shortcuts.listenAction((action) {
|
(_) => shortcuts.listenAction((action) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
UrlLauncher(context, action).launchUrl();
|
UrlLauncher(context, action).launchUrl();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -402,8 +385,9 @@ class ChatListController extends State<ChatList>
|
||||||
_hackyWebRTCFixForWeb();
|
_hackyWebRTCFixForWeb();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
searchServer =
|
searchServer = Matrix.of(
|
||||||
Matrix.of(context).store.getString(_serverStoreNamespace);
|
context,
|
||||||
|
).store.getString(_serverStoreNamespace);
|
||||||
Matrix.of(context).backgroundPush?.setupPush();
|
Matrix.of(context).backgroundPush?.setupPush();
|
||||||
UpdateNotifier.showUpdateSnackBar(context);
|
UpdateNotifier.showUpdateSnackBar(context);
|
||||||
}
|
}
|
||||||
|
|
@ -447,8 +431,9 @@ class ChatListController extends State<ChatList>
|
||||||
Offset.zero & overlay.size,
|
Offset.zero & overlay.size,
|
||||||
);
|
);
|
||||||
|
|
||||||
final displayname =
|
final displayname = room.getLocalizedDisplayname(
|
||||||
room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)));
|
MatrixLocals(L10n.of(context)),
|
||||||
|
);
|
||||||
|
|
||||||
final spacesWithPowerLevels = room.client.rooms
|
final spacesWithPowerLevels = room.client.rooms
|
||||||
.where(
|
.where(
|
||||||
|
|
@ -466,19 +451,17 @@ class ChatListController extends State<ChatList>
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: ChatContextAction.open,
|
value: ChatContextAction.open,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
spacing: 12.0,
|
spacing: 12.0,
|
||||||
children: [
|
children: [
|
||||||
Avatar(
|
Avatar(mxContent: room.avatar, name: displayname),
|
||||||
mxContent: room.avatar,
|
|
||||||
name: displayname,
|
|
||||||
),
|
|
||||||
ConstrainedBox(
|
ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 128),
|
constraints: const BoxConstraints(maxWidth: 128),
|
||||||
child: Text(
|
child: Text(
|
||||||
displayname,
|
displayname,
|
||||||
style:
|
style: TextStyle(
|
||||||
TextStyle(color: Theme.of(context).colorScheme.onSurface),
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
|
@ -491,7 +474,7 @@ class ChatListController extends State<ChatList>
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: ChatContextAction.goToSpace,
|
value: ChatContextAction.goToSpace,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
Avatar(
|
Avatar(
|
||||||
mxContent: space.avatar,
|
mxContent: space.avatar,
|
||||||
|
|
@ -511,7 +494,7 @@ class ChatListController extends State<ChatList>
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: ChatContextAction.mute,
|
value: ChatContextAction.mute,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
room.pushRuleState == PushRuleState.notify
|
room.pushRuleState == PushRuleState.notify
|
||||||
|
|
@ -530,7 +513,7 @@ class ChatListController extends State<ChatList>
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: ChatContextAction.markUnread,
|
value: ChatContextAction.markUnread,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
room.markedUnread
|
room.markedUnread
|
||||||
|
|
@ -549,7 +532,7 @@ class ChatListController extends State<ChatList>
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: ChatContextAction.favorite,
|
value: ChatContextAction.favorite,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
room.isFavourite ? Icons.push_pin : Icons.push_pin_outlined,
|
room.isFavourite ? Icons.push_pin : Icons.push_pin_outlined,
|
||||||
|
|
@ -567,7 +550,7 @@ class ChatListController extends State<ChatList>
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: ChatContextAction.addToSpace,
|
value: ChatContextAction.addToSpace,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.group_work_outlined),
|
const Icon(Icons.group_work_outlined),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
@ -579,7 +562,7 @@ class ChatListController extends State<ChatList>
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: ChatContextAction.leave,
|
value: ChatContextAction.leave,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
Icons.delete_outlined,
|
Icons.delete_outlined,
|
||||||
|
|
@ -601,7 +584,7 @@ class ChatListController extends State<ChatList>
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: ChatContextAction.block,
|
value: ChatContextAction.block,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
Icons.block_outlined,
|
Icons.block_outlined,
|
||||||
|
|
@ -684,8 +667,9 @@ class ChatListController extends State<ChatList>
|
||||||
.map(
|
.map(
|
||||||
(space) => AdaptiveModalAction(
|
(space) => AdaptiveModalAction(
|
||||||
value: space,
|
value: space,
|
||||||
label: space
|
label: space.getLocalizedDisplayname(
|
||||||
.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
|
MatrixLocals(L10n.of(context)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
|
@ -746,8 +730,9 @@ class ChatListController extends State<ChatList>
|
||||||
await client.accountDataLoading;
|
await client.accountDataLoading;
|
||||||
await client.userDeviceKeysLoading;
|
await client.userDeviceKeysLoading;
|
||||||
if (client.prevBatch == null) {
|
if (client.prevBatch == null) {
|
||||||
await client.onSyncStatus.stream
|
await client.onSyncStatus.stream.firstWhere(
|
||||||
.firstWhere((status) => status.status == SyncStatus.finished);
|
(status) => status.status == SyncStatus.finished,
|
||||||
|
);
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -759,8 +744,9 @@ class ChatListController extends State<ChatList>
|
||||||
waitForFirstSync = true;
|
waitForFirstSync = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (client.userDeviceKeys[client.userID!]?.deviceKeys.values
|
if (client.userDeviceKeys[client.userID!]?.deviceKeys.values.any(
|
||||||
.any((device) => !device.verified && !device.blocked) ??
|
(device) => !device.verified && !device.blocked,
|
||||||
|
) ??
|
||||||
false) {
|
false) {
|
||||||
late final ScaffoldFeatureController controller;
|
late final ScaffoldFeatureController controller;
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
@ -772,9 +758,7 @@ class ChatListController extends State<ChatList>
|
||||||
closeIconColor: theme.colorScheme.onErrorContainer,
|
closeIconColor: theme.colorScheme.onErrorContainer,
|
||||||
content: Text(
|
content: Text(
|
||||||
L10n.of(context).oneOfYourDevicesIsNotVerified,
|
L10n.of(context).oneOfYourDevicesIsNotVerified,
|
||||||
style: TextStyle(
|
style: TextStyle(color: theme.colorScheme.onErrorContainer),
|
||||||
color: theme.colorScheme.onErrorContainer,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
action: SnackBarAction(
|
action: SnackBarAction(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|
@ -810,20 +794,21 @@ class ChatListController extends State<ChatList>
|
||||||
setState(() {
|
setState(() {
|
||||||
_activeSpaceId = null;
|
_activeSpaceId = null;
|
||||||
Matrix.of(context).activeBundle = bundle;
|
Matrix.of(context).activeBundle = bundle;
|
||||||
if (!Matrix.of(context)
|
if (!Matrix.of(
|
||||||
.currentBundle!
|
context,
|
||||||
.any((client) => client == Matrix.of(context).client)) {
|
).currentBundle!.any((client) => client == Matrix.of(context).client)) {
|
||||||
Matrix.of(context)
|
Matrix.of(
|
||||||
.setActiveClient(Matrix.of(context).currentBundle!.first);
|
context,
|
||||||
|
).setActiveClient(Matrix.of(context).currentBundle!.first);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void editBundlesForAccount(String? userId, String? activeBundle) async {
|
void editBundlesForAccount(String? userId, String? activeBundle) async {
|
||||||
final l10n = L10n.of(context);
|
final l10n = L10n.of(context);
|
||||||
final client = Matrix.of(context)
|
final client = Matrix.of(
|
||||||
.widget
|
context,
|
||||||
.clients[Matrix.of(context).getClientIndexByMatrixId(userId!)];
|
).widget.clients[Matrix.of(context).getClientIndexByMatrixId(userId!)];
|
||||||
final action = await showModalActionPopup<EditBundleAction>(
|
final action = await showModalActionPopup<EditBundleAction>(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).editBundlesForAccount,
|
title: L10n.of(context).editBundlesForAccount,
|
||||||
|
|
@ -868,10 +853,9 @@ class ChatListController extends State<ChatList>
|
||||||
|
|
||||||
String? get secureActiveBundle {
|
String? get secureActiveBundle {
|
||||||
if (Matrix.of(context).activeBundle == null ||
|
if (Matrix.of(context).activeBundle == null ||
|
||||||
!Matrix.of(context)
|
!Matrix.of(
|
||||||
.accountBundles
|
context,
|
||||||
.keys
|
).accountBundles.keys.contains(Matrix.of(context).activeBundle)) {
|
||||||
.contains(Matrix.of(context).activeBundle)) {
|
|
||||||
return Matrix.of(context).accountBundles.keys.first;
|
return Matrix.of(context).accountBundles.keys.first;
|
||||||
}
|
}
|
||||||
return Matrix.of(context).activeBundle;
|
return Matrix.of(context).activeBundle;
|
||||||
|
|
@ -897,11 +881,7 @@ class ChatListController extends State<ChatList>
|
||||||
|
|
||||||
enum EditBundleAction { addToBundle, removeFromBundle }
|
enum EditBundleAction { addToBundle, removeFromBundle }
|
||||||
|
|
||||||
enum InviteActions {
|
enum InviteActions { accept, decline, block }
|
||||||
accept,
|
|
||||||
decline,
|
|
||||||
block,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ChatContextAction {
|
enum ChatContextAction {
|
||||||
open,
|
open,
|
||||||
|
|
|
||||||
|
|
@ -59,9 +59,7 @@ class ChatListViewBody extends StatelessWidget {
|
||||||
const dummyChatCount = 4;
|
const dummyChatCount = 4;
|
||||||
final filter = controller.searchController.text.toLowerCase();
|
final filter = controller.searchController.text.toLowerCase();
|
||||||
return StreamBuilder(
|
return StreamBuilder(
|
||||||
key: ValueKey(
|
key: ValueKey(client.userID.toString()),
|
||||||
client.userID.toString(),
|
|
||||||
),
|
|
||||||
stream: client.onSync.stream
|
stream: client.onSync.stream
|
||||||
.where((s) => s.hasRoomUpdate)
|
.where((s) => s.hasRoomUpdate)
|
||||||
.rateLimit(const Duration(seconds: 1)),
|
.rateLimit(const Duration(seconds: 1)),
|
||||||
|
|
@ -74,151 +72,151 @@ class ChatListViewBody extends StatelessWidget {
|
||||||
slivers: [
|
slivers: [
|
||||||
ChatListHeader(controller: controller),
|
ChatListHeader(controller: controller),
|
||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildListDelegate(
|
delegate: SliverChildListDelegate([
|
||||||
[
|
if (controller.isSearchMode) ...[
|
||||||
if (controller.isSearchMode) ...[
|
SearchTitle(
|
||||||
SearchTitle(
|
title: L10n.of(context).publicRooms,
|
||||||
title: L10n.of(context).publicRooms,
|
icon: const Icon(Icons.explore_outlined),
|
||||||
icon: const Icon(Icons.explore_outlined),
|
),
|
||||||
),
|
PublicRoomsHorizontalList(publicRooms: publicRooms),
|
||||||
PublicRoomsHorizontalList(publicRooms: publicRooms),
|
SearchTitle(
|
||||||
SearchTitle(
|
title: L10n.of(context).publicSpaces,
|
||||||
title: L10n.of(context).publicSpaces,
|
icon: const Icon(Icons.workspaces_outlined),
|
||||||
icon: const Icon(Icons.workspaces_outlined),
|
),
|
||||||
),
|
PublicRoomsHorizontalList(publicRooms: publicSpaces),
|
||||||
PublicRoomsHorizontalList(publicRooms: publicSpaces),
|
SearchTitle(
|
||||||
SearchTitle(
|
title: L10n.of(context).users,
|
||||||
title: L10n.of(context).users,
|
icon: const Icon(Icons.group_outlined),
|
||||||
icon: const Icon(Icons.group_outlined),
|
),
|
||||||
),
|
AnimatedContainer(
|
||||||
AnimatedContainer(
|
clipBehavior: Clip.hardEdge,
|
||||||
clipBehavior: Clip.hardEdge,
|
decoration: const BoxDecoration(),
|
||||||
decoration: const BoxDecoration(),
|
height:
|
||||||
height: userSearchResult == null ||
|
userSearchResult == null ||
|
||||||
userSearchResult.results.isEmpty
|
userSearchResult.results.isEmpty
|
||||||
? 0
|
? 0
|
||||||
: 106,
|
: 106,
|
||||||
duration: FluffyThemes.animationDuration,
|
duration: FluffyThemes.animationDuration,
|
||||||
curve: FluffyThemes.animationCurve,
|
curve: FluffyThemes.animationCurve,
|
||||||
child: userSearchResult == null
|
child: userSearchResult == null
|
||||||
? null
|
? null
|
||||||
: ListView.builder(
|
: ListView.builder(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: userSearchResult.results.length,
|
itemCount: userSearchResult.results.length,
|
||||||
itemBuilder: (context, i) => _SearchItem(
|
itemBuilder: (context, i) => _SearchItem(
|
||||||
title:
|
title:
|
||||||
userSearchResult.results[i].displayName ??
|
userSearchResult.results[i].displayName ??
|
||||||
userSearchResult
|
userSearchResult
|
||||||
.results[i].userId.localpart ??
|
.results[i]
|
||||||
L10n.of(context).unknownDevice,
|
.userId
|
||||||
avatar: userSearchResult.results[i].avatarUrl,
|
.localpart ??
|
||||||
onPressed: () => UserDialog.show(
|
L10n.of(context).unknownDevice,
|
||||||
context: context,
|
avatar: userSearchResult.results[i].avatarUrl,
|
||||||
profile: userSearchResult.results[i],
|
onPressed: () => UserDialog.show(
|
||||||
),
|
context: context,
|
||||||
|
profile: userSearchResult.results[i],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
if (!controller.isSearchMode &&
|
|
||||||
AppSettings.showPresences.value)
|
|
||||||
GestureDetector(
|
|
||||||
onLongPress: () => controller.dismissStatusList(),
|
|
||||||
child: StatusMessageList(
|
|
||||||
onStatusEdit: controller.setStatus,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (client.rooms.isNotEmpty && !controller.isSearchMode)
|
|
||||||
SizedBox(
|
|
||||||
height: 64,
|
|
||||||
child: ListView(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 12.0,
|
|
||||||
vertical: 12.0,
|
|
||||||
),
|
|
||||||
shrinkWrap: true,
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
children: [
|
|
||||||
if (AppSettings.separateChatTypes.value)
|
|
||||||
ActiveFilter.messages
|
|
||||||
else
|
|
||||||
ActiveFilter.allChats,
|
|
||||||
ActiveFilter.groups,
|
|
||||||
ActiveFilter.unread,
|
|
||||||
if (spaceDelegateCandidates.isNotEmpty &&
|
|
||||||
!AppSettings.displayNavigationRail.value &&
|
|
||||||
!FluffyThemes.isColumnMode(context))
|
|
||||||
ActiveFilter.spaces,
|
|
||||||
]
|
|
||||||
.map(
|
|
||||||
(filter) => Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 4.0,
|
|
||||||
),
|
|
||||||
child: FilterChip(
|
|
||||||
selected: filter == controller.activeFilter,
|
|
||||||
onSelected: (_) =>
|
|
||||||
controller.setActiveFilter(filter),
|
|
||||||
label:
|
|
||||||
Text(filter.toLocalizedString(context)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (controller.isSearchMode)
|
|
||||||
SearchTitle(
|
|
||||||
title: L10n.of(context).chats,
|
|
||||||
icon: const Icon(Icons.forum_outlined),
|
|
||||||
),
|
|
||||||
if (client.prevBatch != null &&
|
|
||||||
rooms.isEmpty &&
|
|
||||||
!controller.isSearchMode) ...[
|
|
||||||
Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Stack(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
children: [
|
|
||||||
const Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
DummyChatListItem(
|
|
||||||
opacity: 0.5,
|
|
||||||
animate: false,
|
|
||||||
),
|
|
||||||
DummyChatListItem(
|
|
||||||
opacity: 0.3,
|
|
||||||
animate: false,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
CupertinoIcons.chat_bubble_text_fill,
|
|
||||||
size: 128,
|
|
||||||
color: theme.colorScheme.secondary,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Text(
|
|
||||||
client.rooms.isEmpty
|
|
||||||
? L10n.of(context).noChatsFoundHere
|
|
||||||
: L10n.of(context).noMoreChatsFound,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
color: theme.colorScheme.secondary,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
),
|
if (!controller.isSearchMode &&
|
||||||
|
AppSettings.showPresences.value)
|
||||||
|
GestureDetector(
|
||||||
|
onLongPress: () => controller.dismissStatusList(),
|
||||||
|
child: StatusMessageList(
|
||||||
|
onStatusEdit: controller.setStatus,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (client.rooms.isNotEmpty && !controller.isSearchMode)
|
||||||
|
SizedBox(
|
||||||
|
height: 64,
|
||||||
|
child: ListView(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12.0,
|
||||||
|
vertical: 12.0,
|
||||||
|
),
|
||||||
|
shrinkWrap: true,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
children:
|
||||||
|
[
|
||||||
|
if (AppSettings.separateChatTypes.value)
|
||||||
|
ActiveFilter.messages
|
||||||
|
else
|
||||||
|
ActiveFilter.allChats,
|
||||||
|
ActiveFilter.groups,
|
||||||
|
ActiveFilter.unread,
|
||||||
|
if (spaceDelegateCandidates.isNotEmpty &&
|
||||||
|
!AppSettings
|
||||||
|
.displayNavigationRail
|
||||||
|
.value &&
|
||||||
|
!FluffyThemes.isColumnMode(context))
|
||||||
|
ActiveFilter.spaces,
|
||||||
|
]
|
||||||
|
.map(
|
||||||
|
(filter) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 4.0,
|
||||||
|
),
|
||||||
|
child: FilterChip(
|
||||||
|
selected:
|
||||||
|
filter == controller.activeFilter,
|
||||||
|
onSelected: (_) =>
|
||||||
|
controller.setActiveFilter(filter),
|
||||||
|
label: Text(
|
||||||
|
filter.toLocalizedString(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (controller.isSearchMode)
|
||||||
|
SearchTitle(
|
||||||
|
title: L10n.of(context).chats,
|
||||||
|
icon: const Icon(Icons.forum_outlined),
|
||||||
|
),
|
||||||
|
if (client.prevBatch != null &&
|
||||||
|
rooms.isEmpty &&
|
||||||
|
!controller.isSearchMode) ...[
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: .center,
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
const Column(
|
||||||
|
mainAxisSize: .min,
|
||||||
|
children: [
|
||||||
|
DummyChatListItem(opacity: 0.5, animate: false),
|
||||||
|
DummyChatListItem(opacity: 0.3, animate: false),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
CupertinoIcons.chat_bubble_text_fill,
|
||||||
|
size: 128,
|
||||||
|
color: theme.colorScheme.secondary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Text(
|
||||||
|
client.rooms.isEmpty
|
||||||
|
? L10n.of(context).noChatsFoundHere
|
||||||
|
: L10n.of(context).noMoreChatsFound,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
color: theme.colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
if (client.prevBatch == null)
|
if (client.prevBatch == null)
|
||||||
SliverList(
|
SliverList(
|
||||||
|
|
@ -257,10 +255,7 @@ class ChatListViewBody extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PublicRoomsHorizontalList extends StatelessWidget {
|
class PublicRoomsHorizontalList extends StatelessWidget {
|
||||||
const PublicRoomsHorizontalList({
|
const PublicRoomsHorizontalList({super.key, required this.publicRooms});
|
||||||
super.key,
|
|
||||||
required this.publicRooms,
|
|
||||||
});
|
|
||||||
|
|
||||||
final List<PublishedRoomsChunk>? publicRooms;
|
final List<PublishedRoomsChunk>? publicRooms;
|
||||||
|
|
||||||
|
|
@ -279,7 +274,8 @@ class PublicRoomsHorizontalList extends StatelessWidget {
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: publicRooms.length,
|
itemCount: publicRooms.length,
|
||||||
itemBuilder: (context, i) => _SearchItem(
|
itemBuilder: (context, i) => _SearchItem(
|
||||||
title: publicRooms[i].name ??
|
title:
|
||||||
|
publicRooms[i].name ??
|
||||||
publicRooms[i].canonicalAlias?.localpart ??
|
publicRooms[i].canonicalAlias?.localpart ??
|
||||||
L10n.of(context).group,
|
L10n.of(context).group,
|
||||||
avatar: publicRooms[i].avatarUrl,
|
avatar: publicRooms[i].avatarUrl,
|
||||||
|
|
@ -310,31 +306,26 @@ class _SearchItem extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => InkWell(
|
Widget build(BuildContext context) => InkWell(
|
||||||
onTap: onPressed,
|
onTap: onPressed,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 84,
|
width: 84,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Avatar(
|
Avatar(mxContent: avatar, name: title),
|
||||||
mxContent: avatar,
|
Padding(
|
||||||
name: title,
|
padding: const EdgeInsets.all(8.0),
|
||||||
),
|
child: Text(
|
||||||
Padding(
|
title,
|
||||||
padding: const EdgeInsets.all(8.0),
|
maxLines: 2,
|
||||||
child: Text(
|
textAlign: TextAlign.center,
|
||||||
title,
|
overflow: TextOverflow.ellipsis,
|
||||||
maxLines: 2,
|
style: const TextStyle(fontSize: 12),
|
||||||
textAlign: TextAlign.center,
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
);
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,19 +34,19 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||||
title: StreamBuilder(
|
title: StreamBuilder(
|
||||||
stream: client.onSyncStatus.stream,
|
stream: client.onSyncStatus.stream,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final status = client.onSyncStatus.value ??
|
final status =
|
||||||
|
client.onSyncStatus.value ??
|
||||||
const SyncStatusUpdate(SyncStatus.waitingForResponse);
|
const SyncStatusUpdate(SyncStatus.waitingForResponse);
|
||||||
final hide = client.onSync.value != null &&
|
final hide =
|
||||||
|
client.onSync.value != null &&
|
||||||
status.status != SyncStatus.error &&
|
status.status != SyncStatus.error &&
|
||||||
client.prevBatch != null;
|
client.prevBatch != null;
|
||||||
return TextField(
|
return TextField(
|
||||||
controller: controller.searchController,
|
controller: controller.searchController,
|
||||||
focusNode: controller.searchFocusNode,
|
focusNode: controller.searchFocusNode,
|
||||||
textInputAction: TextInputAction.search,
|
textInputAction: TextInputAction.search,
|
||||||
onChanged: (text) => controller.onSearchEnter(
|
onChanged: (text) =>
|
||||||
text,
|
controller.onSearchEnter(text, globalSearch: globalSearch),
|
||||||
globalSearch: globalSearch,
|
|
||||||
),
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: theme.colorScheme.secondaryContainer,
|
fillColor: theme.colorScheme.secondaryContainer,
|
||||||
|
|
@ -66,19 +66,19 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||||
),
|
),
|
||||||
prefixIcon: hide
|
prefixIcon: hide
|
||||||
? controller.isSearchMode
|
? controller.isSearchMode
|
||||||
? IconButton(
|
? IconButton(
|
||||||
tooltip: L10n.of(context).cancel,
|
tooltip: L10n.of(context).cancel,
|
||||||
icon: const Icon(Icons.close_outlined),
|
icon: const Icon(Icons.close_outlined),
|
||||||
onPressed: controller.cancelSearch,
|
onPressed: controller.cancelSearch,
|
||||||
color: theme.colorScheme.onPrimaryContainer,
|
|
||||||
)
|
|
||||||
: IconButton(
|
|
||||||
onPressed: controller.startSearch,
|
|
||||||
icon: Icon(
|
|
||||||
Icons.search_outlined,
|
|
||||||
color: theme.colorScheme.onPrimaryContainer,
|
color: theme.colorScheme.onPrimaryContainer,
|
||||||
),
|
)
|
||||||
)
|
: IconButton(
|
||||||
|
onPressed: controller.startSearch,
|
||||||
|
icon: Icon(
|
||||||
|
Icons.search_outlined,
|
||||||
|
color: theme.colorScheme.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
)
|
||||||
: Container(
|
: Container(
|
||||||
margin: const EdgeInsets.all(12),
|
margin: const EdgeInsets.all(12),
|
||||||
width: 8,
|
width: 8,
|
||||||
|
|
@ -97,37 +97,34 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||||
),
|
),
|
||||||
suffixIcon: controller.isSearchMode && globalSearch
|
suffixIcon: controller.isSearchMode && globalSearch
|
||||||
? controller.isSearching
|
? controller.isSearching
|
||||||
? const Padding(
|
? const Padding(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
vertical: 10.0,
|
vertical: 10.0,
|
||||||
horizontal: 12,
|
horizontal: 12,
|
||||||
),
|
|
||||||
child: SizedBox.square(
|
|
||||||
dimension: 24,
|
|
||||||
child: CircularProgressIndicator.adaptive(
|
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
),
|
||||||
),
|
child: SizedBox.square(
|
||||||
)
|
dimension: 24,
|
||||||
: TextButton.icon(
|
child: CircularProgressIndicator.adaptive(
|
||||||
onPressed: controller.setServer,
|
strokeWidth: 2,
|
||||||
style: TextButton.styleFrom(
|
),
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(99),
|
|
||||||
),
|
),
|
||||||
textStyle: const TextStyle(fontSize: 12),
|
)
|
||||||
),
|
: TextButton.icon(
|
||||||
icon: const Icon(Icons.edit_outlined, size: 16),
|
onPressed: controller.setServer,
|
||||||
label: Text(
|
style: TextButton.styleFrom(
|
||||||
controller.searchServer ??
|
shape: RoundedRectangleBorder(
|
||||||
Matrix.of(context).client.homeserver!.host,
|
borderRadius: BorderRadius.circular(99),
|
||||||
maxLines: 2,
|
),
|
||||||
),
|
textStyle: const TextStyle(fontSize: 12),
|
||||||
)
|
),
|
||||||
: SizedBox(
|
icon: const Icon(Icons.edit_outlined, size: 16),
|
||||||
width: 0,
|
label: Text(
|
||||||
child: ClientChooserButton(controller),
|
controller.searchServer ??
|
||||||
),
|
Matrix.of(context).client.homeserver!.host,
|
||||||
|
maxLines: 2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SizedBox(width: 0, child: ClientChooserButton(controller)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,9 @@ class ChatListItem extends StatelessWidget {
|
||||||
final directChatMatrixId = room.directChatMatrixID;
|
final directChatMatrixId = room.directChatMatrixID;
|
||||||
final isDirectChat = directChatMatrixId != null;
|
final isDirectChat = directChatMatrixId != null;
|
||||||
final hasNotifications = room.notificationCount > 0;
|
final hasNotifications = room.notificationCount > 0;
|
||||||
final backgroundColor =
|
final backgroundColor = activeChat
|
||||||
activeChat ? theme.colorScheme.secondaryContainer : null;
|
? theme.colorScheme.secondaryContainer
|
||||||
|
: null;
|
||||||
final displayname = room.getLocalizedDisplayname(
|
final displayname = room.getLocalizedDisplayname(
|
||||||
MatrixLocals(L10n.of(context)),
|
MatrixLocals(L10n.of(context)),
|
||||||
);
|
);
|
||||||
|
|
@ -64,10 +65,7 @@ class ChatListItem extends StatelessWidget {
|
||||||
final space = this.space;
|
final space = this.space;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 1),
|
||||||
horizontal: 8,
|
|
||||||
vertical: 1,
|
|
||||||
),
|
|
||||||
child: Material(
|
child: Material(
|
||||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
|
|
@ -96,7 +94,8 @@ class ChatListItem extends StatelessWidget {
|
||||||
child: Avatar(
|
child: Avatar(
|
||||||
border: BorderSide(
|
border: BorderSide(
|
||||||
width: 2,
|
width: 2,
|
||||||
color: backgroundColor ??
|
color:
|
||||||
|
backgroundColor ??
|
||||||
theme.colorScheme.surface,
|
theme.colorScheme.surface,
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(
|
||||||
|
|
@ -114,14 +113,15 @@ class ChatListItem extends StatelessWidget {
|
||||||
child: Avatar(
|
child: Avatar(
|
||||||
border: space == null
|
border: space == null
|
||||||
? room.isSpace
|
? room.isSpace
|
||||||
? BorderSide(
|
? BorderSide(
|
||||||
width: 1,
|
width: 1,
|
||||||
color: theme.dividerColor,
|
color: theme.dividerColor,
|
||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
: BorderSide(
|
: BorderSide(
|
||||||
width: 2,
|
width: 2,
|
||||||
color: backgroundColor ??
|
color:
|
||||||
|
backgroundColor ??
|
||||||
theme.colorScheme.surface,
|
theme.colorScheme.surface,
|
||||||
),
|
),
|
||||||
borderRadius: room.isSpace
|
borderRadius: room.isSpace
|
||||||
|
|
@ -182,10 +182,7 @@ class ChatListItem extends StatelessWidget {
|
||||||
if (isMuted)
|
if (isMuted)
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.only(left: 4.0),
|
padding: EdgeInsets.only(left: 4.0),
|
||||||
child: Icon(
|
child: Icon(Icons.notifications_off_outlined, size: 16),
|
||||||
Icons.notifications_off_outlined,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (room.isFavourite)
|
if (room.isFavourite)
|
||||||
Padding(
|
Padding(
|
||||||
|
|
@ -202,8 +199,9 @@ class ChatListItem extends StatelessWidget {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 4.0),
|
padding: const EdgeInsets.only(left: 4.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
room.latestEventReceivedTime
|
room.latestEventReceivedTime.localizedTimeShort(
|
||||||
.localizedTimeShort(context),
|
context,
|
||||||
|
),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: theme.colorScheme.outline,
|
color: theme.colorScheme.outline,
|
||||||
|
|
@ -213,8 +211,8 @@ class ChatListItem extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
subtitle: Row(
|
subtitle: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: .start,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
if (typingText.isEmpty &&
|
if (typingText.isEmpty &&
|
||||||
ownMessage &&
|
ownMessage &&
|
||||||
|
|
@ -240,111 +238,111 @@ class ChatListItem extends StatelessWidget {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: room.lastEvent?.relationshipType ==
|
: room.lastEvent?.relationshipType ==
|
||||||
RelationshipTypes.thread
|
RelationshipTypes.thread
|
||||||
? Container(
|
? Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
|
color: theme.colorScheme.outline,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
AppConfig.borderRadius,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.only(right: 4.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: .min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.message_outlined,
|
||||||
|
size: 12,
|
||||||
|
color: theme.colorScheme.outline,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
L10n.of(context).thread,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
color: theme.colorScheme.outline,
|
color: theme.colorScheme.outline,
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
AppConfig.borderRadius,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
padding:
|
],
|
||||||
const EdgeInsets.symmetric(horizontal: 8.0),
|
),
|
||||||
margin: const EdgeInsets.only(right: 4.0),
|
)
|
||||||
child: Row(
|
: const SizedBox.shrink(),
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.message_outlined,
|
|
||||||
size: 12,
|
|
||||||
color: theme.colorScheme.outline,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(
|
|
||||||
L10n.of(context).thread,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: theme.colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: room.isSpace && room.membership == Membership.join
|
child: room.isSpace && room.membership == Membership.join
|
||||||
? Text(
|
? Text(
|
||||||
L10n.of(context)
|
L10n.of(
|
||||||
.countChats(room.spaceChildren.length),
|
context,
|
||||||
|
).countChats(room.spaceChildren.length),
|
||||||
style: TextStyle(color: theme.colorScheme.outline),
|
style: TextStyle(color: theme.colorScheme.outline),
|
||||||
)
|
)
|
||||||
: typingText.isNotEmpty
|
: typingText.isNotEmpty
|
||||||
? Text(
|
? Text(
|
||||||
typingText,
|
typingText,
|
||||||
style: TextStyle(
|
style: TextStyle(color: theme.colorScheme.primary),
|
||||||
color: theme.colorScheme.primary,
|
maxLines: 1,
|
||||||
),
|
softWrap: false,
|
||||||
maxLines: 1,
|
)
|
||||||
softWrap: false,
|
: FutureBuilder(
|
||||||
)
|
key: ValueKey(
|
||||||
: FutureBuilder(
|
'${lastEvent?.eventId}_${lastEvent?.type}_${lastEvent?.redacted}',
|
||||||
key: ValueKey(
|
),
|
||||||
'${lastEvent?.eventId}_${lastEvent?.type}_${lastEvent?.redacted}',
|
future: needLastEventSender
|
||||||
),
|
? lastEvent.calcLocalizedBody(
|
||||||
future: needLastEventSender
|
MatrixLocals(L10n.of(context)),
|
||||||
? lastEvent.calcLocalizedBody(
|
hideReply: true,
|
||||||
MatrixLocals(L10n.of(context)),
|
hideEdit: true,
|
||||||
hideReply: true,
|
plaintextBody: true,
|
||||||
hideEdit: true,
|
removeMarkdown: true,
|
||||||
plaintextBody: true,
|
withSenderNamePrefix:
|
||||||
removeMarkdown: true,
|
(!isDirectChat ||
|
||||||
withSenderNamePrefix: (!isDirectChat ||
|
directChatMatrixId !=
|
||||||
directChatMatrixId !=
|
room.lastEvent?.senderId),
|
||||||
room.lastEvent?.senderId),
|
)
|
||||||
)
|
: null,
|
||||||
|
initialData: lastEvent?.calcLocalizedBodyFallback(
|
||||||
|
MatrixLocals(L10n.of(context)),
|
||||||
|
hideReply: true,
|
||||||
|
hideEdit: true,
|
||||||
|
plaintextBody: true,
|
||||||
|
removeMarkdown: true,
|
||||||
|
withSenderNamePrefix:
|
||||||
|
(!isDirectChat ||
|
||||||
|
directChatMatrixId !=
|
||||||
|
room.lastEvent?.senderId),
|
||||||
|
),
|
||||||
|
builder: (context, snapshot) => Text(
|
||||||
|
room.membership == Membership.invite
|
||||||
|
? room
|
||||||
|
.getState(
|
||||||
|
EventTypes.RoomMember,
|
||||||
|
room.client.userID!,
|
||||||
|
)
|
||||||
|
?.content
|
||||||
|
.tryGet<String>('reason') ??
|
||||||
|
(isDirectChat
|
||||||
|
? L10n.of(context).newChatRequest
|
||||||
|
: L10n.of(context).inviteGroupChat)
|
||||||
|
: snapshot.data ??
|
||||||
|
L10n.of(context).noMessagesYet,
|
||||||
|
softWrap: false,
|
||||||
|
maxLines: room.notificationCount >= 1 ? 2 : 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: unread || room.hasNewMessages
|
||||||
|
? theme.colorScheme.onSurface
|
||||||
|
: theme.colorScheme.outline,
|
||||||
|
decoration: room.lastEvent?.redacted == true
|
||||||
|
? TextDecoration.lineThrough
|
||||||
: null,
|
: null,
|
||||||
initialData:
|
|
||||||
lastEvent?.calcLocalizedBodyFallback(
|
|
||||||
MatrixLocals(L10n.of(context)),
|
|
||||||
hideReply: true,
|
|
||||||
hideEdit: true,
|
|
||||||
plaintextBody: true,
|
|
||||||
removeMarkdown: true,
|
|
||||||
withSenderNamePrefix: (!isDirectChat ||
|
|
||||||
directChatMatrixId !=
|
|
||||||
room.lastEvent?.senderId),
|
|
||||||
),
|
|
||||||
builder: (context, snapshot) => Text(
|
|
||||||
room.membership == Membership.invite
|
|
||||||
? room
|
|
||||||
.getState(
|
|
||||||
EventTypes.RoomMember,
|
|
||||||
room.client.userID!,
|
|
||||||
)
|
|
||||||
?.content
|
|
||||||
.tryGet<String>('reason') ??
|
|
||||||
(isDirectChat
|
|
||||||
? L10n.of(context).newChatRequest
|
|
||||||
: L10n.of(context)
|
|
||||||
.inviteGroupChat)
|
|
||||||
: snapshot.data ??
|
|
||||||
L10n.of(context).noMessagesYet,
|
|
||||||
softWrap: false,
|
|
||||||
maxLines: room.notificationCount >= 1 ? 2 : 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: TextStyle(
|
|
||||||
color: unread || room.hasNewMessages
|
|
||||||
? theme.colorScheme.onSurface
|
|
||||||
: theme.colorScheme.outline,
|
|
||||||
decoration: room.lastEvent?.redacted == true
|
|
||||||
? TextDecoration.lineThrough
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
UnreadBubble(room: room),
|
UnreadBubble(room: room),
|
||||||
|
|
@ -353,27 +351,27 @@ class ChatListItem extends StatelessWidget {
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
trailing: onForget == null
|
trailing: onForget == null
|
||||||
? room.membership == Membership.invite
|
? room.membership == Membership.invite
|
||||||
? IconButton(
|
? IconButton(
|
||||||
tooltip: L10n.of(context).declineInvitation,
|
tooltip: L10n.of(context).declineInvitation,
|
||||||
icon: const Icon(Icons.delete_forever_outlined),
|
icon: const Icon(Icons.delete_forever_outlined),
|
||||||
color: theme.colorScheme.error,
|
color: theme.colorScheme.error,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final consent = await showOkCancelAlertDialog(
|
final consent = await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).declineInvitation,
|
title: L10n.of(context).declineInvitation,
|
||||||
message: L10n.of(context).areYouSure,
|
message: L10n.of(context).areYouSure,
|
||||||
okLabel: L10n.of(context).yes,
|
okLabel: L10n.of(context).yes,
|
||||||
isDestructive: true,
|
isDestructive: true,
|
||||||
);
|
);
|
||||||
if (consent != OkCancelResult.ok) return;
|
if (consent != OkCancelResult.ok) return;
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: room.leave,
|
future: room.leave,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
: IconButton(
|
: IconButton(
|
||||||
icon: const Icon(Icons.delete_outlined),
|
icon: const Icon(Icons.delete_outlined),
|
||||||
onPressed: onForget,
|
onPressed: onForget,
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,7 @@ class ChatListView extends StatelessWidget {
|
||||||
onGoToChats: controller.clearActiveSpace,
|
onGoToChats: controller.clearActiveSpace,
|
||||||
onGoToSpaceId: controller.setActiveSpace,
|
onGoToSpaceId: controller.setActiveSpace,
|
||||||
),
|
),
|
||||||
Container(
|
Container(color: Theme.of(context).dividerColor, width: 1),
|
||||||
color: Theme.of(context).dividerColor,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
Expanded(
|
Expanded(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
|
|
@ -50,8 +47,8 @@ class ChatListView extends StatelessWidget {
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: ChatListViewBody(controller),
|
body: ChatListViewBody(controller),
|
||||||
floatingActionButton: !controller.isSearchMode &&
|
floatingActionButton:
|
||||||
controller.activeSpaceId == null
|
!controller.isSearchMode && controller.activeSpaceId == null
|
||||||
? FloatingActionButton.extended(
|
? FloatingActionButton.extended(
|
||||||
onPressed: () => context.go('/rooms/newprivatechat'),
|
onPressed: () => context.go('/rooms/newprivatechat'),
|
||||||
icon: const Icon(Icons.add_outlined),
|
icon: const Icon(Icons.add_outlined),
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ class ClientChooserButton extends StatelessWidget {
|
||||||
(a, b) => a!.isValidMatrixId == b!.isValidMatrixId
|
(a, b) => a!.isValidMatrixId == b!.isValidMatrixId
|
||||||
? 0
|
? 0
|
||||||
: a.isValidMatrixId && !b.isValidMatrixId
|
: a.isValidMatrixId && !b.isValidMatrixId
|
||||||
? -1
|
? -1
|
||||||
: 1,
|
: 1,
|
||||||
);
|
);
|
||||||
return <PopupMenuEntry<Object>>[
|
return <PopupMenuEntry<Object>>[
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
|
|
@ -97,8 +97,8 @@ class ClientChooserButton extends StatelessWidget {
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: null,
|
value: null,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: .start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
bundle!,
|
bundle!,
|
||||||
|
|
@ -123,7 +123,8 @@ class ClientChooserButton extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Avatar(
|
Avatar(
|
||||||
mxContent: snapshot.data?.avatarUrl,
|
mxContent: snapshot.data?.avatarUrl,
|
||||||
name: snapshot.data?.displayName ??
|
name:
|
||||||
|
snapshot.data?.displayName ??
|
||||||
client.userID!.localpart,
|
client.userID!.localpart,
|
||||||
size: 32,
|
size: 32,
|
||||||
),
|
),
|
||||||
|
|
@ -193,10 +194,7 @@ class ClientChooserButton extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _clientSelected(
|
void _clientSelected(Object object, BuildContext context) async {
|
||||||
Object object,
|
|
||||||
BuildContext context,
|
|
||||||
) async {
|
|
||||||
if (object is Client) {
|
if (object is Client) {
|
||||||
controller.setActiveClient(object);
|
controller.setActiveClient(object);
|
||||||
} else if (object is String) {
|
} else if (object is String) {
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ class NaviRailItem extends StatelessWidget {
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
width: isSelected
|
width: isSelected
|
||||||
? FluffyThemes.isColumnMode(context)
|
? FluffyThemes.isColumnMode(context)
|
||||||
? 8
|
? 8
|
||||||
: 4
|
: 4
|
||||||
: 0,
|
: 0,
|
||||||
duration: FluffyThemes.animationDuration,
|
duration: FluffyThemes.animationDuration,
|
||||||
curve: FluffyThemes.animationCurve,
|
curve: FluffyThemes.animationCurve,
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,8 @@ class SearchTitle extends StatelessWidget {
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
shape: Border(
|
shape: Border(
|
||||||
top: BorderSide(
|
top: BorderSide(color: theme.dividerColor, width: 1),
|
||||||
color: theme.dividerColor,
|
bottom: BorderSide(color: theme.dividerColor, width: 1),
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: theme.dividerColor,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
color: color ?? theme.colorScheme.surface,
|
color: color ?? theme.colorScheme.surface,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
|
|
@ -38,10 +32,7 @@ class SearchTitle extends StatelessWidget {
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
horizontal: 16,
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
child: IconTheme(
|
child: IconTheme(
|
||||||
data: theme.iconTheme.copyWith(size: 16),
|
data: theme.iconTheme.copyWith(size: 16),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,7 @@ enum AddRoomType { chat, subspace }
|
||||||
|
|
||||||
enum SpaceChildAction { edit, moveToSpace, removeFromSpace }
|
enum SpaceChildAction { edit, moveToSpace, removeFromSpace }
|
||||||
|
|
||||||
enum SpaceActions {
|
enum SpaceActions { settings, invite, members, leave }
|
||||||
settings,
|
|
||||||
invite,
|
|
||||||
members,
|
|
||||||
leave,
|
|
||||||
}
|
|
||||||
|
|
||||||
class SpaceView extends StatefulWidget {
|
class SpaceView extends StatefulWidget {
|
||||||
final String spaceId;
|
final String spaceId;
|
||||||
|
|
@ -124,8 +119,9 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().w('Unable to load hierarchy', e, s);
|
Logs().w('Unable to load hierarchy', e, s);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(
|
||||||
.showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
context,
|
||||||
|
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
|
|
@ -141,9 +137,7 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
builder: (_) => PublicRoomDialog(
|
builder: (_) => PublicRoomDialog(
|
||||||
chunk: item,
|
chunk: item,
|
||||||
via: space?.spaceChildren
|
via: space?.spaceChildren
|
||||||
.firstWhereOrNull(
|
.firstWhereOrNull((child) => child.roomId == item.roomId)
|
||||||
(child) => child.roomId == item.roomId,
|
|
||||||
)
|
|
||||||
?.via,
|
?.via,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -224,8 +218,9 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
if (roomType == AddRoomType.subspace) {
|
if (roomType == AddRoomType.subspace) {
|
||||||
roomId = await client.createSpace(
|
roomId = await client.createSpace(
|
||||||
name: names,
|
name: names,
|
||||||
visibility:
|
visibility: isPublicSpace
|
||||||
isPublicSpace ? sdk.Visibility.public : sdk.Visibility.private,
|
? sdk.Visibility.public
|
||||||
|
: sdk.Visibility.private,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
roomId = await client.createGroupChat(
|
roomId = await client.createGroupChat(
|
||||||
|
|
@ -234,8 +229,9 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
preset: isPublicSpace
|
preset: isPublicSpace
|
||||||
? CreateRoomPreset.publicChat
|
? CreateRoomPreset.publicChat
|
||||||
: CreateRoomPreset.privateChat,
|
: CreateRoomPreset.privateChat,
|
||||||
visibility:
|
visibility: isPublicSpace
|
||||||
isPublicSpace ? sdk.Visibility.public : sdk.Visibility.private,
|
? sdk.Visibility.public
|
||||||
|
: sdk.Visibility.private,
|
||||||
initialState: isPublicSpace
|
initialState: isPublicSpace
|
||||||
? null
|
? null
|
||||||
: [
|
: [
|
||||||
|
|
@ -289,7 +285,7 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: SpaceChildAction.moveToSpace,
|
value: SpaceChildAction.moveToSpace,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.move_down_outlined),
|
const Icon(Icons.move_down_outlined),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
@ -300,7 +296,7 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: SpaceChildAction.edit,
|
value: SpaceChildAction.edit,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.edit_outlined),
|
const Icon(Icons.edit_outlined),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
@ -311,7 +307,7 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: SpaceChildAction.removeFromSpace,
|
value: SpaceChildAction.removeFromSpace,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.group_remove_outlined),
|
const Icon(Icons.group_remove_outlined),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
@ -344,8 +340,9 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
.map(
|
.map(
|
||||||
(space) => AdaptiveModalAction(
|
(space) => AdaptiveModalAction(
|
||||||
value: space,
|
value: space,
|
||||||
label: space
|
label: space.getLocalizedDisplayname(
|
||||||
.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
|
MatrixLocals(L10n.of(context)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
|
@ -392,19 +389,12 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
final displayname =
|
final displayname =
|
||||||
room?.getLocalizedDisplayname() ?? L10n.of(context).nothingFound;
|
room?.getLocalizedDisplayname() ?? L10n.of(context).nothingFound;
|
||||||
const avatarSize = Avatar.defaultSize / 1.5;
|
const avatarSize = Avatar.defaultSize / 1.5;
|
||||||
final isAdmin = room?.canChangeStateEvent(
|
final isAdmin = room?.canChangeStateEvent(EventTypes.SpaceChild) == true;
|
||||||
EventTypes.SpaceChild,
|
|
||||||
) ==
|
|
||||||
true;
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: FluffyThemes.isColumnMode(context)
|
leading: FluffyThemes.isColumnMode(context)
|
||||||
? null
|
? null
|
||||||
: Center(
|
: Center(child: CloseButton(onPressed: widget.onBack)),
|
||||||
child: CloseButton(
|
|
||||||
onPressed: widget.onBack,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
titleSpacing: FluffyThemes.isColumnMode(context) ? null : 0,
|
titleSpacing: FluffyThemes.isColumnMode(context) ? null : 0,
|
||||||
title: ListTile(
|
title: ListTile(
|
||||||
|
|
@ -432,7 +422,7 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: AddRoomType.chat,
|
value: AddRoomType.chat,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.group_add_outlined),
|
const Icon(Icons.group_add_outlined),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
@ -443,7 +433,7 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: AddRoomType.subspace,
|
value: AddRoomType.subspace,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.workspaces_outlined),
|
const Icon(Icons.workspaces_outlined),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
@ -460,7 +450,7 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: SpaceActions.settings,
|
value: SpaceActions.settings,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.settings_outlined),
|
const Icon(Icons.settings_outlined),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
@ -471,7 +461,7 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: SpaceActions.invite,
|
value: SpaceActions.invite,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.person_add_outlined),
|
const Icon(Icons.person_add_outlined),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
@ -482,7 +472,7 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: SpaceActions.members,
|
value: SpaceActions.members,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.group_outlined),
|
const Icon(Icons.group_outlined),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
@ -497,7 +487,7 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: SpaceActions.leave,
|
value: SpaceActions.leave,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.delete_outlined),
|
const Icon(Icons.delete_outlined),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
@ -510,12 +500,7 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: room == null
|
body: room == null
|
||||||
? const Center(
|
? const Center(child: Icon(Icons.search_outlined, size: 80))
|
||||||
child: Icon(
|
|
||||||
Icons.search_outlined,
|
|
||||||
size: 80,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: StreamBuilder(
|
: StreamBuilder(
|
||||||
stream: room.client.onSync.stream
|
stream: room.client.onSync.stream
|
||||||
.where((s) => s.hasRoomUpdate)
|
.where((s) => s.hasRoomUpdate)
|
||||||
|
|
@ -573,7 +558,8 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final item = _discoveredChildren[i];
|
final item = _discoveredChildren[i];
|
||||||
final displayname = item.name ??
|
final displayname =
|
||||||
|
item.name ??
|
||||||
item.canonicalAlias ??
|
item.canonicalAlias ??
|
||||||
L10n.of(context).emptyChat;
|
L10n.of(context).emptyChat;
|
||||||
if (!displayname.toLowerCase().contains(filter)) {
|
if (!displayname.toLowerCase().contains(filter)) {
|
||||||
|
|
@ -589,27 +575,31 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
vertical: 1,
|
vertical: 1,
|
||||||
),
|
),
|
||||||
child: Material(
|
child: Material(
|
||||||
borderRadius:
|
borderRadius: BorderRadius.circular(
|
||||||
BorderRadius.circular(AppConfig.borderRadius),
|
AppConfig.borderRadius,
|
||||||
|
),
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
color: joinedRoom != null &&
|
color:
|
||||||
|
joinedRoom != null &&
|
||||||
widget.activeChat == joinedRoom.id
|
widget.activeChat == joinedRoom.id
|
||||||
? theme.colorScheme.secondaryContainer
|
? theme.colorScheme.secondaryContainer
|
||||||
: Colors.transparent,
|
: Colors.transparent,
|
||||||
child: HoverBuilder(
|
child: HoverBuilder(
|
||||||
builder: (context, hovered) => ListTile(
|
builder: (context, hovered) => ListTile(
|
||||||
visualDensity:
|
visualDensity: const VisualDensity(
|
||||||
const VisualDensity(vertical: -0.5),
|
vertical: -0.5,
|
||||||
contentPadding:
|
),
|
||||||
const EdgeInsets.symmetric(horizontal: 8),
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
),
|
||||||
onTap: joinedRoom != null
|
onTap: joinedRoom != null
|
||||||
? () => widget.onChatTab(joinedRoom!)
|
? () => widget.onChatTab(joinedRoom!)
|
||||||
: () => _joinChildRoom(item),
|
: () => _joinChildRoom(item),
|
||||||
onLongPress: isAdmin
|
onLongPress: isAdmin
|
||||||
? () => _showSpaceChildEditMenu(
|
? () => _showSpaceChildEditMenu(
|
||||||
context,
|
context,
|
||||||
item.roomId,
|
item.roomId,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
leading: hovered && isAdmin
|
leading: hovered && isAdmin
|
||||||
? SizedBox.square(
|
? SizedBox.square(
|
||||||
|
|
@ -618,16 +608,18 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
splashRadius: avatarSize,
|
splashRadius: avatarSize,
|
||||||
iconSize: 14,
|
iconSize: 14,
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
foregroundColor: theme.colorScheme
|
foregroundColor: theme
|
||||||
|
.colorScheme
|
||||||
.onTertiaryContainer,
|
.onTertiaryContainer,
|
||||||
backgroundColor: theme
|
backgroundColor: theme
|
||||||
.colorScheme.tertiaryContainer,
|
.colorScheme
|
||||||
|
.tertiaryContainer,
|
||||||
),
|
),
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
_showSpaceChildEditMenu(
|
_showSpaceChildEditMenu(
|
||||||
context,
|
context,
|
||||||
item.roomId,
|
item.roomId,
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.edit_outlined),
|
icon: const Icon(Icons.edit_outlined),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -637,11 +629,13 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
name: '#',
|
name: '#',
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.colorScheme.surfaceContainer,
|
theme.colorScheme.surfaceContainer,
|
||||||
textColor: item.name?.darkColor ??
|
textColor:
|
||||||
|
item.name?.darkColor ??
|
||||||
theme.colorScheme.onSurface,
|
theme.colorScheme.onSurface,
|
||||||
border: item.roomType == 'm.space'
|
border: item.roomType == 'm.space'
|
||||||
? BorderSide(
|
? BorderSide(
|
||||||
color: theme.colorScheme
|
color: theme
|
||||||
|
.colorScheme
|
||||||
.surfaceContainerHighest,
|
.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,7 @@ import '../../widgets/adaptive_dialogs/user_dialog.dart';
|
||||||
class StatusMessageList extends StatelessWidget {
|
class StatusMessageList extends StatelessWidget {
|
||||||
final void Function() onStatusEdit;
|
final void Function() onStatusEdit;
|
||||||
|
|
||||||
const StatusMessageList({
|
const StatusMessageList({required this.onStatusEdit, super.key});
|
||||||
required this.onStatusEdit,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const double height = 116;
|
static const double height = 116;
|
||||||
|
|
||||||
|
|
@ -24,10 +21,7 @@ class StatusMessageList extends StatelessWidget {
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
if (profile.userId == client.userID) return onStatusEdit();
|
if (profile.userId == client.userID) return onStatusEdit();
|
||||||
|
|
||||||
UserDialog.show(
|
UserDialog.show(context: context, profile: profile);
|
||||||
context: context,
|
|
||||||
profile: profile,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,8 +50,9 @@ class StatusMessageList extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final presences =
|
final presences = snapshot.data
|
||||||
snapshot.data?.where(isInterestingPresence).toList();
|
?.where(isInterestingPresence)
|
||||||
|
.toList();
|
||||||
|
|
||||||
// If no other presences than the own entry is interesting, we
|
// If no other presences than the own entry is interesting, we
|
||||||
// hide the presence header.
|
// hide the presence header.
|
||||||
|
|
@ -121,7 +116,8 @@ class PresenceAvatar extends StatelessWidget {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
final profile = snapshot.data;
|
final profile = snapshot.data;
|
||||||
final displayName = profile?.displayName ??
|
final displayName =
|
||||||
|
profile?.displayName ??
|
||||||
presence.userid.localpart ??
|
presence.userid.localpart ??
|
||||||
presence.userid;
|
presence.userid;
|
||||||
final statusMsg = presence.statusMsg;
|
final statusMsg = presence.statusMsg;
|
||||||
|
|
@ -152,8 +148,9 @@ class PresenceAvatar extends StatelessWidget {
|
||||||
padding: const EdgeInsets.all(3),
|
padding: const EdgeInsets.all(3),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: presence.gradient,
|
gradient: presence.gradient,
|
||||||
borderRadius:
|
borderRadius: BorderRadius.circular(
|
||||||
BorderRadius.circular(avatarSize),
|
avatarSize,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|
@ -161,8 +158,9 @@ class PresenceAvatar extends StatelessWidget {
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surface,
|
color: theme.colorScheme.surface,
|
||||||
borderRadius:
|
borderRadius: BorderRadius.circular(
|
||||||
BorderRadius.circular(avatarSize),
|
avatarSize,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(3.0),
|
padding: const EdgeInsets.all(3.0),
|
||||||
child: Avatar(
|
child: Avatar(
|
||||||
|
|
@ -202,9 +200,8 @@ class PresenceAvatar extends StatelessWidget {
|
||||||
right: 8,
|
right: 8,
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 2,
|
spacing: 2,
|
||||||
crossAxisAlignment:
|
crossAxisAlignment: .start,
|
||||||
CrossAxisAlignment.start,
|
mainAxisSize: .min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
children: [
|
||||||
Material(
|
Material(
|
||||||
elevation: statusMsgBubbleElevation,
|
elevation: statusMsgBubbleElevation,
|
||||||
|
|
@ -230,8 +227,9 @@ class PresenceAvatar extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding: const EdgeInsets.only(
|
||||||
const EdgeInsets.only(left: 8.0),
|
left: 8.0,
|
||||||
|
),
|
||||||
child: Material(
|
child: Material(
|
||||||
color: statusMsgBubbleColor,
|
color: statusMsgBubbleColor,
|
||||||
elevation: statusMsgBubbleElevation,
|
elevation: statusMsgBubbleElevation,
|
||||||
|
|
@ -246,8 +244,9 @@ class PresenceAvatar extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding: const EdgeInsets.only(
|
||||||
const EdgeInsets.only(left: 13.0),
|
left: 13.0,
|
||||||
|
),
|
||||||
child: Material(
|
child: Material(
|
||||||
color: statusMsgBubbleColor,
|
color: statusMsgBubbleColor,
|
||||||
elevation: statusMsgBubbleElevation,
|
elevation: statusMsgBubbleElevation,
|
||||||
|
|
@ -280,9 +279,7 @@ class PresenceAvatar extends StatelessWidget {
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 11),
|
||||||
fontSize: 11,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -296,10 +293,12 @@ class PresenceAvatar extends StatelessWidget {
|
||||||
|
|
||||||
extension on Client {
|
extension on Client {
|
||||||
Set<String> get interestingPresences {
|
Set<String> get interestingPresences {
|
||||||
final allHeroes = rooms.map((room) => room.summary.mHeroes).fold(
|
final allHeroes = rooms
|
||||||
<String>{},
|
.map((room) => room.summary.mHeroes)
|
||||||
(previousValue, element) => previousValue..addAll(element ?? {}),
|
.fold(
|
||||||
);
|
<String>{},
|
||||||
|
(previousValue, element) => previousValue..addAll(element ?? {}),
|
||||||
|
);
|
||||||
allHeroes.add(userID!);
|
allHeroes.add(userID!);
|
||||||
return allHeroes;
|
return allHeroes;
|
||||||
}
|
}
|
||||||
|
|
@ -317,31 +316,23 @@ extension on CachedPresence {
|
||||||
|
|
||||||
LinearGradient get gradient => presence.isOnline == true
|
LinearGradient get gradient => presence.isOnline == true
|
||||||
? LinearGradient(
|
? LinearGradient(
|
||||||
colors: [
|
colors: [Colors.green, Colors.green.shade200, Colors.green.shade900],
|
||||||
Colors.green,
|
|
||||||
Colors.green.shade200,
|
|
||||||
Colors.green.shade900,
|
|
||||||
],
|
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
)
|
)
|
||||||
: presence.isUnavailable
|
: presence.isUnavailable
|
||||||
? LinearGradient(
|
? LinearGradient(
|
||||||
colors: [
|
colors: [
|
||||||
Colors.yellow,
|
Colors.yellow,
|
||||||
Colors.yellow.shade200,
|
Colors.yellow.shade200,
|
||||||
Colors.yellow.shade900,
|
Colors.yellow.shade900,
|
||||||
],
|
],
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
)
|
)
|
||||||
: LinearGradient(
|
: LinearGradient(
|
||||||
colors: [
|
colors: [Colors.grey, Colors.grey.shade200, Colors.grey.shade900],
|
||||||
Colors.grey,
|
begin: Alignment.topLeft,
|
||||||
Colors.grey.shade200,
|
end: Alignment.bottomRight,
|
||||||
Colors.grey.shade900,
|
);
|
||||||
],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ class UnreadBubble extends StatelessWidget {
|
||||||
final hasNotifications = room.notificationCount > 0;
|
final hasNotifications = room.notificationCount > 0;
|
||||||
final unreadBubbleSize = unread || room.hasNewMessages
|
final unreadBubbleSize = unread || room.hasNewMessages
|
||||||
? room.notificationCount > 0
|
? room.notificationCount > 0
|
||||||
? 20.0
|
? 20.0
|
||||||
: 14.0
|
: 14.0
|
||||||
: 0.0;
|
: 0.0;
|
||||||
return AnimatedContainer(
|
return AnimatedContainer(
|
||||||
duration: FluffyThemes.animationDuration,
|
duration: FluffyThemes.animationDuration,
|
||||||
|
|
@ -27,13 +27,13 @@ class UnreadBubble extends StatelessWidget {
|
||||||
width: !hasNotifications && !unread && !room.hasNewMessages
|
width: !hasNotifications && !unread && !room.hasNewMessages
|
||||||
? 0
|
? 0
|
||||||
: (unreadBubbleSize - 9) * room.notificationCount.toString().length +
|
: (unreadBubbleSize - 9) * room.notificationCount.toString().length +
|
||||||
9,
|
9,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: room.highlightCount > 0
|
color: room.highlightCount > 0
|
||||||
? theme.colorScheme.error
|
? theme.colorScheme.error
|
||||||
: hasNotifications || room.markedUnread
|
: hasNotifications || room.markedUnread
|
||||||
? theme.colorScheme.primary
|
? theme.colorScheme.primary
|
||||||
: theme.colorScheme.primaryContainer,
|
: theme.colorScheme.primaryContainer,
|
||||||
borderRadius: BorderRadius.circular(7),
|
borderRadius: BorderRadius.circular(7),
|
||||||
),
|
),
|
||||||
child: hasNotifications
|
child: hasNotifications
|
||||||
|
|
@ -43,8 +43,8 @@ class UnreadBubble extends StatelessWidget {
|
||||||
color: room.highlightCount > 0
|
color: room.highlightCount > 0
|
||||||
? theme.colorScheme.onError
|
? theme.colorScheme.onError
|
||||||
: hasNotifications
|
: hasNotifications
|
||||||
? theme.colorScheme.onPrimary
|
? theme.colorScheme.onPrimary
|
||||||
: theme.colorScheme.onPrimaryContainer,
|
: theme.colorScheme.onPrimaryContainer,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,7 @@ class ChatMembersController extends State<ChatMembersPage> {
|
||||||
void setFilter([dynamic _]) async {
|
void setFilter([dynamic _]) async {
|
||||||
final filter = filterController.text.toLowerCase().trim();
|
final filter = filterController.text.toLowerCase().trim();
|
||||||
|
|
||||||
final members = this
|
final members = this.members
|
||||||
.members
|
|
||||||
?.where((member) => member.membership == membershipFilter)
|
?.where((member) => member.membership == membershipFilter)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
|
@ -45,14 +44,15 @@ class ChatMembersController extends State<ChatMembersPage> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
filteredMembers = members
|
filteredMembers =
|
||||||
?.where(
|
members
|
||||||
(user) =>
|
?.where(
|
||||||
user.displayName?.toLowerCase().contains(filter) ??
|
(user) =>
|
||||||
user.id.toLowerCase().contains(filter),
|
user.displayName?.toLowerCase().contains(filter) ??
|
||||||
)
|
user.id.toLowerCase().contains(filter),
|
||||||
.toList()
|
)
|
||||||
?..sort((b, a) => a.powerLevel.compareTo(b.powerLevel));
|
.toList()
|
||||||
|
?..sort((b, a) => a.powerLevel.compareTo(b.powerLevel));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,8 +62,7 @@ class ChatMembersController extends State<ChatMembersPage> {
|
||||||
setState(() {
|
setState(() {
|
||||||
error = null;
|
error = null;
|
||||||
});
|
});
|
||||||
final participants = await Matrix.of(context)
|
final participants = await Matrix.of(context).client
|
||||||
.client
|
|
||||||
.getRoomById(widget.roomId)
|
.getRoomById(widget.roomId)
|
||||||
?.requestParticipants(
|
?.requestParticipants(
|
||||||
[...Membership.values]..remove(Membership.leave),
|
[...Membership.values]..remove(Membership.leave),
|
||||||
|
|
@ -76,8 +75,11 @@ class ChatMembersController extends State<ChatMembersPage> {
|
||||||
});
|
});
|
||||||
setFilter();
|
setFilter();
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs()
|
Logs().d(
|
||||||
.d('Unable to request participants. Try again in 3 seconds...', e, s);
|
'Unable to request participants. Try again in 3 seconds...',
|
||||||
|
e,
|
||||||
|
s,
|
||||||
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
error = e;
|
error = e;
|
||||||
});
|
});
|
||||||
|
|
@ -91,14 +93,12 @@ class ChatMembersController extends State<ChatMembersPage> {
|
||||||
super.initState();
|
super.initState();
|
||||||
refreshMembers();
|
refreshMembers();
|
||||||
|
|
||||||
_updateSub = Matrix.of(context)
|
_updateSub = Matrix.of(context).client.onSync.stream
|
||||||
.client
|
|
||||||
.onSync
|
|
||||||
.stream
|
|
||||||
.where(
|
.where(
|
||||||
(syncUpdate) =>
|
(syncUpdate) =>
|
||||||
syncUpdate.rooms?.join?[widget.roomId]?.timeline?.events
|
syncUpdate.rooms?.join?[widget.roomId]?.timeline?.events?.any(
|
||||||
?.any((state) => state.type == EventTypes.RoomMember) ??
|
(state) => state.type == EventTypes.RoomMember,
|
||||||
|
) ??
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.listen(refreshMembers);
|
.listen(refreshMembers);
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,12 @@ class ChatMembersView extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final room =
|
final room = Matrix.of(
|
||||||
Matrix.of(context).client.getRoomById(controller.widget.roomId);
|
context,
|
||||||
|
).client.getRoomById(controller.widget.roomId);
|
||||||
if (room == null) {
|
if (room == null) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(title: Text(L10n.of(context).oopsSomethingWentWrong)),
|
||||||
title: Text(L10n.of(context).oopsSomethingWentWrong),
|
|
||||||
),
|
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat),
|
child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat),
|
||||||
),
|
),
|
||||||
|
|
@ -32,7 +31,8 @@ class ChatMembersView extends StatelessWidget {
|
||||||
|
|
||||||
final members = controller.filteredMembers;
|
final members = controller.filteredMembers;
|
||||||
|
|
||||||
final roomCount = (room.summary.mJoinedMemberCount ?? 0) +
|
final roomCount =
|
||||||
|
(room.summary.mJoinedMemberCount ?? 0) +
|
||||||
(room.summary.mInvitedMemberCount ?? 0);
|
(room.summary.mInvitedMemberCount ?? 0);
|
||||||
|
|
||||||
final error = controller.error;
|
final error = controller.error;
|
||||||
|
|
@ -41,16 +41,12 @@ class ChatMembersView extends StatelessWidget {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: const Center(child: BackButton()),
|
leading: const Center(child: BackButton()),
|
||||||
title: Text(
|
title: Text(L10n.of(context).countParticipants(roomCount)),
|
||||||
L10n.of(context).countParticipants(roomCount),
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
if (room.canInvite)
|
if (room.canInvite)
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => context.go('/rooms/${room.id}/invite'),
|
onPressed: () => context.go('/rooms/${room.id}/invite'),
|
||||||
icon: const Icon(
|
icon: const Icon(Icons.person_add_outlined),
|
||||||
Icons.person_add_outlined,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -62,7 +58,7 @@ class ChatMembersView extends StatelessWidget {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.error_outline),
|
const Icon(Icons.error_outline),
|
||||||
Text(error.toLocalizedString(context)),
|
Text(error.toLocalizedString(context)),
|
||||||
|
|
@ -77,120 +73,117 @@ class ChatMembersView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: members == null
|
: members == null
|
||||||
? const Center(
|
? const Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(16.0),
|
padding: EdgeInsets.all(16.0),
|
||||||
child: CircularProgressIndicator.adaptive(),
|
child: CircularProgressIndicator.adaptive(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: ListView.builder(
|
: ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: members.length + 1,
|
itemCount: members.length + 1,
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
final availableFilters = Membership.values
|
final availableFilters = Membership.values
|
||||||
.where(
|
.where(
|
||||||
(membership) =>
|
(membership) =>
|
||||||
controller.members?.any(
|
controller.members?.any(
|
||||||
(member) => member.membership == membership,
|
(member) => member.membership == membership,
|
||||||
) ??
|
) ??
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
availableFilters
|
availableFilters.sort(
|
||||||
.sort((a, b) => a == Membership.join ? -1 : 1);
|
(a, b) => a == Membership.join ? -1 : 1,
|
||||||
return Column(
|
);
|
||||||
mainAxisSize: MainAxisSize.min,
|
return Column(
|
||||||
children: [
|
mainAxisSize: .min,
|
||||||
Padding(
|
children: [
|
||||||
padding: const EdgeInsets.all(16.0),
|
Padding(
|
||||||
child: TextField(
|
padding: const EdgeInsets.all(16.0),
|
||||||
controller: controller.filterController,
|
child: TextField(
|
||||||
onChanged: controller.setFilter,
|
controller: controller.filterController,
|
||||||
decoration: InputDecoration(
|
onChanged: controller.setFilter,
|
||||||
filled: true,
|
decoration: InputDecoration(
|
||||||
fillColor:
|
filled: true,
|
||||||
theme.colorScheme.secondaryContainer,
|
fillColor: theme.colorScheme.secondaryContainer,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
borderRadius: BorderRadius.circular(99),
|
borderRadius: BorderRadius.circular(99),
|
||||||
),
|
),
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
color: theme.colorScheme.onPrimaryContainer,
|
color: theme.colorScheme.onPrimaryContainer,
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
),
|
),
|
||||||
prefixIcon: const Icon(Icons.search_outlined),
|
prefixIcon: const Icon(Icons.search_outlined),
|
||||||
hintText: L10n.of(context).search,
|
hintText: L10n.of(context).search,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (availableFilters.length > 1)
|
||||||
|
SizedBox(
|
||||||
|
height: 64,
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12.0,
|
||||||
|
vertical: 12.0,
|
||||||
|
),
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: availableFilters.length,
|
||||||
|
itemBuilder: (context, i) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 4.0,
|
||||||
|
),
|
||||||
|
child: FilterChip(
|
||||||
|
label: Text(switch (availableFilters[i]) {
|
||||||
|
Membership.ban => L10n.of(context).banned,
|
||||||
|
Membership.invite =>
|
||||||
|
L10n.of(context).countInvited(
|
||||||
|
room.summary.mInvitedMemberCount ??
|
||||||
|
controller.members
|
||||||
|
?.where(
|
||||||
|
(member) =>
|
||||||
|
member.membership ==
|
||||||
|
Membership.invite,
|
||||||
|
)
|
||||||
|
.length ??
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
Membership.join =>
|
||||||
|
L10n.of(context).countParticipants(
|
||||||
|
room.summary.mJoinedMemberCount ??
|
||||||
|
controller.members
|
||||||
|
?.where(
|
||||||
|
(member) =>
|
||||||
|
member.membership ==
|
||||||
|
Membership.join,
|
||||||
|
)
|
||||||
|
.length ??
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
Membership.knock => L10n.of(
|
||||||
|
context,
|
||||||
|
).knocking,
|
||||||
|
Membership.leave => L10n.of(
|
||||||
|
context,
|
||||||
|
).leftTheChat,
|
||||||
|
}),
|
||||||
|
selected:
|
||||||
|
controller.membershipFilter ==
|
||||||
|
availableFilters[i],
|
||||||
|
onSelected: (_) => controller
|
||||||
|
.setMembershipFilter(availableFilters[i]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (availableFilters.length > 1)
|
),
|
||||||
SizedBox(
|
],
|
||||||
height: 64,
|
);
|
||||||
child: ListView.builder(
|
}
|
||||||
padding: const EdgeInsets.symmetric(
|
i--;
|
||||||
horizontal: 12.0,
|
return ParticipantListItem(members[i]);
|
||||||
vertical: 12.0,
|
},
|
||||||
),
|
),
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
itemCount: availableFilters.length,
|
|
||||||
itemBuilder: (context, i) => Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 4.0,
|
|
||||||
),
|
|
||||||
child: FilterChip(
|
|
||||||
label: Text(
|
|
||||||
switch (availableFilters[i]) {
|
|
||||||
Membership.ban =>
|
|
||||||
L10n.of(context).banned,
|
|
||||||
Membership.invite =>
|
|
||||||
L10n.of(context).countInvited(
|
|
||||||
room.summary
|
|
||||||
.mInvitedMemberCount ??
|
|
||||||
controller.members
|
|
||||||
?.where(
|
|
||||||
(member) =>
|
|
||||||
member.membership ==
|
|
||||||
Membership.invite,
|
|
||||||
)
|
|
||||||
.length ??
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
Membership.join =>
|
|
||||||
L10n.of(context).countParticipants(
|
|
||||||
room.summary.mJoinedMemberCount ??
|
|
||||||
controller.members
|
|
||||||
?.where(
|
|
||||||
(member) =>
|
|
||||||
member.membership ==
|
|
||||||
Membership.join,
|
|
||||||
)
|
|
||||||
.length ??
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
Membership.knock =>
|
|
||||||
L10n.of(context).knocking,
|
|
||||||
Membership.leave =>
|
|
||||||
L10n.of(context).leftTheChat,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
selected: controller.membershipFilter ==
|
|
||||||
availableFilters[i],
|
|
||||||
onSelected: (_) =>
|
|
||||||
controller.setMembershipFilter(
|
|
||||||
availableFilters[i],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
i--;
|
|
||||||
return ParticipantListItem(members[i]);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,9 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
|
||||||
}) async {
|
}) async {
|
||||||
final room = Matrix.of(context).client.getRoomById(roomId!)!;
|
final room = Matrix.of(context).client.getRoomById(roomId!)!;
|
||||||
if (!room.canSendEvent(EventTypes.RoomPowerLevels)) {
|
if (!room.canSendEvent(EventTypes.RoomPowerLevels)) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text(L10n.of(context).noPermission)),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text(L10n.of(context).noPermission)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
newLevel ??= await showPermissionChooser(
|
newLevel ??= await showPermissionChooser(
|
||||||
|
|
@ -64,12 +64,13 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream get onChanged => Matrix.of(context).client.onSync.stream.where(
|
Stream get onChanged => Matrix.of(context).client.onSync.stream.where(
|
||||||
(e) =>
|
(e) =>
|
||||||
(e.rooms?.join?.containsKey(roomId) ?? false) &&
|
(e.rooms?.join?.containsKey(roomId) ?? false) &&
|
||||||
(e.rooms!.join![roomId!]?.timeline?.events
|
(e.rooms!.join![roomId!]?.timeline?.events?.any(
|
||||||
?.any((s) => s.type == EventTypes.RoomPowerLevels) ??
|
(s) => s.type == EventTypes.RoomPowerLevels,
|
||||||
false),
|
) ??
|
||||||
);
|
false),
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => ChatPermissionsSettingsView(this);
|
Widget build(BuildContext context) => ChatPermissionsSettingsView(this);
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,7 @@ class ChatPermissionsSettingsView extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.info_outlined),
|
leading: const Icon(Icons.info_outlined),
|
||||||
subtitle: Text(
|
subtitle: Text(L10n.of(context).chatPermissionsDescription),
|
||||||
L10n.of(context).chatPermissionsDescription,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Divider(color: theme.dividerColor),
|
Divider(color: theme.dividerColor),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|
@ -60,7 +58,7 @@ class ChatPermissionsSettingsView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
for (final entry in powerLevels.entries)
|
for (final entry in powerLevels.entries)
|
||||||
PermissionsListTile(
|
PermissionsListTile(
|
||||||
|
|
@ -87,12 +85,14 @@ class ChatPermissionsSettingsView extends StatelessWidget {
|
||||||
Builder(
|
Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
const key = 'rooms';
|
const key = 'rooms';
|
||||||
final value = powerLevelsContent
|
final value =
|
||||||
.containsKey('notifications')
|
powerLevelsContent.containsKey('notifications')
|
||||||
? powerLevelsContent
|
? powerLevelsContent
|
||||||
.tryGetMap<String, Object?>('notifications')
|
.tryGetMap<String, Object?>(
|
||||||
?.tryGet<int>('rooms') ??
|
'notifications',
|
||||||
0
|
)
|
||||||
|
?.tryGet<int>('rooms') ??
|
||||||
|
0
|
||||||
: 0;
|
: 0;
|
||||||
return PermissionsListTile(
|
return PermissionsListTile(
|
||||||
permissionKey: key,
|
permissionKey: key,
|
||||||
|
|
|
||||||
|
|
@ -76,8 +76,8 @@ class PermissionsListTile extends StatelessWidget {
|
||||||
final color = permission >= 100
|
final color = permission >= 100
|
||||||
? Colors.orangeAccent
|
? Colors.orangeAccent
|
||||||
: permission >= 50
|
: permission >= 50
|
||||||
? Colors.blueAccent
|
? Colors.blueAccent
|
||||||
: Colors.greenAccent;
|
: Colors.greenAccent;
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
getLocalizedPowerLevelString(context),
|
getLocalizedPowerLevelString(context),
|
||||||
|
|
@ -110,14 +110,12 @@ class PermissionsListTile extends StatelessWidget {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
value: permission >= 100 ? permission : 100,
|
value: permission >= 100 ? permission : 100,
|
||||||
child: Text(
|
child: Text(
|
||||||
L10n.of(context)
|
L10n.of(
|
||||||
.adminLevel(permission >= 100 ? permission : 100),
|
context,
|
||||||
|
).adminLevel(permission >= 100 ? permission : 100),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(value: null, child: Text(L10n.of(context).custom)),
|
||||||
value: null,
|
|
||||||
child: Text(L10n.of(context).custom),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,8 @@ import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||||
class ChatSearchFilesTab extends StatelessWidget {
|
class ChatSearchFilesTab extends StatelessWidget {
|
||||||
final Room room;
|
final Room room;
|
||||||
final Stream<(List<Event>, String?)>? searchStream;
|
final Stream<(List<Event>, String?)>? searchStream;
|
||||||
final void Function({
|
final void Function({String? prevBatch, List<Event>? previousSearchResult})
|
||||||
String? prevBatch,
|
startSearch;
|
||||||
List<Event>? previousSearchResult,
|
|
||||||
}) startSearch;
|
|
||||||
|
|
||||||
const ChatSearchFilesTab({
|
const ChatSearchFilesTab({
|
||||||
required this.room,
|
required this.room,
|
||||||
|
|
@ -32,15 +30,13 @@ class ChatSearchFilesTab extends StatelessWidget {
|
||||||
final events = snapshot.data?.$1;
|
final events = snapshot.data?.$1;
|
||||||
if (searchStream == null || events == null) {
|
if (searchStream == null || events == null) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
children: [
|
children: [
|
||||||
const CircularProgressIndicator.adaptive(strokeWidth: 2),
|
const CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
L10n.of(context).searchIn(
|
L10n.of(context).searchIn(
|
||||||
room.getLocalizedDisplayname(
|
room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
|
||||||
MatrixLocals(L10n.of(context)),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -49,7 +45,7 @@ class ChatSearchFilesTab extends StatelessWidget {
|
||||||
|
|
||||||
if (events.isEmpty) {
|
if (events.isEmpty) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.file_present_outlined, size: 64),
|
const Icon(Icons.file_present_outlined, size: 64),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|
@ -68,9 +64,7 @@ class ChatSearchFilesTab extends StatelessWidget {
|
||||||
return const Padding(
|
return const Padding(
|
||||||
padding: EdgeInsets.all(16.0),
|
padding: EdgeInsets.all(16.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: CircularProgressIndicator.adaptive(
|
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -90,35 +84,35 @@ class ChatSearchFilesTab extends StatelessWidget {
|
||||||
prevBatch: nextBatch,
|
prevBatch: nextBatch,
|
||||||
previousSearchResult: events,
|
previousSearchResult: events,
|
||||||
),
|
),
|
||||||
icon: const Icon(
|
icon: const Icon(Icons.arrow_downward_outlined),
|
||||||
Icons.arrow_downward_outlined,
|
|
||||||
),
|
|
||||||
label: Text(L10n.of(context).searchMore),
|
label: Text(L10n.of(context).searchMore),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final event = events[i];
|
final event = events[i];
|
||||||
final filename = event.content.tryGet<String>('filename') ??
|
final filename =
|
||||||
|
event.content.tryGet<String>('filename') ??
|
||||||
event.content.tryGet<String>('body') ??
|
event.content.tryGet<String>('body') ??
|
||||||
L10n.of(context).unknownEvent('File');
|
L10n.of(context).unknownEvent('File');
|
||||||
final filetype = (filename.contains('.')
|
final filetype = (filename.contains('.')
|
||||||
? filename.split('.').last.toUpperCase()
|
? filename.split('.').last.toUpperCase()
|
||||||
: event.content
|
: event.content
|
||||||
.tryGetMap<String, dynamic>('info')
|
.tryGetMap<String, dynamic>('info')
|
||||||
?.tryGet<String>('mimetype')
|
?.tryGet<String>('mimetype')
|
||||||
?.toUpperCase() ??
|
?.toUpperCase() ??
|
||||||
'UNKNOWN');
|
'UNKNOWN');
|
||||||
final sizeString = event.sizeString;
|
final sizeString = event.sizeString;
|
||||||
final prevEvent = i > 0 ? events[i - 1] : null;
|
final prevEvent = i > 0 ? events[i - 1] : null;
|
||||||
final sameEnvironment = prevEvent == null
|
final sameEnvironment = prevEvent == null
|
||||||
? false
|
? false
|
||||||
: prevEvent.originServerTs
|
: prevEvent.originServerTs.sameEnvironment(
|
||||||
.sameEnvironment(event.originServerTs);
|
event.originServerTs,
|
||||||
|
);
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
if (!sameEnvironment) ...[
|
if (!sameEnvironment) ...[
|
||||||
Row(
|
Row(
|
||||||
|
|
@ -148,8 +142,9 @@ class ChatSearchFilesTab extends StatelessWidget {
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
],
|
],
|
||||||
Material(
|
Material(
|
||||||
borderRadius:
|
borderRadius: BorderRadius.circular(
|
||||||
BorderRadius.circular(AppConfig.borderRadius),
|
AppConfig.borderRadius,
|
||||||
|
),
|
||||||
color: theme.colorScheme.onInverseSurface,
|
color: theme.colorScheme.onInverseSurface,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,8 @@ import 'package:fluffychat/widgets/mxc_image.dart';
|
||||||
class ChatSearchImagesTab extends StatelessWidget {
|
class ChatSearchImagesTab extends StatelessWidget {
|
||||||
final Room room;
|
final Room room;
|
||||||
final Stream<(List<Event>, String?)>? searchStream;
|
final Stream<(List<Event>, String?)>? searchStream;
|
||||||
final void Function({
|
final void Function({String? prevBatch, List<Event>? previousSearchResult})
|
||||||
String? prevBatch,
|
startSearch;
|
||||||
List<Event>? previousSearchResult,
|
|
||||||
}) startSearch;
|
|
||||||
|
|
||||||
const ChatSearchImagesTab({
|
const ChatSearchImagesTab({
|
||||||
required this.room,
|
required this.room,
|
||||||
|
|
@ -35,15 +33,13 @@ class ChatSearchImagesTab extends StatelessWidget {
|
||||||
final events = snapshot.data?.$1;
|
final events = snapshot.data?.$1;
|
||||||
if (searchStream == null || events == null) {
|
if (searchStream == null || events == null) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
children: [
|
children: [
|
||||||
const CircularProgressIndicator.adaptive(strokeWidth: 2),
|
const CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
L10n.of(context).searchIn(
|
L10n.of(context).searchIn(
|
||||||
room.getLocalizedDisplayname(
|
room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
|
||||||
MatrixLocals(L10n.of(context)),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -51,7 +47,7 @@ class ChatSearchImagesTab extends StatelessWidget {
|
||||||
}
|
}
|
||||||
if (events.isEmpty) {
|
if (events.isEmpty) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.photo_outlined, size: 64),
|
const Icon(Icons.photo_outlined, size: 64),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|
@ -80,9 +76,7 @@ class ChatSearchImagesTab extends StatelessWidget {
|
||||||
return const Padding(
|
return const Padding(
|
||||||
padding: EdgeInsets.all(16.0),
|
padding: EdgeInsets.all(16.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: CircularProgressIndicator.adaptive(
|
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -102,9 +96,7 @@ class ChatSearchImagesTab extends StatelessWidget {
|
||||||
prevBatch: nextBatch,
|
prevBatch: nextBatch,
|
||||||
previousSearchResult: events,
|
previousSearchResult: events,
|
||||||
),
|
),
|
||||||
icon: const Icon(
|
icon: const Icon(Icons.arrow_downward_outlined),
|
||||||
Icons.arrow_downward_outlined,
|
|
||||||
),
|
|
||||||
label: Text(L10n.of(context).searchMore),
|
label: Text(L10n.of(context).searchMore),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -113,16 +105,13 @@ class ChatSearchImagesTab extends StatelessWidget {
|
||||||
|
|
||||||
final monthEvents = eventsByMonthList[i].value;
|
final monthEvents = eventsByMonthList[i].value;
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(height: 1, color: theme.dividerColor),
|
||||||
height: 1,
|
|
||||||
color: theme.dividerColor,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
|
@ -135,10 +124,7 @@ class ChatSearchImagesTab extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(height: 1, color: theme.dividerColor),
|
||||||
height: 1,
|
|
||||||
color: theme.dividerColor,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -150,39 +136,35 @@ class ChatSearchImagesTab extends StatelessWidget {
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
padding: const EdgeInsets.all(padding),
|
padding: const EdgeInsets.all(padding),
|
||||||
crossAxisCount: 3,
|
crossAxisCount: 3,
|
||||||
children: monthEvents.map(
|
children: monthEvents.map((event) {
|
||||||
(event) {
|
if (event.messageType == MessageTypes.Video) {
|
||||||
if (event.messageType == MessageTypes.Video) {
|
return Material(
|
||||||
return Material(
|
clipBehavior: Clip.hardEdge,
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
borderRadius: borderRadius,
|
|
||||||
child: EventVideoPlayer(event),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return InkWell(
|
|
||||||
onTap: () => showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => ImageViewer(
|
|
||||||
event,
|
|
||||||
outerContext: context,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
child: Material(
|
child: EventVideoPlayer(event),
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
borderRadius: borderRadius,
|
|
||||||
child: MxcImage(
|
|
||||||
event: event,
|
|
||||||
width: 128,
|
|
||||||
height: 128,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
animated: true,
|
|
||||||
isThumbnail: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
).toList(),
|
return InkWell(
|
||||||
|
onTap: () => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) =>
|
||||||
|
ImageViewer(event, outerContext: context),
|
||||||
|
),
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
child: Material(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
child: MxcImage(
|
||||||
|
event: event,
|
||||||
|
width: 128,
|
||||||
|
height: 128,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
animated: true,
|
||||||
|
isThumbnail: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,8 @@ class ChatSearchMessageTab extends StatelessWidget {
|
||||||
final String searchQuery;
|
final String searchQuery;
|
||||||
final Room room;
|
final Room room;
|
||||||
final Stream<(List<Event>, String?)>? searchStream;
|
final Stream<(List<Event>, String?)>? searchStream;
|
||||||
final void Function({
|
final void Function({String? prevBatch, List<Event>? previousSearchResult})
|
||||||
String? prevBatch,
|
startSearch;
|
||||||
List<Event>? previousSearchResult,
|
|
||||||
}) startSearch;
|
|
||||||
|
|
||||||
const ChatSearchMessageTab({
|
const ChatSearchMessageTab({
|
||||||
required this.searchQuery,
|
required this.searchQuery,
|
||||||
|
|
@ -36,15 +34,13 @@ class ChatSearchMessageTab extends StatelessWidget {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
if (searchStream == null) {
|
if (searchStream == null) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.search_outlined, size: 64),
|
const Icon(Icons.search_outlined, size: 64),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
L10n.of(context).searchIn(
|
L10n.of(context).searchIn(
|
||||||
room.getLocalizedDisplayname(
|
room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
|
||||||
MatrixLocals(L10n.of(context)),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -55,19 +51,15 @@ class ChatSearchMessageTab extends StatelessWidget {
|
||||||
return SelectionArea(
|
return SelectionArea(
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
itemCount: events.length + 1,
|
itemCount: events.length + 1,
|
||||||
separatorBuilder: (context, _) => Divider(
|
separatorBuilder: (context, _) =>
|
||||||
color: theme.dividerColor,
|
Divider(color: theme.dividerColor, height: 1),
|
||||||
height: 1,
|
|
||||||
),
|
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
if (i == events.length) {
|
if (i == events.length) {
|
||||||
if (snapshot.connectionState != ConnectionState.done) {
|
if (snapshot.connectionState != ConnectionState.done) {
|
||||||
return const Padding(
|
return const Padding(
|
||||||
padding: EdgeInsets.all(16.0),
|
padding: EdgeInsets.all(16.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: CircularProgressIndicator.adaptive(
|
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -87,9 +79,7 @@ class ChatSearchMessageTab extends StatelessWidget {
|
||||||
prevBatch: nextBatch,
|
prevBatch: nextBatch,
|
||||||
previousSearchResult: events,
|
previousSearchResult: events,
|
||||||
),
|
),
|
||||||
icon: const Icon(
|
icon: const Icon(Icons.arrow_downward_outlined),
|
||||||
Icons.arrow_downward_outlined,
|
|
||||||
),
|
|
||||||
label: Text(L10n.of(context).searchMore),
|
label: Text(L10n.of(context).searchMore),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -134,15 +124,9 @@ class _MessageSearchResultListTile extends StatelessWidget {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
Avatar(
|
Avatar(mxContent: sender.avatarUrl, name: displayname, size: 16),
|
||||||
mxContent: sender.avatarUrl,
|
|
||||||
name: displayname,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(displayname),
|
||||||
displayname,
|
|
||||||
),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
' | ${event.originServerTs.localizedTimeShort(context)}',
|
' | ${event.originServerTs.localizedTimeShort(context)}',
|
||||||
|
|
@ -164,23 +148,16 @@ class _MessageSearchResultListTile extends StatelessWidget {
|
||||||
.calcLocalizedBodyFallback(
|
.calcLocalizedBodyFallback(
|
||||||
plaintextBody: true,
|
plaintextBody: true,
|
||||||
removeMarkdown: true,
|
removeMarkdown: true,
|
||||||
MatrixLocals(
|
MatrixLocals(L10n.of(context)),
|
||||||
L10n.of(context),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.trim(),
|
.trim(),
|
||||||
maxLines: 7,
|
maxLines: 7,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: const Icon(
|
icon: const Icon(Icons.chevron_right_outlined),
|
||||||
Icons.chevron_right_outlined,
|
|
||||||
),
|
|
||||||
onPressed: () => context.go(
|
onPressed: () => context.go(
|
||||||
'/${Uri(
|
'/${Uri(pathSegments: ['rooms', room.id], queryParameters: {'event': event.eventId})}',
|
||||||
pathSegments: ['rooms', room.id],
|
|
||||||
queryParameters: {'event': event.eventId},
|
|
||||||
)}',
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,7 @@ class ChatSearchView extends StatelessWidget {
|
||||||
if (FluffyThemes.isThreeColumnMode(context))
|
if (FluffyThemes.isThreeColumnMode(context))
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
horizontal: 16,
|
|
||||||
),
|
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: controller.searchController,
|
controller: controller.searchController,
|
||||||
onSubmitted: (_) => controller.restartSearch(),
|
onSubmitted: (_) => controller.restartSearch(),
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,7 @@ class DevicesSettingsController extends State<DevicesSettings> {
|
||||||
.tryGetMap<String, Object?>('org.matrix.msc2965.authentication')
|
.tryGetMap<String, Object?>('org.matrix.msc2965.authentication')
|
||||||
?.tryGet<String>('account');
|
?.tryGet<String>('account');
|
||||||
if (accountManageUrl != null) {
|
if (accountManageUrl != null) {
|
||||||
launchUrlString(
|
launchUrlString(accountManageUrl, mode: LaunchMode.inAppBrowserView);
|
||||||
accountManageUrl,
|
|
||||||
mode: LaunchMode.inAppBrowserView,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (await showOkCancelAlertDialog(
|
if (await showOkCancelAlertDialog(
|
||||||
|
|
@ -86,10 +83,7 @@ class DevicesSettingsController extends State<DevicesSettings> {
|
||||||
context: context,
|
context: context,
|
||||||
delay: false,
|
delay: false,
|
||||||
future: () => matrix.client.uiaRequestBackground(
|
future: () => matrix.client.uiaRequestBackground(
|
||||||
(auth) => matrix.client.deleteDevices(
|
(auth) => matrix.client.deleteDevices(deviceIds, auth: auth),
|
||||||
deviceIds,
|
|
||||||
auth: auth,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
reload();
|
reload();
|
||||||
|
|
@ -106,9 +100,9 @@ class DevicesSettingsController extends State<DevicesSettings> {
|
||||||
if (displayName == null) return;
|
if (displayName == null) return;
|
||||||
final success = await showFutureLoadingDialog(
|
final success = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => Matrix.of(context)
|
future: () => Matrix.of(
|
||||||
.client
|
context,
|
||||||
.updateDevice(device.deviceId, displayName: displayName),
|
).client.updateDevice(device.deviceId, displayName: displayName),
|
||||||
);
|
);
|
||||||
if (success.error == null) {
|
if (success.error == null) {
|
||||||
reload();
|
reload();
|
||||||
|
|
@ -130,8 +124,10 @@ class DevicesSettingsController extends State<DevicesSettings> {
|
||||||
.deviceKeys[device.deviceId]!
|
.deviceKeys[device.deviceId]!
|
||||||
.startVerification();
|
.startVerification();
|
||||||
req.onUpdate = () {
|
req.onUpdate = () {
|
||||||
if ({KeyVerificationState.error, KeyVerificationState.done}
|
if ({
|
||||||
.contains(req.state)) {
|
KeyVerificationState.error,
|
||||||
|
KeyVerificationState.done,
|
||||||
|
}.contains(req.state)) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -162,9 +158,7 @@ class DevicesSettingsController extends State<DevicesSettings> {
|
||||||
bool _isOwnDevice(Device userDevice) =>
|
bool _isOwnDevice(Device userDevice) =>
|
||||||
userDevice.deviceId == Matrix.of(context).client.deviceID;
|
userDevice.deviceId == Matrix.of(context).client.deviceID;
|
||||||
|
|
||||||
Device? get thisDevice => devices!.firstWhereOrNull(
|
Device? get thisDevice => devices!.firstWhereOrNull(_isOwnDevice);
|
||||||
_isOwnDevice,
|
|
||||||
);
|
|
||||||
|
|
||||||
List<Device> get notThisDevice => List<Device>.from(devices!)
|
List<Device> get notThisDevice => List<Device>.from(devices!)
|
||||||
..removeWhere(_isOwnDevice)
|
..removeWhere(_isOwnDevice)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class DevicesSettingsView extends StatelessWidget {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
const Icon(Icons.error_outlined),
|
const Icon(Icons.error_outlined),
|
||||||
Text(snapshot.error.toString()),
|
Text(snapshot.error.toString()),
|
||||||
|
|
@ -47,7 +47,7 @@ class DevicesSettingsView extends StatelessWidget {
|
||||||
itemBuilder: (BuildContext context, int i) {
|
itemBuilder: (BuildContext context, int i) {
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
if (controller.chatBackupEnabled == false)
|
if (controller.chatBackupEnabled == false)
|
||||||
Padding(
|
Padding(
|
||||||
|
|
@ -57,8 +57,9 @@ class DevicesSettingsView extends StatelessWidget {
|
||||||
child: Icon(Icons.info_outlined),
|
child: Icon(Icons.info_outlined),
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
L10n.of(context)
|
L10n.of(
|
||||||
.noticeChatBackupDeviceVerification,
|
context,
|
||||||
|
).noticeChatBackupDeviceVerification,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,7 @@ import '../../utils/date_time_extension.dart';
|
||||||
import '../../utils/matrix_sdk_extensions/device_extension.dart';
|
import '../../utils/matrix_sdk_extensions/device_extension.dart';
|
||||||
import '../../widgets/matrix.dart';
|
import '../../widgets/matrix.dart';
|
||||||
|
|
||||||
enum UserDeviceListItemAction {
|
enum UserDeviceListItemAction { rename, remove, verify, block, unblock }
|
||||||
rename,
|
|
||||||
remove,
|
|
||||||
verify,
|
|
||||||
block,
|
|
||||||
unblock,
|
|
||||||
}
|
|
||||||
|
|
||||||
class UserDeviceListItem extends StatelessWidget {
|
class UserDeviceListItem extends StatelessWidget {
|
||||||
final Device userDevice;
|
final Device userDevice;
|
||||||
|
|
@ -38,7 +32,8 @@ class UserDeviceListItem extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
final keys = client.userDeviceKeys[Matrix.of(context).client.userID]
|
final keys = client
|
||||||
|
.userDeviceKeys[Matrix.of(context).client.userID]
|
||||||
?.deviceKeys[userDevice.deviceId];
|
?.deviceKeys[userDevice.deviceId];
|
||||||
final isOwnDevice = userDevice.deviceId == client.deviceID;
|
final isOwnDevice = userDevice.deviceId == client.deviceID;
|
||||||
|
|
||||||
|
|
@ -113,10 +108,10 @@ class UserDeviceListItem extends StatelessWidget {
|
||||||
backgroundColor: keys == null
|
backgroundColor: keys == null
|
||||||
? Colors.grey[700]
|
? Colors.grey[700]
|
||||||
: keys.blocked
|
: keys.blocked
|
||||||
? Colors.red
|
? Colors.red
|
||||||
: keys.verified
|
: keys.verified
|
||||||
? Colors.green
|
? Colors.green
|
||||||
: Colors.orange,
|
: Colors.orange,
|
||||||
child: Icon(userDevice.icon),
|
child: Icon(userDevice.icon),
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
|
|
@ -126,8 +121,9 @@ class UserDeviceListItem extends StatelessWidget {
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
L10n.of(context).lastActiveAgo(
|
L10n.of(context).lastActiveAgo(
|
||||||
DateTime.fromMillisecondsSinceEpoch(userDevice.lastSeenTs ?? 0)
|
DateTime.fromMillisecondsSinceEpoch(
|
||||||
.localizedTimeShort(context),
|
userDevice.lastSeenTs ?? 0,
|
||||||
|
).localizedTimeShort(context),
|
||||||
),
|
),
|
||||||
style: const TextStyle(fontWeight: FontWeight.w300),
|
style: const TextStyle(fontWeight: FontWeight.w300),
|
||||||
),
|
),
|
||||||
|
|
@ -137,14 +133,14 @@ class UserDeviceListItem extends StatelessWidget {
|
||||||
keys.blocked
|
keys.blocked
|
||||||
? L10n.of(context).blocked
|
? L10n.of(context).blocked
|
||||||
: keys.verified
|
: keys.verified
|
||||||
? L10n.of(context).verified
|
? L10n.of(context).verified
|
||||||
: L10n.of(context).unverified,
|
: L10n.of(context).unverified,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: keys.blocked
|
color: keys.blocked
|
||||||
? Colors.red
|
? Colors.red
|
||||||
: keys.verified
|
: keys.verified
|
||||||
? Colors.green
|
? Colors.green
|
||||||
: Colors.orange,
|
: Colors.orange,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,7 @@ class _StreamView extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(color: Colors.black54),
|
||||||
color: Colors.black54,
|
|
||||||
),
|
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
|
@ -133,9 +131,8 @@ class Calling extends StatefulWidget {
|
||||||
class MyCallingPage extends State<Calling> {
|
class MyCallingPage extends State<Calling> {
|
||||||
Room? get room => call.room;
|
Room? get room => call.room;
|
||||||
|
|
||||||
String get displayName => call.room.getLocalizedDisplayname(
|
String get displayName =>
|
||||||
MatrixLocals(L10n.of(widget.context)),
|
call.room.getLocalizedDisplayname(MatrixLocals(L10n.of(widget.context)));
|
||||||
);
|
|
||||||
|
|
||||||
String get callId => widget.callId;
|
String get callId => widget.callId;
|
||||||
|
|
||||||
|
|
@ -219,10 +216,7 @@ class MyCallingPage extends State<Calling> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanUp() {
|
void cleanUp() {
|
||||||
Timer(
|
Timer(const Duration(seconds: 2), () => widget.onClear?.call());
|
||||||
const Duration(seconds: 2),
|
|
||||||
() => widget.onClear?.call(),
|
|
||||||
);
|
|
||||||
if (call.type == CallType.kVideo) {
|
if (call.type == CallType.kVideo) {
|
||||||
try {
|
try {
|
||||||
unawaited(WakelockPlus.disable());
|
unawaited(WakelockPlus.disable());
|
||||||
|
|
@ -295,8 +289,9 @@ class MyCallingPage extends State<Calling> {
|
||||||
androidNotificationOptions: AndroidNotificationOptions(
|
androidNotificationOptions: AndroidNotificationOptions(
|
||||||
channelId: 'notification_channel_id',
|
channelId: 'notification_channel_id',
|
||||||
channelName: 'Foreground Notification',
|
channelName: 'Foreground Notification',
|
||||||
channelDescription:
|
channelDescription: L10n.of(
|
||||||
L10n.of(widget.context).foregroundServiceRunning,
|
widget.context,
|
||||||
|
).foregroundServiceRunning,
|
||||||
),
|
),
|
||||||
iosNotificationOptions: const IOSNotificationOptions(),
|
iosNotificationOptions: const IOSNotificationOptions(),
|
||||||
foregroundTaskOptions: ForegroundTaskOptions(
|
foregroundTaskOptions: ForegroundTaskOptions(
|
||||||
|
|
@ -434,9 +429,7 @@ class MyCallingPage extends State<Calling> {
|
||||||
hangupButton,
|
hangupButton,
|
||||||
];
|
];
|
||||||
case CallState.kEnded:
|
case CallState.kEnded:
|
||||||
return <Widget>[
|
return <Widget>[hangupButton];
|
||||||
hangupButton,
|
|
||||||
];
|
|
||||||
case CallState.kFledgling:
|
case CallState.kFledgling:
|
||||||
case CallState.kWaitLocalMedia:
|
case CallState.kWaitLocalMedia:
|
||||||
case CallState.kCreateOffer:
|
case CallState.kCreateOffer:
|
||||||
|
|
@ -458,28 +451,20 @@ class MyCallingPage extends State<Calling> {
|
||||||
if (call.localHold || call.remoteOnHold) {
|
if (call.localHold || call.remoteOnHold) {
|
||||||
var title = '';
|
var title = '';
|
||||||
if (call.localHold) {
|
if (call.localHold) {
|
||||||
title = '${call.room.getLocalizedDisplayname(
|
title =
|
||||||
MatrixLocals(L10n.of(widget.context)),
|
'${call.room.getLocalizedDisplayname(MatrixLocals(L10n.of(widget.context)))} held the call.';
|
||||||
)} held the call.';
|
|
||||||
} else if (call.remoteOnHold) {
|
} else if (call.remoteOnHold) {
|
||||||
title = 'You held the call.';
|
title = 'You held the call.';
|
||||||
}
|
}
|
||||||
stackWidgets.add(
|
stackWidgets.add(
|
||||||
Center(
|
Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(
|
const Icon(Icons.pause, size: 48.0, color: Colors.white),
|
||||||
Icons.pause,
|
|
||||||
size: 48.0,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: const TextStyle(
|
style: const TextStyle(color: Colors.white, fontSize: 24.0),
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 24.0,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -488,7 +473,8 @@ class MyCallingPage extends State<Calling> {
|
||||||
return stackWidgets;
|
return stackWidgets;
|
||||||
}
|
}
|
||||||
|
|
||||||
var primaryStream = call.remoteScreenSharingStream ??
|
var primaryStream =
|
||||||
|
call.remoteScreenSharingStream ??
|
||||||
call.localScreenSharingStream ??
|
call.localScreenSharingStream ??
|
||||||
call.remoteUserMediaStream ??
|
call.remoteUserMediaStream ??
|
||||||
call.localUserMediaStream;
|
call.localUserMediaStream;
|
||||||
|
|
@ -527,8 +513,10 @@ class MyCallingPage extends State<Calling> {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: _localVideoWidth,
|
width: _localVideoWidth,
|
||||||
height: _localVideoHeight,
|
height: _localVideoHeight,
|
||||||
child:
|
child: _StreamView(
|
||||||
_StreamView(remoteUserMediaStream!, matrixClient: widget.client),
|
remoteUserMediaStream!,
|
||||||
|
matrixClient: widget.client,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
secondaryStreamViews.add(const SizedBox(height: 10));
|
secondaryStreamViews.add(const SizedBox(height: 10));
|
||||||
|
|
@ -569,9 +557,7 @@ class MyCallingPage extends State<Calling> {
|
||||||
child: Container(
|
child: Container(
|
||||||
width: _localVideoWidth,
|
width: _localVideoWidth,
|
||||||
margin: _localVideoMargin,
|
margin: _localVideoMargin,
|
||||||
child: Column(
|
child: Column(children: secondaryStreamViews),
|
||||||
children: secondaryStreamViews,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -592,16 +578,14 @@ class MyCallingPage extends State<Calling> {
|
||||||
width: 320.0,
|
width: 320.0,
|
||||||
height: 150.0,
|
height: 150.0,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: .spaceAround,
|
||||||
children: _buildActionButtons(isFloating),
|
children: _buildActionButtons(isFloating),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: OrientationBuilder(
|
body: OrientationBuilder(
|
||||||
builder: (BuildContext context, Orientation orientation) {
|
builder: (BuildContext context, Orientation orientation) {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(color: Colors.black87),
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
..._buildContent(orientation, isFloating),
|
..._buildContent(orientation, isFloating),
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,7 @@ class PIPView extends StatefulWidget {
|
||||||
final double? floatingHeight;
|
final double? floatingHeight;
|
||||||
final bool avoidKeyboard;
|
final bool avoidKeyboard;
|
||||||
|
|
||||||
final Widget Function(
|
final Widget Function(BuildContext context, bool isFloating) builder;
|
||||||
BuildContext context,
|
|
||||||
bool isFloating,
|
|
||||||
) builder;
|
|
||||||
|
|
||||||
const PIPView({
|
const PIPView({
|
||||||
super.key,
|
super.key,
|
||||||
|
|
@ -95,10 +92,7 @@ class PIPViewState extends State<PIPView> with TickerProviderStateMixin {
|
||||||
void _onPanUpdate(DragUpdateDetails details) {
|
void _onPanUpdate(DragUpdateDetails details) {
|
||||||
if (!_isDragging) return;
|
if (!_isDragging) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_dragOffset = _dragOffset.translate(
|
_dragOffset = _dragOffset.translate(details.delta.dx, details.delta.dy);
|
||||||
details.delta.dx,
|
|
||||||
details.delta.dy,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,9 +176,7 @@ class PIPViewState extends State<PIPView> with TickerProviderStateMixin {
|
||||||
_dragAnimationController,
|
_dragAnimationController,
|
||||||
]),
|
]),
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
final animationCurve = CurveTween(
|
final animationCurve = CurveTween(curve: Curves.easeInOutQuad);
|
||||||
curve: Curves.easeInOutQuad,
|
|
||||||
);
|
|
||||||
final dragAnimationValue = animationCurve.transform(
|
final dragAnimationValue = animationCurve.transform(
|
||||||
_dragAnimationController.value,
|
_dragAnimationController.value,
|
||||||
);
|
);
|
||||||
|
|
@ -265,21 +257,13 @@ class PIPViewState extends State<PIPView> with TickerProviderStateMixin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PIPViewCorner {
|
enum PIPViewCorner { topLeft, topRight, bottomLeft, bottomRight }
|
||||||
topLeft,
|
|
||||||
topRight,
|
|
||||||
bottomLeft,
|
|
||||||
bottomRight,
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CornerDistance {
|
class _CornerDistance {
|
||||||
final PIPViewCorner corner;
|
final PIPViewCorner corner;
|
||||||
final double distance;
|
final double distance;
|
||||||
|
|
||||||
_CornerDistance({
|
_CornerDistance({required this.corner, required this.distance});
|
||||||
required this.corner,
|
|
||||||
required this.distance,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PIPViewCorner _calculateNearestCorner({
|
PIPViewCorner _calculateNearestCorner({
|
||||||
|
|
@ -288,15 +272,9 @@ PIPViewCorner _calculateNearestCorner({
|
||||||
}) {
|
}) {
|
||||||
_CornerDistance calculateDistance(PIPViewCorner corner) {
|
_CornerDistance calculateDistance(PIPViewCorner corner) {
|
||||||
final distance = offsets[corner]!
|
final distance = offsets[corner]!
|
||||||
.translate(
|
.translate(-offset.dx, -offset.dy)
|
||||||
-offset.dx,
|
|
||||||
-offset.dy,
|
|
||||||
)
|
|
||||||
.distanceSquared;
|
.distanceSquared;
|
||||||
return _CornerDistance(
|
return _CornerDistance(corner: corner, distance: distance);
|
||||||
corner: corner,
|
|
||||||
distance: distance,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final distances = PIPViewCorner.values.map(calculateDistance).toList();
|
final distances = PIPViewCorner.values.map(calculateDistance).toList();
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,10 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
||||||
/// well-known information and forwards to the login page depending on the
|
/// well-known information and forwards to the login page depending on the
|
||||||
/// login type.
|
/// login type.
|
||||||
Future<void> checkHomeserverAction({bool legacyPasswordLogin = false}) async {
|
Future<void> checkHomeserverAction({bool legacyPasswordLogin = false}) async {
|
||||||
final homeserverInput =
|
final homeserverInput = homeserverController.text
|
||||||
homeserverController.text.trim().toLowerCase().replaceAll(' ', '-');
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replaceAll(' ', '-');
|
||||||
|
|
||||||
if (homeserverInput.isEmpty) {
|
if (homeserverInput.isEmpty) {
|
||||||
final client = await Matrix.of(context).getLoginClient();
|
final client = await Matrix.of(context).getLoginClient();
|
||||||
|
|
@ -113,14 +115,12 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
||||||
|
|
||||||
void ssoLoginAction() async {
|
void ssoLoginAction() async {
|
||||||
final redirectUrl = kIsWeb
|
final redirectUrl = kIsWeb
|
||||||
? Uri.parse(html.window.location.href)
|
? Uri.parse(
|
||||||
.resolveUri(
|
html.window.location.href,
|
||||||
Uri(pathSegments: ['auth.html']),
|
).resolveUri(Uri(pathSegments: ['auth.html'])).toString()
|
||||||
)
|
|
||||||
.toString()
|
|
||||||
: isDefaultPlatform
|
: isDefaultPlatform
|
||||||
? '${AppConfig.appOpenUrlScheme.toLowerCase()}://login'
|
? '${AppConfig.appOpenUrlScheme.toLowerCase()}://login'
|
||||||
: 'http://localhost:3001//login';
|
: 'http://localhost:3001//login';
|
||||||
final client = await Matrix.of(context).getLoginClient();
|
final client = await Matrix.of(context).getLoginClient();
|
||||||
final url = client.homeserver!.replace(
|
final url = client.homeserver!.replace(
|
||||||
path: '/_matrix/client/v3/login/sso/redirect',
|
path: '/_matrix/client/v3/login/sso/redirect',
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,16 @@ import 'homeserver_picker.dart';
|
||||||
class HomeserverPickerView extends StatelessWidget {
|
class HomeserverPickerView extends StatelessWidget {
|
||||||
final HomeserverPickerController controller;
|
final HomeserverPickerController controller;
|
||||||
|
|
||||||
const HomeserverPickerView(
|
const HomeserverPickerView(this.controller, {super.key});
|
||||||
this.controller, {
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
return LoginScaffold(
|
return LoginScaffold(
|
||||||
enforceMobileMode:
|
enforceMobileMode: Matrix.of(
|
||||||
Matrix.of(context).widget.clients.any((client) => client.isLogged()),
|
context,
|
||||||
|
).widget.clients.any((client) => client.isLogged()),
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: Text(
|
title: Text(
|
||||||
|
|
@ -42,7 +40,7 @@ class HomeserverPickerView extends StatelessWidget {
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: MoreLoginActions.importBackup,
|
value: MoreLoginActions.importBackup,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.import_export_outlined),
|
const Icon(Icons.import_export_outlined),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
@ -53,7 +51,7 @@ class HomeserverPickerView extends StatelessWidget {
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: MoreLoginActions.privacy,
|
value: MoreLoginActions.privacy,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.privacy_tip_outlined),
|
const Icon(Icons.privacy_tip_outlined),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
@ -64,7 +62,7 @@ class HomeserverPickerView extends StatelessWidget {
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: MoreLoginActions.about,
|
value: MoreLoginActions.about,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.info_outlined),
|
const Icon(Icons.info_outlined),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
@ -100,8 +98,9 @@ class HomeserverPickerView extends StatelessWidget {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
||||||
child: SelectableLinkify(
|
child: SelectableLinkify(
|
||||||
text: L10n.of(context).appIntroduction,
|
text: L10n.of(context).appIntroduction,
|
||||||
textScaleFactor:
|
textScaleFactor: MediaQuery.textScalerOf(
|
||||||
MediaQuery.textScalerOf(context).scale(1),
|
context,
|
||||||
|
).scale(1),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: theme.colorScheme.secondary,
|
color: theme.colorScheme.secondary,
|
||||||
|
|
@ -114,8 +113,8 @@ class HomeserverPickerView extends StatelessWidget {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(32.0),
|
padding: const EdgeInsets.all(32.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: .stretch,
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
onSubmitted: (_) =>
|
onSubmitted: (_) =>
|
||||||
|
|
@ -147,11 +146,13 @@ class HomeserverPickerView extends StatelessWidget {
|
||||||
L10n.of(context).whatIsAHomeserver,
|
L10n.of(context).whatIsAHomeserver,
|
||||||
),
|
),
|
||||||
content: Linkify(
|
content: Linkify(
|
||||||
text: L10n.of(context)
|
text: L10n.of(
|
||||||
.homeserverDescription,
|
context,
|
||||||
|
).homeserverDescription,
|
||||||
textScaleFactor:
|
textScaleFactor:
|
||||||
MediaQuery.textScalerOf(context)
|
MediaQuery.textScalerOf(
|
||||||
.scale(1),
|
context,
|
||||||
|
).scale(1),
|
||||||
options: const LinkifyOptions(
|
options: const LinkifyOptions(
|
||||||
humanize: false,
|
humanize: false,
|
||||||
),
|
),
|
||||||
|
|
@ -169,8 +170,9 @@ class HomeserverPickerView extends StatelessWidget {
|
||||||
Uri.https('servers.joinmatrix.org'),
|
Uri.https('servers.joinmatrix.org'),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
L10n.of(context)
|
L10n.of(
|
||||||
.discoverHomeservers,
|
context,
|
||||||
|
).discoverHomeservers,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
AdaptiveDialogAction(
|
AdaptiveDialogAction(
|
||||||
|
|
@ -206,8 +208,8 @@ class HomeserverPickerView extends StatelessWidget {
|
||||||
onPressed: controller.isLoading
|
onPressed: controller.isLoading
|
||||||
? null
|
? null
|
||||||
: () => controller.checkHomeserverAction(
|
: () => controller.checkHomeserverAction(
|
||||||
legacyPasswordLogin: true,
|
legacyPasswordLogin: true,
|
||||||
),
|
),
|
||||||
child: Text(L10n.of(context).loginWithMatrixId),
|
child: Text(L10n.of(context).loginWithMatrixId),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,8 @@ class ImageViewerController extends State<ImageViewer> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
allEvents = widget.timeline?.events
|
allEvents =
|
||||||
|
widget.timeline?.events
|
||||||
.where(
|
.where(
|
||||||
(event) => {
|
(event) => {
|
||||||
MessageTypes.Image,
|
MessageTypes.Image,
|
||||||
|
|
@ -44,8 +45,9 @@ class ImageViewerController extends State<ImageViewer> {
|
||||||
.reversed
|
.reversed
|
||||||
.toList() ??
|
.toList() ??
|
||||||
[widget.event];
|
[widget.event];
|
||||||
var index =
|
var index = allEvents.indexWhere(
|
||||||
allEvents.indexWhere((event) => event.eventId == widget.event.eventId);
|
(event) => event.eventId == widget.event.eventId,
|
||||||
|
);
|
||||||
if (index < 0) index = 0;
|
if (index < 0) index = 0;
|
||||||
pageController = PageController(initialPage: index);
|
pageController = PageController(initialPage: index);
|
||||||
}
|
}
|
||||||
|
|
@ -93,11 +95,10 @@ class ImageViewerController extends State<ImageViewer> {
|
||||||
|
|
||||||
/// Forward this image to another room.
|
/// Forward this image to another room.
|
||||||
void forwardAction() => showScaffoldDialog(
|
void forwardAction() => showScaffoldDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => ShareScaffoldDialog(
|
builder: (context) =>
|
||||||
items: [ContentShareItem(currentEvent.content)],
|
ShareScaffoldDialog(items: [ContentShareItem(currentEvent.content)]),
|
||||||
),
|
);
|
||||||
);
|
|
||||||
|
|
||||||
/// Save this file with a system call.
|
/// Save this file with a system call.
|
||||||
void saveFileAction(BuildContext context) => currentEvent.saveFile(context);
|
void saveFileAction(BuildContext context) => currentEvent.saveFile(context);
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ class ImageViewerView extends StatelessWidget {
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
if (controller.canGoBack)
|
if (controller.canGoBack)
|
||||||
Padding(
|
Padding(
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,7 @@ import '../../widgets/mxc_image.dart';
|
||||||
class EventVideoPlayer extends StatefulWidget {
|
class EventVideoPlayer extends StatefulWidget {
|
||||||
final Event event;
|
final Event event;
|
||||||
|
|
||||||
const EventVideoPlayer(
|
const EventVideoPlayer(this.event, {super.key});
|
||||||
this.event, {
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EventVideoPlayerState createState() => EventVideoPlayerState();
|
EventVideoPlayerState createState() => EventVideoPlayerState();
|
||||||
|
|
@ -54,8 +51,9 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
|
||||||
: (progress) {
|
: (progress) {
|
||||||
final progressPercentage = progress / fileSize;
|
final progressPercentage = progress / fileSize;
|
||||||
setState(() {
|
setState(() {
|
||||||
_downloadProgress =
|
_downloadProgress = progressPercentage < 1
|
||||||
progressPercentage < 1 ? progressPercentage : null;
|
? progressPercentage
|
||||||
|
: null;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -100,11 +98,9 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} on IOException catch (e) {
|
} on IOException catch (e) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(e.toLocalizedString(context)),
|
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
ErrorReporter(context, 'Unable to play video').onErrorCallback(e, s);
|
ErrorReporter(context, 'Unable to play video').onErrorCallback(e, s);
|
||||||
}
|
}
|
||||||
|
|
@ -136,8 +132,10 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final hasThumbnail = widget.event.hasThumbnail;
|
final hasThumbnail = widget.event.hasThumbnail;
|
||||||
final blurHash = (widget.event.infoMap as Map<String, dynamic>)
|
final blurHash =
|
||||||
.tryGet<String>('xyz.amorgan.blurhash') ??
|
(widget.event.infoMap as Map<String, dynamic>).tryGet<String>(
|
||||||
|
'xyz.amorgan.blurhash',
|
||||||
|
) ??
|
||||||
fallbackBlurHash;
|
fallbackBlurHash;
|
||||||
final infoMap = widget.event.content.tryGetMap<String, Object?>('info');
|
final infoMap = widget.event.content.tryGetMap<String, Object?>('info');
|
||||||
final videoWidth = infoMap?.tryGet<int>('w') ?? 400;
|
final videoWidth = infoMap?.tryGet<int>('w') ?? 400;
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,7 @@ import '../../utils/localized_exception_extension.dart';
|
||||||
|
|
||||||
class InvitationSelection extends StatefulWidget {
|
class InvitationSelection extends StatefulWidget {
|
||||||
final String roomId;
|
final String roomId;
|
||||||
const InvitationSelection({
|
const InvitationSelection({super.key, required this.roomId});
|
||||||
super.key,
|
|
||||||
required this.roomId,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
InvitationSelectionController createState() =>
|
InvitationSelectionController createState() =>
|
||||||
|
|
@ -47,8 +44,8 @@ class InvitationSelectionController extends State<InvitationSelection> {
|
||||||
.toList();
|
.toList();
|
||||||
contacts.sort(
|
contacts.sort(
|
||||||
(a, b) => a.calcDisplayname().toLowerCase().compareTo(
|
(a, b) => a.calcDisplayname().toLowerCase().compareTo(
|
||||||
b.calcDisplayname().toLowerCase(),
|
b.calcDisplayname().toLowerCase(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return contacts;
|
return contacts;
|
||||||
}
|
}
|
||||||
|
|
@ -91,9 +88,9 @@ class InvitationSelectionController extends State<InvitationSelection> {
|
||||||
try {
|
try {
|
||||||
response = await matrix.client.searchUserDirectory(text, limit: 10);
|
response = await matrix.client.searchUserDirectory(text, limit: 10);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text((e).toLocalizedString(context))),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text((e).toLocalizedString(context))));
|
||||||
return;
|
return;
|
||||||
} finally {
|
} finally {
|
||||||
setState(() => loading = false);
|
setState(() => loading = false);
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,12 @@ class InvitationSelectionView extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final room =
|
final room = Matrix.of(
|
||||||
Matrix.of(context).client.getRoomById(controller.widget.roomId);
|
context,
|
||||||
|
).client.getRoomById(controller.widget.roomId);
|
||||||
if (room == null) {
|
if (room == null) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(title: Text(L10n.of(context).oopsSomethingWentWrong)),
|
||||||
title: Text(L10n.of(context).oopsSomethingWentWrong),
|
|
||||||
),
|
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat),
|
child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat),
|
||||||
),
|
),
|
||||||
|
|
@ -76,11 +75,14 @@ class InvitationSelectionView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
StreamBuilder<Object>(
|
StreamBuilder<Object>(
|
||||||
stream: room.client.onRoomState.stream
|
stream: room.client.onRoomState.stream.where(
|
||||||
.where((update) => update.roomId == room.id),
|
(update) => update.roomId == room.id,
|
||||||
|
),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final participants =
|
final participants = room
|
||||||
room.getParticipants().map((user) => user.id).toSet();
|
.getParticipants()
|
||||||
|
.map((user) => user.id)
|
||||||
|
.toSet();
|
||||||
return controller.foundProfiles.isNotEmpty
|
return controller.foundProfiles.isNotEmpty
|
||||||
? ListView.builder(
|
? ListView.builder(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
|
@ -88,17 +90,21 @@ class InvitationSelectionView extends StatelessWidget {
|
||||||
itemCount: controller.foundProfiles.length,
|
itemCount: controller.foundProfiles.length,
|
||||||
itemBuilder: (BuildContext context, int i) =>
|
itemBuilder: (BuildContext context, int i) =>
|
||||||
_InviteContactListTile(
|
_InviteContactListTile(
|
||||||
profile: controller.foundProfiles[i],
|
profile: controller.foundProfiles[i],
|
||||||
isMember: participants
|
isMember: participants.contains(
|
||||||
.contains(controller.foundProfiles[i].userId),
|
controller.foundProfiles[i].userId,
|
||||||
onTap: () => controller.inviteAction(
|
),
|
||||||
context,
|
onTap: () => controller.inviteAction(
|
||||||
controller.foundProfiles[i].userId,
|
context,
|
||||||
controller.foundProfiles[i].displayName ??
|
controller.foundProfiles[i].userId,
|
||||||
controller.foundProfiles[i].userId.localpart ??
|
controller.foundProfiles[i].displayName ??
|
||||||
L10n.of(context).user,
|
controller
|
||||||
),
|
.foundProfiles[i]
|
||||||
),
|
.userId
|
||||||
|
.localpart ??
|
||||||
|
L10n.of(context).user,
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: FutureBuilder<List<User>>(
|
: FutureBuilder<List<User>>(
|
||||||
future: controller.getContacts(context),
|
future: controller.getContacts(context),
|
||||||
|
|
@ -117,23 +123,26 @@ class InvitationSelectionView extends StatelessWidget {
|
||||||
itemCount: contacts.length,
|
itemCount: contacts.length,
|
||||||
itemBuilder: (BuildContext context, int i) =>
|
itemBuilder: (BuildContext context, int i) =>
|
||||||
_InviteContactListTile(
|
_InviteContactListTile(
|
||||||
user: contacts[i],
|
user: contacts[i],
|
||||||
profile: Profile(
|
profile: Profile(
|
||||||
avatarUrl: contacts[i].avatarUrl,
|
avatarUrl: contacts[i].avatarUrl,
|
||||||
displayName: contacts[i].displayName ??
|
displayName:
|
||||||
contacts[i].id.localpart ??
|
contacts[i].displayName ??
|
||||||
L10n.of(context).user,
|
contacts[i].id.localpart ??
|
||||||
userId: contacts[i].id,
|
L10n.of(context).user,
|
||||||
),
|
userId: contacts[i].id,
|
||||||
isMember: participants.contains(contacts[i].id),
|
),
|
||||||
onTap: () => controller.inviteAction(
|
isMember: participants.contains(
|
||||||
context,
|
contacts[i].id,
|
||||||
contacts[i].id,
|
),
|
||||||
contacts[i].displayName ??
|
onTap: () => controller.inviteAction(
|
||||||
contacts[i].id.localpart ??
|
context,
|
||||||
L10n.of(context).user,
|
contacts[i].id,
|
||||||
),
|
contacts[i].displayName ??
|
||||||
),
|
contacts[i].id.localpart ??
|
||||||
|
L10n.of(context).user,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -169,10 +178,7 @@ class _InviteContactListTile extends StatelessWidget {
|
||||||
mxContent: profile.avatarUrl,
|
mxContent: profile.avatarUrl,
|
||||||
name: profile.displayName,
|
name: profile.displayName,
|
||||||
presenceUserId: profile.userId,
|
presenceUserId: profile.userId,
|
||||||
onTap: () => UserDialog.show(
|
onTap: () => UserDialog.show(context: context, profile: profile),
|
||||||
context: context,
|
|
||||||
profile: profile,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
profile.displayName ?? profile.userId.localpart ?? l10n.user,
|
profile.displayName ?? profile.userId.localpart ?? l10n.user,
|
||||||
|
|
@ -183,9 +189,7 @@ class _InviteContactListTile extends StatelessWidget {
|
||||||
profile.userId,
|
profile.userId,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(color: theme.colorScheme.secondary),
|
||||||
color: theme.colorScheme.secondary,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
trailing: TextButton.icon(
|
trailing: TextButton.icon(
|
||||||
onPressed: isMember ? null : onTap,
|
onPressed: isMember ? null : onTap,
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,14 @@ import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||||
|
|
||||||
class KeyVerificationDialog extends StatefulWidget {
|
class KeyVerificationDialog extends StatefulWidget {
|
||||||
Future<bool?> show(BuildContext context) => showAdaptiveDialog<bool>(
|
Future<bool?> show(BuildContext context) => showAdaptiveDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => this,
|
builder: (context) => this,
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
final KeyVerification request;
|
final KeyVerification request;
|
||||||
|
|
||||||
const KeyVerificationDialog({
|
const KeyVerificationDialog({super.key, required this.request});
|
||||||
super.key,
|
|
||||||
required this.request,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
KeyVerificationPageState createState() => KeyVerificationPageState();
|
KeyVerificationPageState createState() => KeyVerificationPageState();
|
||||||
|
|
@ -57,8 +54,10 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
||||||
void dispose() {
|
void dispose() {
|
||||||
widget.request.onUpdate =
|
widget.request.onUpdate =
|
||||||
originalOnUpdate; // don't want to get updates anymore
|
originalOnUpdate; // don't want to get updates anymore
|
||||||
if (![KeyVerificationState.error, KeyVerificationState.done]
|
if (![
|
||||||
.contains(widget.request.state)) {
|
KeyVerificationState.error,
|
||||||
|
KeyVerificationState.done,
|
||||||
|
].contains(widget.request.state)) {
|
||||||
widget.request.cancel('m.user');
|
widget.request.cancel('m.user');
|
||||||
}
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
@ -98,8 +97,9 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
User? user;
|
User? user;
|
||||||
final directChatId =
|
final directChatId = widget.request.client.getDirectChatFromUserId(
|
||||||
widget.request.client.getDirectChatFromUserId(widget.request.userId);
|
widget.request.userId,
|
||||||
|
);
|
||||||
if (directChatId != null) {
|
if (directChatId != null) {
|
||||||
user = widget.request.client
|
user = widget.request.client
|
||||||
.getRoomById(directChatId)!
|
.getRoomById(directChatId)!
|
||||||
|
|
@ -122,7 +122,7 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
||||||
body = Container(
|
body = Container(
|
||||||
margin: const EdgeInsets.only(left: 8.0, right: 8.0),
|
margin: const EdgeInsets.only(left: 8.0, right: 8.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
L10n.of(context).askSSSSSign,
|
L10n.of(context).askSSSSSign,
|
||||||
|
|
@ -152,17 +152,13 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
||||||
);
|
);
|
||||||
buttons.add(
|
buttons.add(
|
||||||
AdaptiveDialogAction(
|
AdaptiveDialogAction(
|
||||||
child: Text(
|
child: Text(L10n.of(context).submit),
|
||||||
L10n.of(context).submit,
|
|
||||||
),
|
|
||||||
onPressed: () => checkInput(textEditingController.text),
|
onPressed: () => checkInput(textEditingController.text),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
buttons.add(
|
buttons.add(
|
||||||
AdaptiveDialogAction(
|
AdaptiveDialogAction(
|
||||||
child: Text(
|
child: Text(L10n.of(context).skip),
|
||||||
L10n.of(context).skip,
|
|
||||||
),
|
|
||||||
onPressed: () => widget.request.openSSSS(skip: true),
|
onPressed: () => widget.request.openSSSS(skip: true),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -170,7 +166,7 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
||||||
case KeyVerificationState.askAccept:
|
case KeyVerificationState.askAccept:
|
||||||
title = Text(L10n.of(context).newVerificationRequest);
|
title = Text(L10n.of(context).newVerificationRequest);
|
||||||
body = Column(
|
body = Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Avatar(
|
Avatar(
|
||||||
|
|
@ -179,16 +175,14 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
||||||
size: Avatar.defaultSize * 2,
|
size: Avatar.defaultSize * 2,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(L10n.of(context).askVerificationRequest(displayName)),
|
||||||
L10n.of(context).askVerificationRequest(displayName),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
buttons.add(
|
buttons.add(
|
||||||
AdaptiveDialogAction(
|
AdaptiveDialogAction(
|
||||||
onPressed: () => widget.request.rejectVerification().then(
|
onPressed: () => widget.request.rejectVerification().then(
|
||||||
(_) => Navigator.of(context, rootNavigator: false).pop(false),
|
(_) => Navigator.of(context, rootNavigator: false).pop(false),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
L10n.of(context).reject,
|
L10n.of(context).reject,
|
||||||
style: TextStyle(color: theme.colorScheme.error),
|
style: TextStyle(color: theme.colorScheme.error),
|
||||||
|
|
@ -211,10 +205,7 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
||||||
Stack(
|
Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
Avatar(
|
Avatar(mxContent: user?.avatarUrl, name: displayName),
|
||||||
mxContent: user?.avatarUrl,
|
|
||||||
name: displayName,
|
|
||||||
),
|
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: Avatar.defaultSize + 2,
|
width: Avatar.defaultSize + 2,
|
||||||
height: Avatar.defaultSize + 2,
|
height: Avatar.defaultSize + 2,
|
||||||
|
|
@ -258,16 +249,15 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
||||||
title = Text(L10n.of(context).compareNumbersMatch);
|
title = Text(L10n.of(context).compareNumbersMatch);
|
||||||
final numbers = widget.request.sasNumbers;
|
final numbers = widget.request.sasNumbers;
|
||||||
final numbstr = '${numbers[0]}-${numbers[1]}-${numbers[2]}';
|
final numbstr = '${numbers[0]}-${numbers[1]}-${numbers[2]}';
|
||||||
compareWidget =
|
compareWidget = TextSpan(
|
||||||
TextSpan(text: numbstr, style: const TextStyle(fontSize: 40));
|
text: numbstr,
|
||||||
|
style: const TextStyle(fontSize: 40),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
body = Column(
|
body = Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text.rich(
|
Text.rich(compareWidget, textAlign: TextAlign.center),
|
||||||
compareWidget,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
buttons.add(
|
buttons.add(
|
||||||
|
|
@ -291,15 +281,12 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
||||||
? L10n.of(context).waitingPartnerEmoji
|
? L10n.of(context).waitingPartnerEmoji
|
||||||
: L10n.of(context).waitingPartnerNumbers;
|
: L10n.of(context).waitingPartnerNumbers;
|
||||||
body = Column(
|
body = Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const CircularProgressIndicator.adaptive(strokeWidth: 2),
|
const CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(acceptText, textAlign: TextAlign.center),
|
||||||
acceptText,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
@ -315,9 +302,7 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
||||||
);
|
);
|
||||||
buttons.add(
|
buttons.add(
|
||||||
AdaptiveDialogAction(
|
AdaptiveDialogAction(
|
||||||
child: Text(
|
child: Text(L10n.of(context).close),
|
||||||
L10n.of(context).close,
|
|
||||||
),
|
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
Navigator.of(context, rootNavigator: false).pop(true),
|
Navigator.of(context, rootNavigator: false).pop(true),
|
||||||
),
|
),
|
||||||
|
|
@ -326,7 +311,7 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
||||||
case KeyVerificationState.error:
|
case KeyVerificationState.error:
|
||||||
title = const Text('');
|
title = const Text('');
|
||||||
body = Column(
|
body = Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Icon(Icons.cancel, color: theme.colorScheme.error, size: 64.0),
|
Icon(Icons.cancel, color: theme.colorScheme.error, size: 64.0),
|
||||||
|
|
@ -340,9 +325,7 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
||||||
);
|
);
|
||||||
buttons.add(
|
buttons.add(
|
||||||
AdaptiveDialogAction(
|
AdaptiveDialogAction(
|
||||||
child: Text(
|
child: Text(L10n.of(context).close),
|
||||||
L10n.of(context).close,
|
|
||||||
),
|
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
Navigator.of(context, rootNavigator: false).pop(false),
|
Navigator.of(context, rootNavigator: false).pop(false),
|
||||||
),
|
),
|
||||||
|
|
@ -355,9 +338,7 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
height: 256,
|
height: 256,
|
||||||
width: 256,
|
width: 256,
|
||||||
child: ListView(
|
child: ListView(children: [body]),
|
||||||
children: [body],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
actions: buttons,
|
actions: buttons,
|
||||||
);
|
);
|
||||||
|
|
@ -399,7 +380,7 @@ class _Emoji extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(emoji.emoji, style: const TextStyle(fontSize: 50)),
|
Text(emoji.emoji, style: const TextStyle(fontSize: 50)),
|
||||||
Padding(
|
Padding(
|
||||||
|
|
|
||||||
|
|
@ -130,8 +130,9 @@ class LoginController extends State<Login> {
|
||||||
final dialogResult = await showOkCancelAlertDialog(
|
final dialogResult = await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
title: L10n.of(context)
|
title: L10n.of(
|
||||||
.noMatrixServer(newDomain.toString(), oldHomeserver.toString()),
|
context,
|
||||||
|
).noMatrixServer(newDomain.toString(), oldHomeserver.toString()),
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: L10n.of(context).ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: L10n.of(context).cancel,
|
||||||
);
|
);
|
||||||
|
|
@ -165,8 +166,9 @@ class LoginController extends State<Login> {
|
||||||
message: L10n.of(context).enterAnEmailAddress,
|
message: L10n.of(context).enterAnEmailAddress,
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: L10n.of(context).ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: L10n.of(context).cancel,
|
||||||
initialText:
|
initialText: usernameController.text.isEmail
|
||||||
usernameController.text.isEmail ? usernameController.text : '',
|
? usernameController.text
|
||||||
|
: '',
|
||||||
hintText: L10n.of(context).enterAnEmailAddress,
|
hintText: L10n.of(context).enterAnEmailAddress,
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
);
|
);
|
||||||
|
|
@ -238,8 +240,9 @@ class LoginController extends State<Login> {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension on String {
|
extension on String {
|
||||||
static final RegExp _phoneRegex =
|
static final RegExp _phoneRegex = RegExp(
|
||||||
RegExp(r'^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*$');
|
r'^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*$',
|
||||||
|
);
|
||||||
static final RegExp _emailRegex = RegExp(r'(.+)@(.+)\.(.+)');
|
static final RegExp _emailRegex = RegExp(r'(.+)@(.+)\.(.+)');
|
||||||
|
|
||||||
bool get isEmail => _emailRegex.hasMatch(this);
|
bool get isEmail => _emailRegex.hasMatch(this);
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,9 @@ class LoginView extends StatelessWidget {
|
||||||
final titleParts = title.split(homeserver);
|
final titleParts = title.split(homeserver);
|
||||||
|
|
||||||
return LoginScaffold(
|
return LoginScaffold(
|
||||||
enforceMobileMode:
|
enforceMobileMode: Matrix.of(
|
||||||
Matrix.of(context).widget.clients.any((client) => client.isLogged()),
|
context,
|
||||||
|
).widget.clients.any((client) => client.isLogged()),
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: controller.loading ? null : const Center(child: BackButton()),
|
leading: controller.loading ? null : const Center(child: BackButton()),
|
||||||
automaticallyImplyLeading: !controller.loading,
|
automaticallyImplyLeading: !controller.loading,
|
||||||
|
|
@ -62,8 +63,9 @@ class LoginView extends StatelessWidget {
|
||||||
controller: controller.usernameController,
|
controller: controller.usernameController,
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
autofillHints:
|
autofillHints: controller.loading
|
||||||
controller.loading ? null : [AutofillHints.username],
|
? null
|
||||||
|
: [AutofillHints.username],
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefixIcon: const Icon(Icons.account_box_outlined),
|
prefixIcon: const Icon(Icons.account_box_outlined),
|
||||||
errorText: controller.usernameError,
|
errorText: controller.usernameError,
|
||||||
|
|
@ -79,8 +81,9 @@ class LoginView extends StatelessWidget {
|
||||||
child: TextField(
|
child: TextField(
|
||||||
readOnly: controller.loading,
|
readOnly: controller.loading,
|
||||||
autocorrect: false,
|
autocorrect: false,
|
||||||
autofillHints:
|
autofillHints: controller.loading
|
||||||
controller.loading ? null : [AutofillHints.password],
|
? null
|
||||||
|
: [AutofillHints.password],
|
||||||
controller: controller.passwordController,
|
controller: controller.passwordController,
|
||||||
textInputAction: TextInputAction.go,
|
textInputAction: TextInputAction.go,
|
||||||
obscureText: !controller.showPassword,
|
obscureText: !controller.showPassword,
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,7 @@ import 'package:fluffychat/widgets/matrix.dart';
|
||||||
|
|
||||||
class NewGroup extends StatefulWidget {
|
class NewGroup extends StatefulWidget {
|
||||||
final CreateGroupType createGroupType;
|
final CreateGroupType createGroupType;
|
||||||
const NewGroup({
|
const NewGroup({this.createGroupType = CreateGroupType.group, super.key});
|
||||||
this.createGroupType = CreateGroupType.group,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
NewGroupController createState() => NewGroupController();
|
NewGroupController createState() => NewGroupController();
|
||||||
|
|
@ -66,8 +63,9 @@ class NewGroupController extends State<NewGroup> {
|
||||||
Future<void> _createGroup() async {
|
Future<void> _createGroup() async {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final roomId = await Matrix.of(context).client.createGroupChat(
|
final roomId = await Matrix.of(context).client.createGroupChat(
|
||||||
visibility:
|
visibility: groupCanBeFound
|
||||||
groupCanBeFound ? sdk.Visibility.public : sdk.Visibility.private,
|
? sdk.Visibility.public
|
||||||
|
: sdk.Visibility.private,
|
||||||
preset: publicGroup
|
preset: publicGroup
|
||||||
? sdk.CreateRoomPreset.publicChat
|
? sdk.CreateRoomPreset.publicChat
|
||||||
: sdk.CreateRoomPreset.privateChat,
|
: sdk.CreateRoomPreset.privateChat,
|
||||||
|
|
@ -87,24 +85,24 @@ class NewGroupController extends State<NewGroup> {
|
||||||
Future<void> _createSpace() async {
|
Future<void> _createSpace() async {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final spaceId = await Matrix.of(context).client.createRoom(
|
final spaceId = await Matrix.of(context).client.createRoom(
|
||||||
preset: publicGroup
|
preset: publicGroup
|
||||||
? sdk.CreateRoomPreset.publicChat
|
? sdk.CreateRoomPreset.publicChat
|
||||||
: sdk.CreateRoomPreset.privateChat,
|
: sdk.CreateRoomPreset.privateChat,
|
||||||
creationContent: {'type': RoomCreationTypes.mSpace},
|
creationContent: {'type': RoomCreationTypes.mSpace},
|
||||||
visibility: publicGroup ? sdk.Visibility.public : null,
|
visibility: publicGroup ? sdk.Visibility.public : null,
|
||||||
roomAliasName: publicGroup
|
roomAliasName: publicGroup
|
||||||
? nameController.text.trim().toLowerCase().replaceAll(' ', '_')
|
? nameController.text.trim().toLowerCase().replaceAll(' ', '_')
|
||||||
: null,
|
: null,
|
||||||
name: nameController.text.trim(),
|
name: nameController.text.trim(),
|
||||||
powerLevelContentOverride: {'events_default': 100},
|
powerLevelContentOverride: {'events_default': 100},
|
||||||
initialState: [
|
initialState: [
|
||||||
if (avatar != null)
|
if (avatar != null)
|
||||||
sdk.StateEvent(
|
sdk.StateEvent(
|
||||||
type: sdk.EventTypes.RoomAvatar,
|
type: sdk.EventTypes.RoomAvatar,
|
||||||
content: {'url': avatarUrl.toString()},
|
content: {'url': avatarUrl.toString()},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.pop<String>(spaceId);
|
context.pop<String>(spaceId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class NewGroupView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
body: MaxWidthBody(
|
body: MaxWidthBody(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
|
@ -104,8 +104,9 @@ class NewGroupView extends StatelessWidget {
|
||||||
curve: FluffyThemes.animationCurve,
|
curve: FluffyThemes.animationCurve,
|
||||||
child: controller.publicGroup
|
child: controller.publicGroup
|
||||||
? SwitchListTile.adaptive(
|
? SwitchListTile.adaptive(
|
||||||
contentPadding:
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(horizontal: 32),
|
horizontal: 32,
|
||||||
|
),
|
||||||
secondary: const Icon(Icons.search_outlined),
|
secondary: const Icon(Icons.search_outlined),
|
||||||
title: Text(L10n.of(context).groupCanBeFoundViaSearch),
|
title: Text(L10n.of(context).groupCanBeFoundViaSearch),
|
||||||
value: controller.groupCanBeFound,
|
value: controller.groupCanBeFound,
|
||||||
|
|
@ -121,17 +122,16 @@ class NewGroupView extends StatelessWidget {
|
||||||
child: controller.createGroupType == CreateGroupType.space
|
child: controller.createGroupType == CreateGroupType.space
|
||||||
? const SizedBox.shrink()
|
? const SizedBox.shrink()
|
||||||
: SwitchListTile.adaptive(
|
: SwitchListTile.adaptive(
|
||||||
contentPadding:
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(horizontal: 32),
|
horizontal: 32,
|
||||||
|
),
|
||||||
secondary: Icon(
|
secondary: Icon(
|
||||||
Icons.lock_outlined,
|
Icons.lock_outlined,
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
L10n.of(context).enableEncryption,
|
L10n.of(context).enableEncryption,
|
||||||
style: TextStyle(
|
style: TextStyle(color: theme.colorScheme.onSurface),
|
||||||
color: theme.colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
value: !controller.publicGroup,
|
value: !controller.publicGroup,
|
||||||
onChanged: null,
|
onChanged: null,
|
||||||
|
|
@ -142,8 +142,9 @@ class NewGroupView extends StatelessWidget {
|
||||||
curve: FluffyThemes.animationCurve,
|
curve: FluffyThemes.animationCurve,
|
||||||
child: controller.createGroupType == CreateGroupType.space
|
child: controller.createGroupType == CreateGroupType.space
|
||||||
? ListTile(
|
? ListTile(
|
||||||
contentPadding:
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(horizontal: 32),
|
horizontal: 32,
|
||||||
|
),
|
||||||
trailing: const Padding(
|
trailing: const Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16.0),
|
padding: EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
child: Icon(Icons.info_outlined),
|
child: Icon(Icons.info_outlined),
|
||||||
|
|
@ -157,8 +158,9 @@ class NewGroupView extends StatelessWidget {
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed:
|
onPressed: controller.loading
|
||||||
controller.loading ? null : controller.submitAction,
|
? null
|
||||||
|
: controller.submitAction,
|
||||||
child: controller.loading
|
child: controller.loading
|
||||||
? const LinearProgressIndicator()
|
? const LinearProgressIndicator()
|
||||||
: Text(
|
: Text(
|
||||||
|
|
@ -181,9 +183,7 @@ class NewGroupView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
error.toLocalizedString(context),
|
error.toLocalizedString(context),
|
||||||
style: TextStyle(
|
style: TextStyle(color: theme.colorScheme.error),
|
||||||
color: theme.colorScheme.error,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,9 @@ class NewPrivateChatController extends State<NewPrivateChat> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Profile>> _searchUser(String searchTerm) async {
|
Future<List<Profile>> _searchUser(String searchTerm) async {
|
||||||
final result =
|
final result = await Matrix.of(
|
||||||
await Matrix.of(context).client.searchUserDirectory(searchTerm);
|
context,
|
||||||
|
).client.searchUserDirectory(searchTerm);
|
||||||
final profiles = result.results;
|
final profiles = result.results;
|
||||||
|
|
||||||
if (searchTerm.isValidMatrixId &&
|
if (searchTerm.isValidMatrixId &&
|
||||||
|
|
@ -73,9 +74,7 @@ class NewPrivateChatController extends State<NewPrivateChat> {
|
||||||
if (info.version.sdkInt < 21) {
|
if (info.version.sdkInt < 21) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(L10n.of(context).unsupportedAndroidVersionLong),
|
||||||
L10n.of(context).unsupportedAndroidVersionLong,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|
@ -93,15 +92,13 @@ class NewPrivateChatController extends State<NewPrivateChat> {
|
||||||
await Clipboard.setData(
|
await Clipboard.setData(
|
||||||
ClipboardData(text: Matrix.of(context).client.userID!),
|
ClipboardData(text: Matrix.of(context).client.userID!),
|
||||||
);
|
);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text(L10n.of(context).copiedToClipboard)),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text(L10n.of(context).copiedToClipboard)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void openUserModal(Profile profile) => UserDialog.show(
|
void openUserModal(Profile profile) =>
|
||||||
context: context,
|
UserDialog.show(context: context, profile: profile);
|
||||||
profile: profile,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => NewPrivateChatView(this);
|
Widget build(BuildContext context) => NewPrivateChatView(this);
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,10 @@ class NewPrivateChatView extends StatelessWidget {
|
||||||
backgroundColor: theme.scaffoldBackgroundColor,
|
backgroundColor: theme.scaffoldBackgroundColor,
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed:
|
onPressed: UrlLauncher(
|
||||||
UrlLauncher(context, AppConfig.startChatTutorial).launchUrl,
|
context,
|
||||||
|
AppConfig.startChatTutorial,
|
||||||
|
).launchUrl,
|
||||||
child: Text(L10n.of(context).help),
|
child: Text(L10n.of(context).help),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -105,8 +107,9 @@ class NewPrivateChatView extends StatelessWidget {
|
||||||
? ListView(
|
? ListView(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(horizontal: 18.0),
|
horizontal: 18.0,
|
||||||
|
),
|
||||||
child: SelectableText.rich(
|
child: SelectableText.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -157,8 +160,9 @@ class NewPrivateChatView extends StatelessWidget {
|
||||||
theme.colorScheme.primaryContainer,
|
theme.colorScheme.primaryContainer,
|
||||||
foregroundColor:
|
foregroundColor:
|
||||||
theme.colorScheme.onPrimaryContainer,
|
theme.colorScheme.onPrimaryContainer,
|
||||||
child:
|
child: const Icon(
|
||||||
const Icon(Icons.qr_code_scanner_outlined),
|
Icons.qr_code_scanner_outlined,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
title: Text(L10n.of(context).scanQrCode),
|
title: Text(L10n.of(context).scanQrCode),
|
||||||
onTap: controller.openScannerAction,
|
onTap: controller.openScannerAction,
|
||||||
|
|
@ -185,15 +189,14 @@ class NewPrivateChatView extends StatelessWidget {
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(
|
||||||
AppConfig.borderRadius,
|
AppConfig.borderRadius,
|
||||||
),
|
),
|
||||||
onTap: () => showQrCodeViewer(
|
onTap: () =>
|
||||||
context,
|
showQrCodeViewer(context, userId),
|
||||||
userId,
|
|
||||||
),
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints:
|
constraints: const BoxConstraints(
|
||||||
const BoxConstraints(maxWidth: 200),
|
maxWidth: 200,
|
||||||
|
),
|
||||||
child: PrettyQrView.data(
|
child: PrettyQrView.data(
|
||||||
data: 'https://matrix.to/#/$userId',
|
data: 'https://matrix.to/#/$userId',
|
||||||
decoration: PrettyQrDecoration(
|
decoration: PrettyQrDecoration(
|
||||||
|
|
@ -218,7 +221,7 @@ class NewPrivateChatView extends StatelessWidget {
|
||||||
final error = snapshot.error;
|
final error = snapshot.error;
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
error.toLocalizedString(context),
|
error.toLocalizedString(context),
|
||||||
|
|
@ -243,7 +246,7 @@ class NewPrivateChatView extends StatelessWidget {
|
||||||
}
|
}
|
||||||
if (result.isEmpty) {
|
if (result.isEmpty) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.search_outlined, size: 86),
|
const Icon(Icons.search_outlined, size: 86),
|
||||||
Padding(
|
Padding(
|
||||||
|
|
@ -265,7 +268,8 @@ class NewPrivateChatView extends StatelessWidget {
|
||||||
itemCount: result.length,
|
itemCount: result.length,
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
final contact = result[i];
|
final contact = result[i];
|
||||||
final displayname = contact.displayName ??
|
final displayname =
|
||||||
|
contact.displayName ??
|
||||||
contact.userId.localpart ??
|
contact.userId.localpart ??
|
||||||
contact.userId;
|
contact.userId;
|
||||||
return ListTile(
|
return ListTile(
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,9 @@ class SettingsController extends State<Settings> {
|
||||||
bool profileUpdated = false;
|
bool profileUpdated = false;
|
||||||
|
|
||||||
void updateProfile() => setState(() {
|
void updateProfile() => setState(() {
|
||||||
profileUpdated = true;
|
profileUpdated = true;
|
||||||
profileFuture = null;
|
profileFuture = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
void setDisplaynameAction() async {
|
void setDisplaynameAction() async {
|
||||||
final profile = await profileFuture;
|
final profile = await profileFuture;
|
||||||
|
|
@ -132,15 +132,9 @@ class SettingsController extends State<Settings> {
|
||||||
imageQuality: 50,
|
imageQuality: 50,
|
||||||
);
|
);
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
file = MatrixFile(
|
file = MatrixFile(bytes: await result.readAsBytes(), name: result.path);
|
||||||
bytes: await result.readAsBytes(),
|
|
||||||
name: result.path,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
final result = await selectFiles(
|
final result = await selectFiles(context, type: FileSelectorType.images);
|
||||||
context,
|
|
||||||
type: FileSelectorType.images,
|
|
||||||
);
|
|
||||||
final pickedFile = result.firstOrNull;
|
final pickedFile = result.firstOrNull;
|
||||||
if (pickedFile == null) return;
|
if (pickedFile == null) return;
|
||||||
file = MatrixFile(
|
file = MatrixFile(
|
||||||
|
|
@ -176,8 +170,8 @@ class SettingsController extends State<Settings> {
|
||||||
await client.encryption?.crossSigning.isCached() ?? false;
|
await client.encryption?.crossSigning.isCached() ?? false;
|
||||||
final needsBootstrap =
|
final needsBootstrap =
|
||||||
await client.encryption?.keyManager.isCached() == false ||
|
await client.encryption?.keyManager.isCached() == false ||
|
||||||
client.encryption?.crossSigning.enabled == false ||
|
client.encryption?.crossSigning.enabled == false ||
|
||||||
crossSigning == false;
|
crossSigning == false;
|
||||||
final isUnknownSession = client.isUnknownSession;
|
final isUnknownSession = client.isUnknownSession;
|
||||||
setState(() {
|
setState(() {
|
||||||
showChatBackupBanner = needsBootstrap || isUnknownSession;
|
showChatBackupBanner = needsBootstrap || isUnknownSession;
|
||||||
|
|
@ -204,9 +198,7 @@ class SettingsController extends State<Settings> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
profileFuture ??= client.getProfileFromUserId(
|
profileFuture ??= client.getProfileFromUserId(client.userID!);
|
||||||
client.userID!,
|
|
||||||
);
|
|
||||||
return SettingsView(this);
|
return SettingsView(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,9 @@ class SettingsView extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final showChatBackupBanner = controller.showChatBackupBanner;
|
final showChatBackupBanner = controller.showChatBackupBanner;
|
||||||
final activeRoute =
|
final activeRoute = GoRouter.of(
|
||||||
GoRouter.of(context).routeInformationProvider.value.uri.path;
|
context,
|
||||||
|
).routeInformationProvider.value.uri.path;
|
||||||
final accountManageUrl = Matrix.of(context)
|
final accountManageUrl = Matrix.of(context)
|
||||||
.client
|
.client
|
||||||
.wellKnown
|
.wellKnown
|
||||||
|
|
@ -41,10 +42,7 @@ class SettingsView extends StatelessWidget {
|
||||||
onGoToChats: () => context.go('/rooms'),
|
onGoToChats: () => context.go('/rooms'),
|
||||||
onGoToSpaceId: (spaceId) => context.go('/rooms?spaceId=$spaceId'),
|
onGoToSpaceId: (spaceId) => context.go('/rooms?spaceId=$spaceId'),
|
||||||
),
|
),
|
||||||
Container(
|
Container(color: Theme.of(context).dividerColor, width: 1),
|
||||||
color: Theme.of(context).dividerColor,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
|
|
@ -53,9 +51,7 @@ class SettingsView extends StatelessWidget {
|
||||||
: AppBar(
|
: AppBar(
|
||||||
title: Text(L10n.of(context).settings),
|
title: Text(L10n.of(context).settings),
|
||||||
leading: Center(
|
leading: Center(
|
||||||
child: BackButton(
|
child: BackButton(onPressed: () => context.go('/rooms')),
|
||||||
onPressed: () => context.go('/rooms'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: ListTileTheme(
|
body: ListTileTheme(
|
||||||
|
|
@ -68,7 +64,8 @@ class SettingsView extends StatelessWidget {
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final profile = snapshot.data;
|
final profile = snapshot.data;
|
||||||
final avatar = profile?.avatarUrl;
|
final avatar = profile?.avatarUrl;
|
||||||
final mxid = Matrix.of(context).client.userID ??
|
final mxid =
|
||||||
|
Matrix.of(context).client.userID ??
|
||||||
L10n.of(context).user;
|
L10n.of(context).user;
|
||||||
final displayname =
|
final displayname =
|
||||||
profile?.displayName ?? mxid.localpart ?? mxid;
|
profile?.displayName ?? mxid.localpart ?? mxid;
|
||||||
|
|
@ -84,10 +81,10 @@ class SettingsView extends StatelessWidget {
|
||||||
size: Avatar.defaultSize * 2.5,
|
size: Avatar.defaultSize * 2.5,
|
||||||
onTap: avatar != null
|
onTap: avatar != null
|
||||||
? () => showDialog(
|
? () => showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) =>
|
builder: (_) =>
|
||||||
MxcImageViewer(avatar),
|
MxcImageViewer(avatar),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
if (profile != null)
|
if (profile != null)
|
||||||
|
|
@ -108,8 +105,8 @@ class SettingsView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: .start,
|
||||||
children: [
|
children: [
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: controller.setDisplaynameAction,
|
onPressed: controller.setDisplaynameAction,
|
||||||
|
|
@ -126,9 +123,7 @@ class SettingsView extends StatelessWidget {
|
||||||
displayname,
|
displayname,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 18),
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
|
|
@ -182,9 +177,7 @@ class SettingsView extends StatelessWidget {
|
||||||
title: Text(L10n.of(context).chatBackup),
|
title: Text(L10n.of(context).chatBackup),
|
||||||
onChanged: controller.firstRunBootstrapAction,
|
onChanged: controller.firstRunBootstrapAction,
|
||||||
),
|
),
|
||||||
Divider(
|
Divider(color: theme.dividerColor),
|
||||||
color: theme.dividerColor,
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.format_paint_outlined),
|
leading: const Icon(Icons.format_paint_outlined),
|
||||||
title: Text(L10n.of(context).changeTheme),
|
title: Text(L10n.of(context).changeTheme),
|
||||||
|
|
@ -198,8 +191,8 @@ class SettingsView extends StatelessWidget {
|
||||||
title: Text(L10n.of(context).notifications),
|
title: Text(L10n.of(context).notifications),
|
||||||
tileColor:
|
tileColor:
|
||||||
activeRoute.startsWith('/rooms/settings/notifications')
|
activeRoute.startsWith('/rooms/settings/notifications')
|
||||||
? theme.colorScheme.surfaceContainerHigh
|
? theme.colorScheme.surfaceContainerHigh
|
||||||
: null,
|
: null,
|
||||||
onTap: () => context.go('/rooms/settings/notifications'),
|
onTap: () => context.go('/rooms/settings/notifications'),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|
@ -224,8 +217,8 @@ class SettingsView extends StatelessWidget {
|
||||||
onTap: () => context.go('/rooms/settings/security'),
|
onTap: () => context.go('/rooms/settings/security'),
|
||||||
tileColor:
|
tileColor:
|
||||||
activeRoute.startsWith('/rooms/settings/security')
|
activeRoute.startsWith('/rooms/settings/security')
|
||||||
? theme.colorScheme.surfaceContainerHigh
|
? theme.colorScheme.surfaceContainerHigh
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
Divider(color: theme.dividerColor),
|
Divider(color: theme.dividerColor),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|
@ -239,8 +232,8 @@ class SettingsView extends StatelessWidget {
|
||||||
onTap: () => context.go('/rooms/settings/homeserver'),
|
onTap: () => context.go('/rooms/settings/homeserver'),
|
||||||
tileColor:
|
tileColor:
|
||||||
activeRoute.startsWith('/rooms/settings/homeserver')
|
activeRoute.startsWith('/rooms/settings/homeserver')
|
||||||
? theme.colorScheme.surfaceContainerHigh
|
? theme.colorScheme.surfaceContainerHigh
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.privacy_tip_outlined),
|
leading: const Icon(Icons.privacy_tip_outlined),
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,10 @@ class Settings3PidController extends State<Settings3Pid> {
|
||||||
final response = await showFutureLoadingDialog(
|
final response = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => Matrix.of(context).client.requestTokenToRegisterEmail(
|
future: () => Matrix.of(context).client.requestTokenToRegisterEmail(
|
||||||
clientSecret,
|
clientSecret,
|
||||||
input,
|
input,
|
||||||
Settings3Pid.sendAttempt++,
|
Settings3Pid.sendAttempt++,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (response.error != null) return;
|
if (response.error != null) return;
|
||||||
final ok = await showOkAlertDialog(
|
final ok = await showOkAlertDialog(
|
||||||
|
|
@ -52,12 +52,10 @@ class Settings3PidController extends State<Settings3Pid> {
|
||||||
context: context,
|
context: context,
|
||||||
delay: false,
|
delay: false,
|
||||||
future: () => Matrix.of(context).client.uiaRequestBackground(
|
future: () => Matrix.of(context).client.uiaRequestBackground(
|
||||||
(auth) => Matrix.of(context).client.add3PID(
|
(auth) => Matrix.of(
|
||||||
clientSecret,
|
context,
|
||||||
response.result!.sid,
|
).client.add3PID(clientSecret, response.result!.sid, auth: auth),
|
||||||
auth: auth,
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (success.error != null) return;
|
if (success.error != null) return;
|
||||||
setState(() => request = null);
|
setState(() => request = null);
|
||||||
|
|
@ -78,10 +76,9 @@ class Settings3PidController extends State<Settings3Pid> {
|
||||||
}
|
}
|
||||||
final success = await showFutureLoadingDialog(
|
final success = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => Matrix.of(context).client.delete3pidFromAccount(
|
future: () => Matrix.of(
|
||||||
identifier.address,
|
context,
|
||||||
identifier.medium,
|
).client.delete3pidFromAccount(identifier.address, identifier.medium),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (success.error != null) return;
|
if (success.error != null) return;
|
||||||
setState(() => request = null);
|
setState(() => request = null);
|
||||||
|
|
|
||||||
|
|
@ -33,67 +33,71 @@ class Settings3PidView extends StatelessWidget {
|
||||||
withScrolling: false,
|
withScrolling: false,
|
||||||
child: FutureBuilder<List<ThirdPartyIdentifier>?>(
|
child: FutureBuilder<List<ThirdPartyIdentifier>?>(
|
||||||
future: controller.request,
|
future: controller.request,
|
||||||
builder: (
|
builder:
|
||||||
BuildContext context,
|
(
|
||||||
AsyncSnapshot<List<ThirdPartyIdentifier>?> snapshot,
|
BuildContext context,
|
||||||
) {
|
AsyncSnapshot<List<ThirdPartyIdentifier>?> snapshot,
|
||||||
if (snapshot.hasError) {
|
) {
|
||||||
return Center(
|
if (snapshot.hasError) {
|
||||||
child: Text(
|
return Center(
|
||||||
snapshot.error.toString(),
|
child: Text(
|
||||||
textAlign: TextAlign.center,
|
snapshot.error.toString(),
|
||||||
),
|
textAlign: TextAlign.center,
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!snapshot.hasData) {
|
|
||||||
return const Center(
|
|
||||||
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final identifier = snapshot.data!;
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
leading: CircleAvatar(
|
|
||||||
backgroundColor: theme.scaffoldBackgroundColor,
|
|
||||||
foregroundColor:
|
|
||||||
identifier.isEmpty ? Colors.orange : Colors.grey,
|
|
||||||
child: Icon(
|
|
||||||
identifier.isEmpty
|
|
||||||
? Icons.warning_outlined
|
|
||||||
: Icons.info_outlined,
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
title: Text(
|
}
|
||||||
identifier.isEmpty
|
if (!snapshot.hasData) {
|
||||||
? L10n.of(context).noPasswordRecoveryDescription
|
return const Center(
|
||||||
: L10n.of(context)
|
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||||
.withTheseAddressesRecoveryDescription,
|
);
|
||||||
),
|
}
|
||||||
),
|
final identifier = snapshot.data!;
|
||||||
const Divider(),
|
return Column(
|
||||||
Expanded(
|
children: [
|
||||||
child: ListView.builder(
|
ListTile(
|
||||||
itemCount: identifier.length,
|
|
||||||
itemBuilder: (BuildContext context, int i) => ListTile(
|
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
backgroundColor: theme.scaffoldBackgroundColor,
|
backgroundColor: theme.scaffoldBackgroundColor,
|
||||||
foregroundColor: Colors.grey,
|
foregroundColor: identifier.isEmpty
|
||||||
child: Icon(identifier[i].iconData),
|
? Colors.orange
|
||||||
|
: Colors.grey,
|
||||||
|
child: Icon(
|
||||||
|
identifier.isEmpty
|
||||||
|
? Icons.warning_outlined
|
||||||
|
: Icons.info_outlined,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
title: Text(identifier[i].address),
|
title: Text(
|
||||||
trailing: IconButton(
|
identifier.isEmpty
|
||||||
tooltip: L10n.of(context).delete,
|
? L10n.of(context).noPasswordRecoveryDescription
|
||||||
icon: const Icon(Icons.delete_forever_outlined),
|
: L10n.of(
|
||||||
color: Colors.red,
|
context,
|
||||||
onPressed: () => controller.delete3Pid(identifier[i]),
|
).withTheseAddressesRecoveryDescription,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const Divider(),
|
||||||
),
|
Expanded(
|
||||||
],
|
child: ListView.builder(
|
||||||
);
|
itemCount: identifier.length,
|
||||||
},
|
itemBuilder: (BuildContext context, int i) => ListTile(
|
||||||
|
leading: CircleAvatar(
|
||||||
|
backgroundColor: theme.scaffoldBackgroundColor,
|
||||||
|
foregroundColor: Colors.grey,
|
||||||
|
child: Icon(identifier[i].iconData),
|
||||||
|
),
|
||||||
|
title: Text(identifier[i].address),
|
||||||
|
trailing: IconButton(
|
||||||
|
tooltip: L10n.of(context).delete,
|
||||||
|
icon: const Icon(Icons.delete_forever_outlined),
|
||||||
|
color: Colors.red,
|
||||||
|
onPressed: () =>
|
||||||
|
controller.delete3Pid(identifier[i]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -46,11 +46,7 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(L10n.of(context).importEmojis),
|
title: Text(L10n.of(context).importEmojis),
|
||||||
content: _loading
|
content: _loading
|
||||||
? Center(
|
? Center(child: CircularProgressIndicator(value: _progress))
|
||||||
child: CircularProgressIndicator(
|
|
||||||
value: _progress,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: SingleChildScrollView(
|
: SingleChildScrollView(
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
alignment: WrapAlignment.spaceEvenly,
|
alignment: WrapAlignment.spaceEvenly,
|
||||||
|
|
@ -79,8 +75,8 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
|
||||||
onPressed: _loading
|
onPressed: _loading
|
||||||
? null
|
? null
|
||||||
: _importMap.isNotEmpty
|
: _importMap.isNotEmpty
|
||||||
? _addEmotePack
|
? _addEmotePack
|
||||||
: null,
|
: null,
|
||||||
child: Text(L10n.of(context).importNow),
|
child: Text(L10n.of(context).importNow),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -91,12 +87,8 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
|
||||||
_importMap = Map.fromEntries(
|
_importMap = Map.fromEntries(
|
||||||
widget.archive.files
|
widget.archive.files
|
||||||
.where((e) => e.isFile)
|
.where((e) => e.isFile)
|
||||||
.map(
|
.map((e) => MapEntry(e, e.name.emoteNameFromPath))
|
||||||
(e) => MapEntry(e, e.name.emoteNameFromPath),
|
.sorted((a, b) => a.value.compareTo(b.value)),
|
||||||
)
|
|
||||||
.sorted(
|
|
||||||
(a, b) => a.value.compareTo(b.value),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,10 +140,7 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
|
||||||
final imageCode = entry.value;
|
final imageCode = entry.value;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var mxcFile = MatrixImageFile(
|
var mxcFile = MatrixImageFile(bytes: file.content, name: file.name);
|
||||||
bytes: file.content,
|
|
||||||
name: file.name,
|
|
||||||
);
|
|
||||||
|
|
||||||
final thumbnail = (await mxcFile.generateThumbnail(
|
final thumbnail = (await mxcFile.generateThumbnail(
|
||||||
nativeImplementations: ClientManager.nativeImplementations,
|
nativeImplementations: ClientManager.nativeImplementations,
|
||||||
|
|
@ -162,14 +151,12 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
|
||||||
mxcFile = thumbnail;
|
mxcFile = thumbnail;
|
||||||
}
|
}
|
||||||
final uri = await Matrix.of(context).client.uploadContent(
|
final uri = await Matrix.of(context).client.uploadContent(
|
||||||
mxcFile.bytes,
|
mxcFile.bytes,
|
||||||
filename: mxcFile.name,
|
filename: mxcFile.name,
|
||||||
contentType: mxcFile.mimeType,
|
contentType: mxcFile.mimeType,
|
||||||
);
|
);
|
||||||
|
|
||||||
final info = <String, dynamic>{
|
final info = <String, dynamic>{...mxcFile.info};
|
||||||
...mxcFile.info,
|
|
||||||
};
|
|
||||||
|
|
||||||
// normalize width / height to 256, required for stickers
|
// normalize width / height to 256, required for stickers
|
||||||
if (info['w'] is int && info['h'] is int) {
|
if (info['w'] is int && info['h'] is int) {
|
||||||
|
|
@ -184,9 +171,9 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
|
||||||
}
|
}
|
||||||
widget.controller.pack!.images[imageCode] =
|
widget.controller.pack!.images[imageCode] =
|
||||||
ImagePackImageContent.fromJson(<String, dynamic>{
|
ImagePackImageContent.fromJson(<String, dynamic>{
|
||||||
'url': uri.toString(),
|
'url': uri.toString(),
|
||||||
'info': info,
|
'info': info,
|
||||||
});
|
});
|
||||||
successfulUploads.add(file.name);
|
successfulUploads.add(file.name);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logs().d('Could not upload emote $imageCode');
|
Logs().d('Could not upload emote $imageCode');
|
||||||
|
|
@ -204,8 +191,9 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
|
||||||
// in case we have unhandled / duplicated emotes left, don't pop
|
// in case we have unhandled / duplicated emotes left, don't pop
|
||||||
if (mounted) setState(() {});
|
if (mounted) setState(() {});
|
||||||
if (_importMap.isEmpty) {
|
if (_importMap.isEmpty) {
|
||||||
WidgetsBinding.instance
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
.addPostFrameCallback((_) => Navigator.of(context).pop());
|
(_) => Navigator.of(context).pop(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -250,21 +238,20 @@ class _EmojiImportPreviewState extends State<_EmojiImportPreview> {
|
||||||
if (hasError) return _ImageFileError(name: widget.entry.key.name);
|
if (hasError) return _ImageFileError(name: widget.entry.key.name);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: .center,
|
||||||
children: [
|
children: [
|
||||||
Image.memory(
|
Image.memory(
|
||||||
widget.entry.key.content,
|
widget.entry.key.content,
|
||||||
height: 64,
|
height: 64,
|
||||||
width: 64,
|
width: 64,
|
||||||
errorBuilder: (context, e, s) {
|
errorBuilder: (context, e, s) {
|
||||||
WidgetsBinding.instance
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
.addPostFrameCallback((_) => _setRenderError());
|
(_) => _setRenderError(),
|
||||||
|
|
||||||
return _ImageFileError(
|
|
||||||
name: widget.entry.key.name,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return _ImageFileError(name: widget.entry.key.name);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|
@ -323,9 +310,9 @@ class _ImageFileError extends StatelessWidget {
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
message: name,
|
message: name,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: .start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: .center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.error),
|
const Icon(Icons.error),
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -347,8 +334,7 @@ extension on String {
|
||||||
/// Used to compute emote name proposal based on file name
|
/// Used to compute emote name proposal based on file name
|
||||||
String get emoteNameFromPath {
|
String get emoteNameFromPath {
|
||||||
// ... removing leading path
|
// ... removing leading path
|
||||||
return split(RegExp(r'[/\\]'))
|
return split(RegExp(r'[/\\]')).last
|
||||||
.last
|
|
||||||
// ... removing file extension
|
// ... removing file extension
|
||||||
.split('.')
|
.split('.')
|
||||||
.first
|
.first
|
||||||
|
|
|
||||||
|
|
@ -55,10 +55,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||||
|
|
||||||
final event = key == null
|
final event = key == null
|
||||||
? null
|
? null
|
||||||
: room?.getState(
|
: room?.getState('im.ponies.room_emotes', key);
|
||||||
'im.ponies.room_emotes',
|
|
||||||
key,
|
|
||||||
);
|
|
||||||
final eventPack = event?.content.tryGetMap<String, Object?>('pack');
|
final eventPack = event?.content.tryGetMap<String, Object?>('pack');
|
||||||
packDisplayNameController.text =
|
packDisplayNameController.text =
|
||||||
eventPack?.tryGet<String>('display_name') ?? '';
|
eventPack?.tryGet<String>('display_name') ?? '';
|
||||||
|
|
@ -71,13 +68,11 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||||
|
|
||||||
ImagePackContent _getPack() {
|
ImagePackContent _getPack() {
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
final event = (room != null
|
final event =
|
||||||
|
(room != null
|
||||||
? room!.getState('im.ponies.room_emotes', stateKey ?? '')
|
? room!.getState('im.ponies.room_emotes', stateKey ?? '')
|
||||||
: client.accountData['im.ponies.user_emotes']) ??
|
: client.accountData['im.ponies.user_emotes']) ??
|
||||||
BasicEvent(
|
BasicEvent(type: 'm.dummy', content: {});
|
||||||
type: 'm.dummy',
|
|
||||||
content: {},
|
|
||||||
);
|
|
||||||
// make sure we work on a *copy* of the event
|
// make sure we work on a *copy* of the event
|
||||||
return BasicEvent.fromJson(event.toJson()).parsedImagePackContent;
|
return BasicEvent.fromJson(event.toJson()).parsedImagePackContent;
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +119,8 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
final content = client.accountData['im.ponies.emote_rooms']?.content ??
|
final content =
|
||||||
|
client.accountData['im.ponies.emote_rooms']?.content ??
|
||||||
<String, dynamic>{};
|
<String, dynamic>{};
|
||||||
if (active) {
|
if (active) {
|
||||||
if (content['rooms'] is! Map) {
|
if (content['rooms'] is! Map) {
|
||||||
|
|
@ -158,14 +154,15 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||||
TextEditingController();
|
TextEditingController();
|
||||||
|
|
||||||
void removeImageAction(String oldImageCode) => setState(() {
|
void removeImageAction(String oldImageCode) => setState(() {
|
||||||
pack!.images.remove(oldImageCode);
|
pack!.images.remove(oldImageCode);
|
||||||
showSave = true;
|
showSave = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
void toggleUsage(String imageCode, ImagePackUsage usage) {
|
void toggleUsage(String imageCode, ImagePackUsage usage) {
|
||||||
setState(() {
|
setState(() {
|
||||||
final usages =
|
final usages = pack!.images[imageCode]!.usage ??= List.from(
|
||||||
pack!.images[imageCode]!.usage ??= List.from(ImagePackUsage.values);
|
ImagePackUsage.values,
|
||||||
|
);
|
||||||
if (!usages.remove(usage)) usages.add(usage);
|
if (!usages.remove(usage)) usages.add(usage);
|
||||||
showSave = true;
|
showSave = true;
|
||||||
});
|
});
|
||||||
|
|
@ -265,9 +262,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||||
|
|
||||||
if (packKeys?.contains(name) ?? false) {
|
if (packKeys?.contains(name) ?? false) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(content: Text(L10n.of(context).stickerPackNameAlreadyExists)),
|
||||||
content: Text(L10n.of(context).stickerPackNameAlreadyExists),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -316,20 +311,19 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||||
bytes: await pickedFile.readAsBytes(),
|
bytes: await pickedFile.readAsBytes(),
|
||||||
name: pickedFile.name,
|
name: pickedFile.name,
|
||||||
);
|
);
|
||||||
file = await file.generateThumbnail(
|
file =
|
||||||
|
await file.generateThumbnail(
|
||||||
nativeImplementations: ClientManager.nativeImplementations,
|
nativeImplementations: ClientManager.nativeImplementations,
|
||||||
) ??
|
) ??
|
||||||
file;
|
file;
|
||||||
final uri = await Matrix.of(context).client.uploadContent(
|
final uri = await Matrix.of(context).client.uploadContent(
|
||||||
file.bytes,
|
file.bytes,
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
contentType: file.mimeType,
|
contentType: file.mimeType,
|
||||||
);
|
);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
final info = <String, dynamic>{
|
final info = <String, dynamic>{...file.info};
|
||||||
...file.info,
|
|
||||||
};
|
|
||||||
// normalize width / height to 256, required for stickers
|
// normalize width / height to 256, required for stickers
|
||||||
if (info['w'] is int && info['h'] is int) {
|
if (info['w'] is int && info['h'] is int) {
|
||||||
final ratio = info['w'] / info['h'];
|
final ratio = info['w'] / info['h'];
|
||||||
|
|
@ -342,11 +336,9 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final imageCode = pickedFile.name.split('.').first;
|
final imageCode = pickedFile.name.split('.').first;
|
||||||
pack!.images[imageCode] =
|
pack!.images[imageCode] = ImagePackImageContent.fromJson(
|
||||||
ImagePackImageContent.fromJson(<String, dynamic>{
|
<String, dynamic>{'url': uri.toString(), 'info': info},
|
||||||
'url': uri.toString(),
|
);
|
||||||
'info': info,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -363,10 +355,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> importEmojiZip() async {
|
Future<void> importEmojiZip() async {
|
||||||
final result = await selectFiles(
|
final result = await selectFiles(context, type: FileSelectorType.zip);
|
||||||
context,
|
|
||||||
type: FileSelectorType.zip,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.isEmpty) return;
|
if (result.isEmpty) return;
|
||||||
|
|
||||||
|
|
@ -378,10 +367,8 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||||
context: context,
|
context: context,
|
||||||
// breaks [Matrix.of] calls otherwise
|
// breaks [Matrix.of] calls otherwise
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
builder: (context) => ImportEmoteArchiveDialog(
|
builder: (context) =>
|
||||||
controller: this,
|
ImportEmoteArchiveDialog(controller: this, archive: archive),
|
||||||
archive: archive,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
@ -404,11 +391,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||||
);
|
);
|
||||||
|
|
||||||
archive.addFile(
|
archive.addFile(
|
||||||
ArchiveFile(
|
ArchiveFile(name, response.bodyBytes.length, response.bodyBytes),
|
||||||
name,
|
|
||||||
response.bodyBytes.length,
|
|
||||||
response.bodyBytes,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final fileName =
|
final fileName =
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,7 @@ class EmotesSettingsView extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (controller.widget.roomId != null && controller.room == null) {
|
if (controller.widget.roomId != null && controller.room == null) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(title: Text(L10n.of(context).oopsSomethingWentWrong)),
|
||||||
title: Text(L10n.of(context).oopsSomethingWentWrong),
|
|
||||||
),
|
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat),
|
child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat),
|
||||||
),
|
),
|
||||||
|
|
@ -110,10 +108,7 @@ class EmotesSettingsView extends StatelessWidget {
|
||||||
horizontal: 4.0,
|
horizontal: 4.0,
|
||||||
),
|
),
|
||||||
child: FilterChip(
|
child: FilterChip(
|
||||||
label: const Icon(
|
label: const Icon(Icons.add_outlined, size: 20),
|
||||||
Icons.add_outlined,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
onSelected: controller.showSave
|
onSelected: controller.showSave
|
||||||
? null
|
? null
|
||||||
: (_) => controller.createImagePack(),
|
: (_) => controller.createImagePack(),
|
||||||
|
|
@ -122,23 +117,24 @@ class EmotesSettingsView extends StatelessWidget {
|
||||||
}
|
}
|
||||||
i--;
|
i--;
|
||||||
final key = packKeys[i];
|
final key = packKeys[i];
|
||||||
final event = controller.room
|
final event = controller.room?.getState(
|
||||||
?.getState('im.ponies.room_emotes', packKeys[i]);
|
'im.ponies.room_emotes',
|
||||||
|
packKeys[i],
|
||||||
|
);
|
||||||
|
|
||||||
final eventPack =
|
final eventPack = event?.content
|
||||||
event?.content.tryGetMap<String, Object?>('pack');
|
.tryGetMap<String, Object?>('pack');
|
||||||
final packName =
|
final packName =
|
||||||
eventPack?.tryGet<String>('display_name') ??
|
eventPack?.tryGet<String>('display_name') ??
|
||||||
eventPack?.tryGet<String>('name') ??
|
eventPack?.tryGet<String>('name') ??
|
||||||
(key.isNotEmpty ? key : 'Default');
|
(key.isNotEmpty ? key : 'Default');
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
horizontal: 4.0,
|
|
||||||
),
|
|
||||||
child: FilterChip(
|
child: FilterChip(
|
||||||
label: Text(packName),
|
label: Text(packName),
|
||||||
selected: controller.stateKey == key ||
|
selected:
|
||||||
|
controller.stateKey == key ||
|
||||||
(controller.stateKey == null && key.isEmpty),
|
(controller.stateKey == null && key.isEmpty),
|
||||||
onSelected: controller.showSave
|
onSelected: controller.showSave
|
||||||
? null
|
? null
|
||||||
|
|
@ -153,8 +149,8 @@ class EmotesSettingsView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
body: MaxWidthBody(
|
body: MaxWidthBody(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: .stretch,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
if (controller.room != null) ...[
|
if (controller.room != null) ...[
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
@ -188,9 +184,10 @@ class EmotesSettingsView extends StatelessWidget {
|
||||||
? null
|
? null
|
||||||
: IconButton(
|
: IconButton(
|
||||||
icon: const Icon(Icons.link_outlined),
|
icon: const Icon(Icons.link_outlined),
|
||||||
onPressed: () =>
|
onPressed: () => UrlLauncher(
|
||||||
UrlLauncher(context, attributionUrl.toString())
|
context,
|
||||||
.launchUrl(),
|
attributionUrl.toString(),
|
||||||
|
).launchUrl(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -283,25 +280,23 @@ class EmotesSettingsView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
onSubmitted: (s) =>
|
onSubmitted: (s) =>
|
||||||
controller.submitImageAction(
|
controller.submitImageAction(
|
||||||
imageCode,
|
imageCode,
|
||||||
image,
|
image,
|
||||||
textEditingController,
|
textEditingController,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!controller.readonly)
|
if (!controller.readonly)
|
||||||
PopupMenuButton<ImagePackUsage>(
|
PopupMenuButton<ImagePackUsage>(
|
||||||
onSelected: (usage) => controller.toggleUsage(
|
onSelected: (usage) =>
|
||||||
imageCode,
|
controller.toggleUsage(imageCode, usage),
|
||||||
usage,
|
|
||||||
),
|
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: ImagePackUsage.sticker,
|
value: ImagePackUsage.sticker,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
if (image.usage?.contains(
|
if (image.usage?.contains(
|
||||||
ImagePackUsage.sticker,
|
ImagePackUsage.sticker,
|
||||||
|
|
@ -316,7 +311,7 @@ class EmotesSettingsView extends StatelessWidget {
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: ImagePackUsage.emoticon,
|
value: ImagePackUsage.emoticon,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
if (image.usage?.contains(
|
if (image.usage?.contains(
|
||||||
ImagePackUsage.emoticon,
|
ImagePackUsage.emoticon,
|
||||||
|
|
@ -363,10 +358,8 @@ class _EmoteImage extends StatelessWidget {
|
||||||
final key = 'sticker_preview_$mxc';
|
final key = 'sticker_preview_$mxc';
|
||||||
return InkWell(
|
return InkWell(
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
onTap: () => showDialog(
|
onTap: () =>
|
||||||
context: context,
|
showDialog(context: context, builder: (_) => MxcImageViewer(mxc)),
|
||||||
builder: (_) => MxcImageViewer(mxc),
|
|
||||||
),
|
|
||||||
child: MxcImage(
|
child: MxcImage(
|
||||||
key: ValueKey(key),
|
key: ValueKey(key),
|
||||||
cacheKey: key,
|
cacheKey: key,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class SettingsHomeserver extends StatefulWidget {
|
||||||
|
|
||||||
class SettingsHomeserverController extends State<SettingsHomeserver> {
|
class SettingsHomeserverController extends State<SettingsHomeserver> {
|
||||||
Future<({String name, String version, Uri federationBaseUrl})>
|
Future<({String name, String version, Uri federationBaseUrl})>
|
||||||
fetchServerInfo() async {
|
fetchServerInfo() async {
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
final domain = client.userID!.domain!;
|
final domain = client.userID!.domain!;
|
||||||
final httpClient = client.httpClient;
|
final httpClient = client.httpClient;
|
||||||
|
|
@ -37,15 +37,10 @@ class SettingsHomeserverController extends State<SettingsHomeserver> {
|
||||||
}
|
}
|
||||||
|
|
||||||
final serverVersionResult = await http.get(
|
final serverVersionResult = await http.get(
|
||||||
federationBaseUrl.resolveUri(
|
federationBaseUrl.resolveUri(Uri(path: '/_matrix/federation/v1/version')),
|
||||||
Uri(path: '/_matrix/federation/v1/version'),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
final {
|
final {
|
||||||
'server': {
|
'server': {'name': String name, 'version': String version},
|
||||||
'name': String name,
|
|
||||||
'version': String version,
|
|
||||||
},
|
|
||||||
} = Map<String, Map<String, dynamic>>.from(
|
} = Map<String, Map<String, dynamic>>.from(
|
||||||
jsonDecode(serverVersionResult.body),
|
jsonDecode(serverVersionResult.body),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,16 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
automaticallyImplyLeading: !FluffyThemes.isColumnMode(context),
|
automaticallyImplyLeading: !FluffyThemes.isColumnMode(context),
|
||||||
centerTitle: FluffyThemes.isColumnMode(context),
|
centerTitle: FluffyThemes.isColumnMode(context),
|
||||||
title: Text(
|
title: Text(
|
||||||
L10n.of(context)
|
L10n.of(
|
||||||
.aboutHomeserver(client.userID?.domain ?? 'Homeserver'),
|
context,
|
||||||
|
).aboutHomeserver(client.userID?.domain ?? 'Homeserver'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: MaxWidthBody(
|
body: MaxWidthBody(
|
||||||
withScrolling: true,
|
withScrolling: true,
|
||||||
child: SelectionArea(
|
child: SelectionArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
|
|
@ -68,9 +69,7 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
}
|
}
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return const Center(
|
return const Center(
|
||||||
child: CircularProgressIndicator.adaptive(
|
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final supportPage = data.supportPage;
|
final supportPage = data.supportPage;
|
||||||
|
|
@ -85,7 +84,7 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
if (supportPage != null)
|
if (supportPage != null)
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|
@ -93,32 +92,28 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
subtitle: Text(supportPage.toString()),
|
subtitle: Text(supportPage.toString()),
|
||||||
),
|
),
|
||||||
if (contacts != null)
|
if (contacts != null)
|
||||||
...contacts.map(
|
...contacts.map((contact) {
|
||||||
(contact) {
|
return ListTile(
|
||||||
return ListTile(
|
title: Text(
|
||||||
title: Text(
|
contact.role.localizedString(L10n.of(context)),
|
||||||
contact.role.localizedString(
|
),
|
||||||
L10n.of(context),
|
subtitle: Column(
|
||||||
),
|
mainAxisSize: .min,
|
||||||
),
|
children: [
|
||||||
subtitle: Column(
|
if (contact.emailAddress != null)
|
||||||
mainAxisSize: MainAxisSize.min,
|
TextButton(
|
||||||
children: [
|
onPressed: () {},
|
||||||
if (contact.emailAddress != null)
|
child: Text(contact.emailAddress!),
|
||||||
TextButton(
|
),
|
||||||
onPressed: () {},
|
if (contact.matrixId != null)
|
||||||
child: Text(contact.emailAddress!),
|
TextButton(
|
||||||
),
|
onPressed: () {},
|
||||||
if (contact.matrixId != null)
|
child: Text(contact.matrixId!),
|
||||||
TextButton(
|
),
|
||||||
onPressed: () {},
|
],
|
||||||
child: Text(contact.matrixId!),
|
),
|
||||||
),
|
);
|
||||||
],
|
}),
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -129,7 +124,7 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
final error = snapshot.error;
|
final error = snapshot.error;
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
Icons.error_outlined,
|
Icons.error_outlined,
|
||||||
|
|
@ -139,9 +134,7 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
Text(
|
Text(
|
||||||
error.toLocalizedString(context),
|
error.toLocalizedString(context),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(color: theme.colorScheme.error),
|
||||||
color: theme.colorScheme.error,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -149,13 +142,11 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
final data = snapshot.data;
|
final data = snapshot.data;
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return const Center(
|
return const Center(
|
||||||
child: CircularProgressIndicator.adaptive(
|
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10n.of(context).name),
|
title: Text(L10n.of(context).name),
|
||||||
|
|
@ -169,8 +160,9 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
title: const Text('Federation Base URL'),
|
title: const Text('Federation Base URL'),
|
||||||
subtitle: Linkify(
|
subtitle: Linkify(
|
||||||
text: data.federationBaseUrl.toString(),
|
text: data.federationBaseUrl.toString(),
|
||||||
textScaleFactor:
|
textScaleFactor: MediaQuery.textScalerOf(
|
||||||
MediaQuery.textScalerOf(context).scale(1),
|
context,
|
||||||
|
).scale(1),
|
||||||
options: const LinkifyOptions(humanize: false),
|
options: const LinkifyOptions(humanize: false),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
|
|
@ -191,7 +183,7 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
final error = snapshot.error;
|
final error = snapshot.error;
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: .center,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
Icons.error_outlined,
|
Icons.error_outlined,
|
||||||
|
|
@ -201,9 +193,7 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
Text(
|
Text(
|
||||||
error.toLocalizedString(context),
|
error.toLocalizedString(context),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(color: theme.colorScheme.error),
|
||||||
color: theme.colorScheme.error,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -211,14 +201,12 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
final wellKnown = snapshot.data;
|
final wellKnown = snapshot.data;
|
||||||
if (wellKnown == null) {
|
if (wellKnown == null) {
|
||||||
return const Center(
|
return const Center(
|
||||||
child: CircularProgressIndicator.adaptive(
|
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final identityServer = wellKnown.mIdentityServer;
|
final identityServer = wellKnown.mIdentityServer;
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
|
|
@ -233,8 +221,9 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
title: const Text('Base URL'),
|
title: const Text('Base URL'),
|
||||||
subtitle: Linkify(
|
subtitle: Linkify(
|
||||||
text: wellKnown.mHomeserver.baseUrl.toString(),
|
text: wellKnown.mHomeserver.baseUrl.toString(),
|
||||||
textScaleFactor:
|
textScaleFactor: MediaQuery.textScalerOf(
|
||||||
MediaQuery.textScalerOf(context).scale(1),
|
context,
|
||||||
|
).scale(1),
|
||||||
options: const LinkifyOptions(humanize: false),
|
options: const LinkifyOptions(humanize: false),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
|
|
@ -248,8 +237,9 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
title: const Text('Identity Server:'),
|
title: const Text('Identity Server:'),
|
||||||
subtitle: Linkify(
|
subtitle: Linkify(
|
||||||
text: identityServer.baseUrl.toString(),
|
text: identityServer.baseUrl.toString(),
|
||||||
textScaleFactor:
|
textScaleFactor: MediaQuery.textScalerOf(
|
||||||
MediaQuery.textScalerOf(context).scale(1),
|
context,
|
||||||
|
).scale(1),
|
||||||
options: const LinkifyOptions(humanize: false),
|
options: const LinkifyOptions(humanize: false),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
|
|
@ -262,15 +252,17 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
(entry) => ListTile(
|
(entry) => ListTile(
|
||||||
title: Text(entry.key),
|
title: Text(entry.key),
|
||||||
subtitle: Material(
|
subtitle: Material(
|
||||||
borderRadius:
|
borderRadius: BorderRadius.circular(
|
||||||
BorderRadius.circular(AppConfig.borderRadius),
|
AppConfig.borderRadius,
|
||||||
|
),
|
||||||
color: theme.colorScheme.surfaceContainer,
|
color: theme.colorScheme.surfaceContainer,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: Text(
|
child: Text(
|
||||||
const JsonEncoder.withIndent(' ')
|
const JsonEncoder.withIndent(
|
||||||
.convert(entry.value),
|
' ',
|
||||||
|
).convert(entry.value),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,12 @@ class SettingsIgnoreListView extends StatelessWidget {
|
||||||
return const Center(child: CircularProgressIndicator.adaptive());
|
return const Center(child: CircularProgressIndicator.adaptive());
|
||||||
}
|
}
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
controller: controller.controller,
|
controller: controller.controller,
|
||||||
|
|
@ -68,9 +68,7 @@ class SettingsIgnoreListView extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Divider(
|
Divider(color: theme.dividerColor),
|
||||||
color: theme.dividerColor,
|
|
||||||
),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: client.ignoredUsers.length,
|
itemCount: client.ignoredUsers.length,
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,8 @@ class SettingsNotificationsController extends State<SettingsNotifications> {
|
||||||
final success = await showFutureLoadingDialog(
|
final success = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => Matrix.of(context).client.deletePusher(
|
future: () => Matrix.of(context).client.deletePusher(
|
||||||
PusherId(
|
PusherId(appId: pusher.appId, pushkey: pusher.pushkey),
|
||||||
appId: pusher.appId,
|
),
|
||||||
pushkey: pusher.pushkey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (success.error != null) return;
|
if (success.error != null) return;
|
||||||
|
|
@ -66,10 +63,7 @@ class SettingsNotificationsController extends State<SettingsNotifications> {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
final updateFromSync = Matrix.of(context)
|
final updateFromSync = Matrix.of(context).client.onSync.stream
|
||||||
.client
|
|
||||||
.onSync
|
|
||||||
.stream
|
|
||||||
.where(
|
.where(
|
||||||
(syncUpdate) =>
|
(syncUpdate) =>
|
||||||
syncUpdate.accountData?.any(
|
syncUpdate.accountData?.any(
|
||||||
|
|
@ -78,17 +72,16 @@ class SettingsNotificationsController extends State<SettingsNotifications> {
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.first;
|
.first;
|
||||||
await Matrix.of(context).client.setPushRuleEnabled(
|
await Matrix.of(
|
||||||
kind,
|
context,
|
||||||
pushRule.ruleId,
|
).client.setPushRuleEnabled(kind, pushRule.ruleId, !pushRule.enabled);
|
||||||
!pushRule.enabled,
|
|
||||||
);
|
|
||||||
await updateFromSync;
|
await updateFromSync;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().w('Unable to toggle push rule', e, s);
|
Logs().w('Unable to toggle push rule', e, s);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(
|
||||||
.showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
context,
|
||||||
|
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -116,9 +109,7 @@ class SettingsNotificationsController extends State<SettingsNotifications> {
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
prettyJson(rule.toJson()),
|
prettyJson(rule.toJson()),
|
||||||
style: TextStyle(
|
style: TextStyle(color: theme.colorScheme.onSurface),
|
||||||
color: theme.colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -158,10 +149,7 @@ class SettingsNotificationsController extends State<SettingsNotifications> {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
final updateFromSync = Matrix.of(context)
|
final updateFromSync = Matrix.of(context).client.onSync.stream
|
||||||
.client
|
|
||||||
.onSync
|
|
||||||
.stream
|
|
||||||
.where(
|
.where(
|
||||||
(syncUpdate) =>
|
(syncUpdate) =>
|
||||||
syncUpdate.accountData?.any(
|
syncUpdate.accountData?.any(
|
||||||
|
|
@ -170,17 +158,14 @@ class SettingsNotificationsController extends State<SettingsNotifications> {
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.first;
|
.first;
|
||||||
await Matrix.of(context).client.deletePushRule(
|
await Matrix.of(context).client.deletePushRule(kind, rule.ruleId);
|
||||||
kind,
|
|
||||||
rule.ruleId,
|
|
||||||
);
|
|
||||||
await updateFromSync;
|
await updateFromSync;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().w('Unable to delete push rule', e, s);
|
Logs().w('Unable to delete push rule', e, s);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text(e.toLocalizedString(context))),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,12 @@ class SettingsNotificationsView extends StatelessWidget {
|
||||||
body: MaxWidthBody(
|
body: MaxWidthBody(
|
||||||
child: StreamBuilder(
|
child: StreamBuilder(
|
||||||
stream: Matrix.of(context).client.onSync.stream.where(
|
stream: Matrix.of(context).client.onSync.stream.where(
|
||||||
(syncUpdate) =>
|
(syncUpdate) =>
|
||||||
syncUpdate.accountData?.any(
|
syncUpdate.accountData?.any(
|
||||||
(accountData) => accountData.type == 'm.push_rules',
|
(accountData) => accountData.type == 'm.push_rules',
|
||||||
) ??
|
) ??
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
builder: (BuildContext context, _) {
|
builder: (BuildContext context, _) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return SelectionArea(
|
return SelectionArea(
|
||||||
|
|
@ -96,14 +96,14 @@ class SettingsNotificationsView extends StatelessWidget {
|
||||||
onChanged: controller.isLoading
|
onChanged: controller.isLoading
|
||||||
? null
|
? null
|
||||||
: rule.ruleId != '.m.rule.master' &&
|
: rule.ruleId != '.m.rule.master' &&
|
||||||
Matrix.of(context)
|
Matrix.of(
|
||||||
.client
|
context,
|
||||||
.allPushNotificationsMuted
|
).client.allPushNotificationsMuted
|
||||||
? null
|
? null
|
||||||
: (_) => controller.togglePushRule(
|
: (_) => controller.togglePushRule(
|
||||||
category.kind,
|
category.kind,
|
||||||
rule,
|
rule,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Divider(color: theme.dividerColor),
|
Divider(color: theme.dividerColor),
|
||||||
|
|
@ -118,8 +118,9 @@ class SettingsNotificationsView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
FutureBuilder<List<Pusher>?>(
|
FutureBuilder<List<Pusher>?>(
|
||||||
future: controller.pusherFuture ??=
|
future: controller.pusherFuture ??= Matrix.of(
|
||||||
Matrix.of(context).client.getPushers(),
|
context,
|
||||||
|
).client.getPushers(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
Center(
|
Center(
|
||||||
|
|
|
||||||
|
|
@ -55,13 +55,11 @@ class SettingsPasswordController extends State<SettingsPassword> {
|
||||||
try {
|
try {
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
await Matrix.of(context).client.changePassword(
|
await Matrix.of(context).client.changePassword(
|
||||||
newPassword1Controller.text,
|
newPassword1Controller.text,
|
||||||
oldPassword: oldPasswordController.text,
|
oldPassword: oldPasswordController.text,
|
||||||
);
|
);
|
||||||
scaffoldMessenger.showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(
|
SnackBar(content: Text(L10n.of(context).passwordHasBeenChanged)),
|
||||||
content: Text(L10n.of(context).passwordHasBeenChanged),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (mounted) context.pop();
|
if (mounted) context.pop();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,7 @@ class SettingsPasswordView extends StatelessWidget {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(title: Text(L10n.of(context).changePassword)),
|
||||||
title: Text(L10n.of(context).changePassword),
|
|
||||||
),
|
|
||||||
body: ListTileTheme(
|
body: ListTileTheme(
|
||||||
iconColor: theme.colorScheme.onSurface,
|
iconColor: theme.colorScheme.onSurface,
|
||||||
child: MaxWidthBody(
|
child: MaxWidthBody(
|
||||||
|
|
@ -69,8 +67,9 @@ class SettingsPasswordView extends StatelessWidget {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed:
|
onPressed: controller.loading
|
||||||
controller.loading ? null : controller.changePassword,
|
? null
|
||||||
|
: controller.changePassword,
|
||||||
child: controller.loading
|
child: controller.loading
|
||||||
? const LinearProgressIndicator()
|
? const LinearProgressIndicator()
|
||||||
: Text(L10n.of(context).changePassword),
|
: Text(L10n.of(context).changePassword),
|
||||||
|
|
|
||||||
|
|
@ -90,13 +90,13 @@ class SettingsSecurityController extends State<SettingsSecurity> {
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => Matrix.of(context).client.deactivateAccount(
|
future: () => Matrix.of(context).client.deactivateAccount(
|
||||||
auth: AuthenticationPassword(
|
auth: AuthenticationPassword(
|
||||||
password: input,
|
password: input,
|
||||||
identifier: AuthenticationUserIdentifier(
|
identifier: AuthenticationUserIdentifier(
|
||||||
user: Matrix.of(context).client.userID!,
|
user: Matrix.of(context).client.userID!,
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,9 +104,7 @@ class SettingsSecurityController extends State<SettingsSecurity> {
|
||||||
|
|
||||||
void changeShareKeysWith(ShareKeysWith? shareKeysWith) async {
|
void changeShareKeysWith(ShareKeysWith? shareKeysWith) async {
|
||||||
if (shareKeysWith == null) return;
|
if (shareKeysWith == null) return;
|
||||||
AppSettings.shareKeysWith.setItem(
|
AppSettings.shareKeysWith.setItem(shareKeysWith.name);
|
||||||
shareKeysWith.name,
|
|
||||||
);
|
|
||||||
Matrix.of(context).client.shareKeysWith = shareKeysWith;
|
Matrix.of(context).client.shareKeysWith = shareKeysWith;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,9 @@ class SettingsSecurityView extends StatelessWidget {
|
||||||
iconColor: theme.colorScheme.onSurface,
|
iconColor: theme.colorScheme.onSurface,
|
||||||
child: MaxWidthBody(
|
child: MaxWidthBody(
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future: Matrix.of(context)
|
future: Matrix.of(
|
||||||
.client
|
context,
|
||||||
.getCapabilities()
|
).client.getCapabilities().timeout(const Duration(seconds: 10)),
|
||||||
.timeout(const Duration(seconds: 10)),
|
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final capabilities = snapshot.data;
|
final capabilities = snapshot.data;
|
||||||
final error = snapshot.error;
|
final error = snapshot.error;
|
||||||
|
|
@ -61,8 +60,9 @@ class SettingsSecurityView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
SettingsSwitchListTile.adaptive(
|
SettingsSwitchListTile.adaptive(
|
||||||
title: L10n.of(context).sendTypingNotifications,
|
title: L10n.of(context).sendTypingNotifications,
|
||||||
subtitle:
|
subtitle: L10n.of(
|
||||||
L10n.of(context).sendTypingNotificationsDescription,
|
context,
|
||||||
|
).sendTypingNotificationsDescription,
|
||||||
setting: AppSettings.sendTypingNotifications,
|
setting: AppSettings.sendTypingNotifications,
|
||||||
),
|
),
|
||||||
SettingsSwitchListTile.adaptive(
|
SettingsSwitchListTile.adaptive(
|
||||||
|
|
@ -103,14 +103,16 @@ class SettingsSecurityView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Material(
|
title: Material(
|
||||||
borderRadius:
|
borderRadius: BorderRadius.circular(
|
||||||
BorderRadius.circular(AppConfig.borderRadius / 2),
|
AppConfig.borderRadius / 2,
|
||||||
|
),
|
||||||
color: theme.colorScheme.onInverseSurface,
|
color: theme.colorScheme.onInverseSurface,
|
||||||
child: DropdownButton<ShareKeysWith>(
|
child: DropdownButton<ShareKeysWith>(
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
borderRadius:
|
borderRadius: BorderRadius.circular(
|
||||||
BorderRadius.circular(AppConfig.borderRadius / 2),
|
AppConfig.borderRadius / 2,
|
||||||
|
),
|
||||||
underline: const SizedBox.shrink(),
|
underline: const SizedBox.shrink(),
|
||||||
value: Matrix.of(context).client.shareKeysWith,
|
value: Matrix.of(context).client.shareKeysWith,
|
||||||
items: ShareKeysWith.values
|
items: ShareKeysWith.values
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue