Merge branch 'malin/addRequireTrailingCommasRule' into 'main'

refactor: Added and applied require_trailing_commas linter rule

See merge request famedly/fluffychat!1091
This commit is contained in:
Krille 2023-03-02 10:28:45 +00:00
commit e461cb1f53
112 changed files with 3509 additions and 2898 deletions

View File

@ -8,6 +8,7 @@ linter:
- prefer_final_locals
- prefer_final_in_for_each
- sort_pub_dependencies
- require_trailing_commas
analyzer:
errors:

View File

@ -145,12 +145,14 @@ void main() {
await tester.waitFor(
find.descendant(
of: find.byType(InvitationSelectionView),
matching: find.byType(TextField)),
matching: find.byType(TextField),
),
);
await tester.enterText(
find.descendant(
of: find.byType(InvitationSelectionView),
matching: find.byType(TextField)),
matching: find.byType(TextField),
),
Users.user2.name,
);
@ -160,14 +162,17 @@ void main() {
await Future.delayed(const Duration(milliseconds: 1000));
await tester.pumpAndSettle();
await tester.tap(find
await tester.tap(
find
.descendant(
of: find.descendant(
of: find.byType(InvitationSelectionView),
matching: find.byType(ListTile),
),
matching: find.text(Users.user2.name))
.last);
matching: find.text(Users.user2.name),
)
.last,
);
await tester.pumpAndSettle();
await tester.waitFor(find.maybeUppercaseText('Yes'));

View File

@ -144,7 +144,8 @@ extension DefaultFlowExtensions on WidgetTester {
do {
if (DateTime.now().isAfter(end)) {
throw Exception(
'Timed out waiting for HomeserverPicker or ChatListViewBody');
'Timed out waiting for HomeserverPicker or ChatListViewBody',
);
}
await pumpAndSettle();

View File

@ -71,7 +71,8 @@ abstract class AppConfig {
} catch (e) {
Logs().w(
'Invalid color in config.json! Please make sure to define the color in this format: "0xffdd0000"',
e);
e,
);
}
}
if (json['application_name'] is String) {

View File

@ -70,7 +70,10 @@ class AppRoutes {
widget: const ChatDetails(),
stackedRoutes: _chatDetailsRoutes,
),
VWidget(path: ':roomid', widget: const Chat(), stackedRoutes: [
VWidget(
path: ':roomid',
widget: const Chat(),
stackedRoutes: [
VWidget(
path: 'encryption',
widget: const ChatEncryptionSettings(),
@ -84,7 +87,8 @@ class AppRoutes {
widget: const ChatDetails(),
stackedRoutes: _chatDetailsRoutes,
),
]),
],
),
VWidget(
path: '/settings',
widget: const Settings(),
@ -277,7 +281,8 @@ class AppRoutes {
widget: const SignupPage(),
buildTransition: _fadeTransition,
),
]),
],
),
VWidget(
path: 'logs',
widget: const LogViewer(),
@ -368,7 +373,8 @@ class AppRoutes {
widget: const SignupPage(),
buildTransition: _fadeTransition,
),
]),
],
),
],
),
VWidget(

View File

@ -95,7 +95,8 @@ class AddStoryController extends State<AddStoryPage> {
bytes: bytes,
name: picked.name,
);
});
},
);
setState(() {
image = matrixFile.result;

View File

@ -99,19 +99,23 @@ class InviteStoryPageState extends State<InviteStoryPage> {
final error = snapshot.error;
if (error != null) {
return Center(
child: Text(error.toLocalizedString(context)));
child: Text(error.toLocalizedString(context)),
);
}
return const Center(
child: CircularProgressIndicator.adaptive());
child: CircularProgressIndicator.adaptive(),
);
}
_undecided = contacts.map((u) => u.id).toSet();
return ListView.builder(
itemCount: contacts.length,
itemBuilder: (context, i) => SwitchListTile.adaptive(
value: _invite.contains(contacts[i].id),
onChanged: (b) => setState(() => b
onChanged: (b) => setState(
() => b
? _invite.add(contacts[i].id)
: _invite.remove(contacts[i].id)),
: _invite.remove(contacts[i].id),
),
secondary: Avatar(
mxContent: contacts[i].avatarUrl,
name: contacts[i].calcDisplayname(),
@ -119,7 +123,8 @@ class InviteStoryPageState extends State<InviteStoryPage> {
title: Text(contacts[i].calcDisplayname()),
),
);
}),
},
),
),
],
),

View File

@ -39,16 +39,19 @@ class ArchiveView extends StatelessWidget {
child: Text(
L10n.of(context)!.oopsSomethingWentWrong,
textAlign: TextAlign.center,
));
),
);
}
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator.adaptive(strokeWidth: 2));
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
);
} else {
archive = snapshot.data;
if (archive == null || archive!.isEmpty) {
return const Center(
child: Icon(Icons.archive_outlined, size: 80));
child: Icon(Icons.archive_outlined, size: 80),
);
}
return ListView.builder(
itemCount: archive!.length,

View File

@ -246,7 +246,8 @@ class BootstrapDialogState extends State<BootstrapDialog> {
body: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: FluffyThemes.columnWidth * 1.5),
maxWidth: FluffyThemes.columnWidth * 1.5,
),
child: ListView(
padding: const EdgeInsets.all(16.0),
children: [
@ -258,7 +259,8 @@ class BootstrapDialogState extends State<BootstrapDialog> {
color: Theme.of(context).colorScheme.primary,
),
subtitle: Text(
L10n.of(context)!.pleaseEnterRecoveryKeyDescription),
L10n.of(context)!.pleaseEnterRecoveryKeyDescription,
),
),
const Divider(height: 32),
TextField(
@ -274,10 +276,9 @@ class BootstrapDialogState extends State<BootstrapDialog> {
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(16),
hintStyle: TextStyle(
fontFamily: Theme.of(context)
.textTheme
.bodyLarge
?.fontFamily),
fontFamily:
Theme.of(context).textTheme.bodyLarge?.fontFamily,
),
hintText: L10n.of(context)!.recoveryKey,
errorText: _recoveryKeyInputError,
),
@ -307,8 +308,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
keyOrPassphrase: key,
);
Logs().d('SSSS unlocked');
await bootstrap
.client.encryption!.crossSigning
await bootstrap.client.encryption!.crossSigning
.selfSign(
keyOrPassphrase: key,
);
@ -316,22 +316,28 @@ class BootstrapDialogState extends State<BootstrapDialog> {
await bootstrap.openExistingSsss();
} catch (e, s) {
Logs().w('Unable to unlock SSSS', e, s);
setState(() => _recoveryKeyInputError =
L10n.of(context)!.oopsSomethingWentWrong);
setState(
() => _recoveryKeyInputError =
L10n.of(context)!.oopsSomethingWentWrong,
);
} finally {
setState(
() => _recoveryKeyInputLoading = false);
() => _recoveryKeyInputLoading = false,
);
}
}),
},
),
const SizedBox(height: 16),
Row(children: [
Row(
children: [
const Expanded(child: Divider()),
Padding(
padding: const EdgeInsets.all(12.0),
child: Text(L10n.of(context)!.or),
),
const Expanded(child: Divider()),
]),
],
),
const SizedBox(height: 16),
ElevatedButton.icon(
icon: const Icon(Icons.cast_connected_outlined),
@ -408,11 +414,13 @@ class BootstrapDialogState extends State<BootstrapDialog> {
case BootstrapState.error:
titleText = L10n.of(context)!.oopsSomethingWentWrong;
body = const Icon(Icons.error_outline, color: Colors.red, size: 40);
buttons.add(AdaptiveFlatButton(
buttons.add(
AdaptiveFlatButton(
label: L10n.of(context)!.close,
onPressed: () =>
Navigator.of(context, rootNavigator: false).pop<bool>(false),
));
),
);
break;
case BootstrapState.done:
titleText = L10n.of(context)!.everythingReady;
@ -423,11 +431,13 @@ class BootstrapDialogState extends State<BootstrapDialog> {
Text(L10n.of(context)!.yourChatBackupHasBeenSetUp),
],
);
buttons.add(AdaptiveFlatButton(
buttons.add(
AdaptiveFlatButton(
label: L10n.of(context)!.close,
onPressed: () =>
Navigator.of(context, rootNavigator: false).pop<bool>(false),
));
),
);
break;
}
}

View File

@ -75,7 +75,8 @@ class AddWidgetTileState extends State<AddWidgetTile> {
Navigator.of(context).pop();
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context)!.errorAddingWidget)));
SnackBar(content: Text(L10n.of(context)!.errorAddingWidget)),
);
}
}

View File

@ -26,12 +26,15 @@ class AddWidgetTileView extends StatelessWidget {
'm.jitsi': Text(L10n.of(context)!.widgetJitsi),
'm.video': Text(L10n.of(context)!.widgetVideo),
'm.custom': Text(L10n.of(context)!.widgetCustom),
}.map((key, value) => MapEntry(
}.map(
(key, value) => MapEntry(
key,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: value,
))),
),
),
),
onValueChanged: controller.setWidgetType,
),
Padding(

View File

@ -82,10 +82,12 @@ class ChatController extends State<Chat> {
final matrixFiles = <MatrixFile>[];
for (var i = 0; i < bytesList.result!.length; i++) {
matrixFiles.add(MatrixFile(
matrixFiles.add(
MatrixFile(
bytes: bytesList.result![i],
name: details.files[i].name,
).detectFileType);
).detectFileType,
);
}
await showDialog(
@ -139,7 +141,8 @@ class ChatController extends State<Chat> {
final userId = room?.directChatMatrixID;
if (room == null || userId == null) {
throw Exception(
'Try to recreate a room with is not a DM room. This should not be possible from the UI!');
'Try to recreate a room with is not a DM room. This should not be possible from the UI!',
);
}
final success = await showFutureLoadingDialog(
context: context,
@ -150,7 +153,8 @@ class ChatController extends State<Chat> {
await room.leave();
await waitForSync;
return await client.startDirectChat(userId);
});
},
);
final roomId = success.result;
if (roomId == null) return;
VRouter.of(context).toSegments(['rooms', roomId]);
@ -160,7 +164,8 @@ class ChatController extends State<Chat> {
final room = this.room;
if (room == null) {
throw Exception(
'Leave room button clicked while room is null. This should not be possible from the UI!');
'Leave room button clicked while room is null. This should not be possible from the UI!',
);
}
final success = await showFutureLoadingDialog(
context: context,
@ -327,10 +332,12 @@ class ChatController extends State<Chat> {
}
// ignore: unawaited_futures
room!.sendTextEvent(sendController.text,
room!.sendTextEvent(
sendController.text,
inReplyTo: replyEvent,
editEventId: editEvent?.eventId,
parseCommands: parseCommands);
parseCommands: parseCommands,
);
sendController.value = TextEditingValue(
text: pendingText,
selection: const TextSelection.collapsed(offset: 0),
@ -354,10 +361,12 @@ class ChatController extends State<Chat> {
useRootNavigator: false,
builder: (c) => SendFileDialog(
files: result
.map((xfile) => MatrixFile(
.map(
(xfile) => MatrixFile(
bytes: xfile.toUint8List(),
name: xfile.fileName!,
).detectFileType)
).detectFileType,
)
.toList(),
room: room!,
),
@ -375,10 +384,12 @@ class ChatController extends State<Chat> {
useRootNavigator: false,
builder: (c) => SendFileDialog(
files: result
.map((xfile) => MatrixFile(
.map(
(xfile) => MatrixFile(
bytes: xfile.toUint8List(),
name: xfile.fileName!,
).detectFileType)
).detectFileType,
)
.toList(),
room: room!,
),
@ -538,7 +549,8 @@ class ChatController extends State<Chat> {
if (copyString.isNotEmpty) copyString += '\n\n';
copyString += event.getDisplayEvent(timeline!).calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)!),
withSenderNamePrefix: true);
withSenderNamePrefix: true,
);
}
return copyString;
}
@ -572,7 +584,8 @@ class ChatController extends State<Chat> {
key: 0,
label: L10n.of(context)!.inoffensive,
),
]);
],
);
if (score == null) return;
final reason = await showTextInputDialog(
useRootNavigator: false,
@ -580,7 +593,8 @@ class ChatController extends State<Chat> {
title: L10n.of(context)!.whyDoYouWantToReportThis,
okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context)!.cancel,
textFields: [DialogTextField(hintText: L10n.of(context)!.reason)]);
textFields: [DialogTextField(hintText: L10n.of(context)!.reason)],
);
if (reason == null || reason.single.isEmpty) return;
final result = await showFutureLoadingDialog(
context: context,
@ -597,7 +611,8 @@ class ChatController extends State<Chat> {
selectedEvents.clear();
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context)!.contentHasBeenReported)));
SnackBar(content: Text(L10n.of(context)!.contentHasBeenReported)),
);
}
void redactEventsAction() async {
@ -620,7 +635,8 @@ class ChatController extends State<Chat> {
} else {
final client = currentRoomBundle.firstWhere(
(cl) => selectedEvents.first.senderId == cl!.userID,
orElse: () => null);
orElse: () => null,
);
if (client == null) {
return;
}
@ -630,7 +646,8 @@ class ChatController extends State<Chat> {
} else {
await event.remove();
}
});
},
);
}
setState(() {
showEmojiPicker = false;
@ -740,7 +757,8 @@ class ChatController extends State<Chat> {
eventIndex =
timeline!.events.indexWhere((e) => e.eventId == eventId);
}
});
},
);
}
if (!mounted) {
return;
@ -811,7 +829,8 @@ class ChatController extends State<Chat> {
sendController
..text = sendController.text.characters.skipLast(1).toString()
..selection = TextSelection.fromPosition(
TextPosition(offset: sendController.text.length));
TextPosition(offset: sendController.text.length),
);
break;
}
}
@ -847,7 +866,8 @@ class ChatController extends State<Chat> {
void editSelectedEventAction() {
final client = currentRoomBundle.firstWhere(
(cl) => selectedEvents.first.senderId == cl!.userID,
orElse: () => null);
orElse: () => null,
);
if (client == null) {
return;
}
@ -855,10 +875,12 @@ class ChatController extends State<Chat> {
setState(() {
pendingText = sendController.text;
editEvent = selectedEvents.first;
inputText = sendController.text = editEvent!
.getDisplayEvent(timeline!)
.calcLocalizedBodyFallback(MatrixLocals(L10n.of(context)!),
withSenderNamePrefix: false, hideReply: true);
inputText = sendController.text =
editEvent!.getDisplayEvent(timeline!).calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)!),
withSenderNamePrefix: false,
hideReply: true,
);
selectedEvents.clear();
});
inputFocus.requestFocus();
@ -881,10 +903,12 @@ class ChatController extends State<Chat> {
}
final result = await showFutureLoadingDialog(
context: context,
future: () => room!.client.joinRoom(room!
future: () => room!.client.joinRoom(
room!
.getState(EventTypes.RoomTombstone)!
.parsedTombstoneContent
.replacementRoom),
.replacementRoom,
),
);
await showFutureLoadingDialog(
context: context,
@ -1079,7 +1103,8 @@ class ChatController extends State<Chat> {
final success = await showFutureLoadingDialog(
context: context,
future: () =>
Matrix.of(context).voipPlugin!.voip.requestTurnServerCredentials());
Matrix.of(context).voipPlugin!.voip.requestTurnServerCredentials(),
);
if (success.result != null) {
final voipPlugin = Matrix.of(context).voipPlugin;
try {

View File

@ -86,7 +86,8 @@ class ChatEventList extends StatelessWidget {
index: i - 1,
controller: controller.scrollController,
child: event.isVisibleInGui
? Message(event,
? Message(
event,
onSwipe: (direction) =>
controller.replyAction(replyTo: event),
onInfoTab: controller.showEventInfo,
@ -108,7 +109,8 @@ class ChatEventList extends StatelessWidget {
timeline: controller.timeline!,
nextEvent: i < controller.timeline!.events.length
? controller.timeline!.events[i]
: null)
: null,
)
: Container(),
);
},

View File

@ -300,7 +300,8 @@ class _ChatAccountPicker extends StatelessWidget {
builder: (context, snapshot) => PopupMenuButton<String>(
onSelected: _popupMenuButtonSelected,
itemBuilder: (BuildContext context) => clients
.map((client) => PopupMenuItem<String>(
.map(
(client) => PopupMenuItem<String>(
value: client!.userID,
child: FutureBuilder<Profile>(
future: client.fetchOwnProfile(),
@ -311,12 +312,12 @@ class _ChatAccountPicker extends StatelessWidget {
client.userID!.localpart,
size: 20,
),
title:
Text(snapshot.data?.displayName ?? client.userID!),
title: Text(snapshot.data?.displayName ?? client.userID!),
contentPadding: const EdgeInsets.all(0),
),
),
))
),
)
.toList(),
child: Avatar(
mxContent: snapshot.data?.avatarUrl,

View File

@ -53,7 +53,8 @@ class ChatView extends StatelessWidget {
icon: Icon(Icons.adaptive.share),
tooltip: L10n.of(context)!.share,
onPressed: () => controller.saveSelectedEvent(context),
)),
),
),
if (controller.canRedactSelectedEvents)
IconButton(
icon: const Icon(Icons.delete_outlined),
@ -155,7 +156,9 @@ class ChatView extends StatelessWidget {
if (controller.room!.membership == Membership.invite) {
showFutureLoadingDialog(
context: context, future: () => controller.room!.join());
context: context,
future: () => controller.room!.join(),
);
}
final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 16.0 : 8.0;
final colorScheme = Theme.of(context).colorScheme;
@ -254,8 +257,9 @@ class ChatView extends StatelessWidget {
builder: (context) {
if (controller.timeline == null) {
return const Center(
child: CircularProgressIndicator
.adaptive(strokeWidth: 2),
child:
CircularProgressIndicator.adaptive(
strokeWidth: 2,),
);
}
@ -263,7 +267,8 @@ class ChatView extends StatelessWidget {
controller: controller,
);
},
)),
),
),
),
if (controller.room!.canSendDefaultMessages &&
controller.room!.membership == Membership.join)
@ -274,7 +279,8 @@ class ChatView extends StatelessWidget {
right: bottomSheetPadding,
),
constraints: const BoxConstraints(
maxWidth: FluffyThemes.columnWidth * 2.5),
maxWidth: FluffyThemes.columnWidth * 2.5,
),
alignment: Alignment.center,
child: Material(
borderRadius: const BorderRadius.only(
@ -324,7 +330,8 @@ class ChatView extends StatelessWidget {
onPressed:
controller.recreateChat,
label: Text(
L10n.of(context)!.reopenChat),
L10n.of(context)!.reopenChat,
),
),
],
)

View File

@ -15,15 +15,18 @@ class EditWidgetsDialog extends StatelessWidget {
return SimpleDialog(
title: Text(L10n.of(context)!.editWidgets),
children: [
...room.widgets.map((e) => ListTile(
...room.widgets.map(
(e) => ListTile(
title: Text(e.name ?? e.type),
leading: IconButton(
onPressed: () {
room.deleteWidget(e.id!);
Navigator.of(context).pop();
},
icon: const Icon(Icons.delete)),
)),
icon: const Icon(Icons.delete),
),
),
),
AddWidgetTile(room: room),
],
);

View File

@ -26,21 +26,21 @@ class EncryptionButton extends StatelessWidget {
? L10n.of(context)!.encrypted
: L10n.of(context)!.encryptionNotEnabled,
icon: Icon(
room.encrypted
? Icons.lock_outlined
: Icons.lock_open_outlined,
room.encrypted ? Icons.lock_outlined : Icons.lock_open_outlined,
size: 20,
color: room.joinRules != JoinRules.public &&
!room.encrypted
color: room.joinRules != JoinRules.public && !room.encrypted
? Colors.red
: room.joinRules != JoinRules.public &&
snapshot.data ==
EncryptionHealthState.unverifiedDevices
? Colors.orange
: null),
: null,
),
onPressed: () => VRouter.of(context)
.toSegments(['rooms', room.id, 'encryption']),
));
});
),
);
},
);
}
}

View File

@ -54,7 +54,8 @@ class EventInfoDialog extends StatelessWidget {
),
title: Text(L10n.of(context)!.sender),
subtitle: Text(
'${event.senderFromMemoryOrFallback.calcDisplayname()} [${event.senderId}]'),
'${event.senderFromMemoryOrFallback.calcDisplayname()} [${event.senderId}]',
),
),
ListTile(
title: Text(L10n.of(context)!.time),

View File

@ -69,7 +69,8 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
if (!kIsWeb) {
final tempDir = await getTemporaryDirectory();
final fileName = Uri.encodeComponent(
widget.event.attachmentOrThumbnailMxcUrl()!.pathSegments.last);
widget.event.attachmentOrThumbnailMxcUrl()!.pathSegments.last,
);
file = File('${tempDir.path}/${fileName}_${matrixFile.name}');
await file.writeAsBytes(matrixFile.bytes);
}
@ -224,11 +225,14 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
for (var i = 0; i < AudioPlayerWidget.wavesCount; i++)
Expanded(
child: InkWell(
onTap: () => audioPlayer?.seek(Duration(
onTap: () => audioPlayer?.seek(
Duration(
milliseconds:
(maxPosition / AudioPlayerWidget.wavesCount)
.round() *
i)),
i,
),
),
child: Container(
height: 32,
alignment: Alignment.center,
@ -240,7 +244,8 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
color: widget.color,
borderRadius: BorderRadius.circular(64),
),
height: 32 * (waveform[i] / 1024)),
height: 32 * (waveform[i] / 1024),
),
),
),
),

View File

@ -38,9 +38,14 @@ class HtmlMessage extends StatelessWidget {
// miss-matching tags, and this way we actually correctly identify what we want to strip and, well,
// strip it.
final renderHtml = html.replaceAll(
RegExp('<mx-reply>.*</mx-reply>',
caseSensitive: false, multiLine: false, dotAll: true),
'');
RegExp(
'<mx-reply>.*</mx-reply>',
caseSensitive: false,
multiLine: false,
dotAll: true,
),
'',
);
// there is no need to pre-validate the html, as we validate it while rendering
@ -61,8 +66,12 @@ class HtmlMessage extends StatelessWidget {
maxLines: maxLines,
onLinkTap: (url) => UrlLauncher(context, url).launchUrl(),
onPillTap: (url) => UrlLauncher(context, url).launchUrl(),
getMxcUrl: (String mxc, double? width, double? height,
{bool? animated = false}) {
getMxcUrl: (
String mxc,
double? width,
double? height, {
bool? animated = false,
}) {
final ratio = MediaQuery.of(context).devicePixelRatio;
return Uri.parse(mxc)
.getThumbnail(
@ -77,7 +86,8 @@ class HtmlMessage extends StatelessWidget {
onImageTap: (String mxc) => showDialog(
context: Matrix.of(context).navigatorContext,
useRootNavigator: false,
builder: (_) => ImageViewer(Event(
builder: (_) => ImageViewer(
Event(
type: EventTypes.Message,
content: <String, dynamic>{
'body': mxc,
@ -87,7 +97,10 @@ class HtmlMessage extends StatelessWidget {
senderId: room.client.userID!,
originServerTs: DateTime.now(),
eventId: 'fake_event',
room: room))),
room: room,
),
),
),
setCodeLanguage: (String key, String value) async {
await matrix.store.setItem('${SettingKeys.codeLanguage}.$key', value);
},

View File

@ -27,8 +27,9 @@ class Message extends StatelessWidget {
final bool selected;
final Timeline timeline;
const Message(this.event,
{this.nextEvent,
const Message(
this.event, {
this.nextEvent,
this.longPressSelect = false,
this.onSelect,
this.onInfoTab,
@ -37,8 +38,8 @@ class Message extends StatelessWidget {
required this.onSwipe,
this.selected = false,
required this.timeline,
Key? key})
: super(key: key);
Key? key,
}) : super(key: key);
/// Indicates wheither the user may use a mouse instead
/// of touchscreen.
@ -126,13 +127,15 @@ class Message extends StatelessWidget {
height: 16 * AppConfig.bubbleSizeFactor,
child: event.status == EventStatus.sending
? const CircularProgressIndicator.adaptive(
strokeWidth: 2)
strokeWidth: 2,
)
: event.status == EventStatus.error
? const Icon(Icons.error, color: Colors.red)
: null,
),
),
))
),
)
: FutureBuilder<User?>(
future: event.fetchSenderUser(),
builder: (context, snapshot) {
@ -142,7 +145,8 @@ class Message extends StatelessWidget {
name: user.calcDisplayname(),
onTap: () => onAvatarTab!(event),
);
}),
},
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -171,7 +175,8 @@ class Message extends StatelessWidget {
: displayname.lightColorText),
),
);
}),
},
),
),
Container(
alignment: alignment,
@ -198,7 +203,8 @@ class Message extends StatelessWidget {
? EdgeInsets.zero
: EdgeInsets.all(16 * AppConfig.bubbleSizeFactor),
constraints: const BoxConstraints(
maxWidth: FluffyThemes.columnWidth * 1.5),
maxWidth: FluffyThemes.columnWidth * 1.5,
),
child: Stack(
children: <Widget>[
Column(
@ -233,11 +239,14 @@ class Message extends StatelessWidget {
child: AbsorbPointer(
child: Container(
margin: EdgeInsets.symmetric(
vertical: 4.0 *
AppConfig.bubbleSizeFactor),
child: ReplyContent(replyEvent,
vertical:
4.0 * AppConfig.bubbleSizeFactor,
),
child: ReplyContent(
replyEvent,
ownMessage: ownMessage,
timeline: timeline),
timeline: timeline,
),
),
),
);
@ -249,10 +258,13 @@ class Message extends StatelessWidget {
onInfoTab: onInfoTab,
),
if (event.hasAggregatedEvents(
timeline, RelationshipTypes.edit))
timeline,
RelationshipTypes.edit,
))
Padding(
padding: EdgeInsets.only(
top: 4.0 * AppConfig.bubbleSizeFactor),
top: 4.0 * AppConfig.bubbleSizeFactor,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
@ -301,7 +313,8 @@ class Message extends StatelessWidget {
Padding(
padding: displayTime
? EdgeInsets.symmetric(
vertical: 8.0 * AppConfig.bubbleSizeFactor)
vertical: 8.0 * AppConfig.bubbleSizeFactor,
)
: EdgeInsets.zero,
child: Center(
child: Material(
@ -311,7 +324,8 @@ class Message extends StatelessWidget {
.colorScheme
.background
.withOpacity(0.33),
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
borderRadius:
BorderRadius.circular(AppConfig.borderRadius / 2),
clipBehavior: Clip.antiAlias,
child: Padding(
padding: const EdgeInsets.all(6.0),
@ -320,7 +334,8 @@ class Message extends StatelessWidget {
style: TextStyle(fontSize: 14 * AppConfig.fontSizeFactor),
),
),
)),
),
),
),
row,
if (event.hasAggregatedEvents(timeline, RelationshipTypes.reaction))

View File

@ -27,21 +27,27 @@ class MessageContent extends StatelessWidget {
final Color textColor;
final void Function(Event)? onInfoTab;
const MessageContent(this.event,
{this.onInfoTab, Key? key, required this.textColor})
: super(key: key);
const MessageContent(
this.event, {
this.onInfoTab,
Key? key,
required this.textColor,
}) : super(key: key);
void _verifyOrRequestKey(BuildContext context) async {
final l10n = L10n.of(context)!;
if (event.content['can_request_session'] != true) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
event.type == EventTypes.Encrypted
? l10n.needPantalaimonWarning
: event.calcLocalizedBodyFallback(
MatrixLocals(l10n),
),
)));
),
),
);
return;
}
final client = Matrix.of(context).client;
@ -216,27 +222,32 @@ class MessageContent extends StatelessWidget {
future: event.redactedBecause?.fetchSenderUser(),
builder: (context, snapshot) {
return _ButtonContent(
label: L10n.of(context)!.redactedAnEvent(snapshot.data
?.calcDisplayname() ??
event.senderFromMemoryOrFallback.calcDisplayname()),
label: L10n.of(context)!.redactedAnEvent(
snapshot.data?.calcDisplayname() ??
event.senderFromMemoryOrFallback.calcDisplayname(),
),
icon: const Icon(Icons.delete_outlined),
textColor: buttonTextColor,
onPressed: () => onInfoTab!(event),
);
});
},
);
}
final bigEmotes = event.onlyEmotes &&
event.numberEmotes > 0 &&
event.numberEmotes <= 10;
return FutureBuilder<String>(
future: event.calcLocalizedBody(MatrixLocals(L10n.of(context)!),
hideReply: true),
future: event.calcLocalizedBody(
MatrixLocals(L10n.of(context)!),
hideReply: true,
),
builder: (context, snapshot) {
return LinkText(
text: snapshot.data ??
event.calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)!),
hideReply: true),
hideReply: true,
),
textStyle: TextStyle(
color: textColor,
fontSize: bigEmotes ? fontSize * 3 : fontSize,
@ -251,7 +262,8 @@ class MessageContent extends StatelessWidget {
),
onLinkTap: (url) => UrlLauncher(context, url).launchUrl(),
);
});
},
);
}
case EventTypes.CallInvite:
return FutureBuilder<User?>(
@ -260,12 +272,14 @@ class MessageContent extends StatelessWidget {
return _ButtonContent(
label: L10n.of(context)!.startedACall(
snapshot.data?.calcDisplayname() ??
event.senderFromMemoryOrFallback.calcDisplayname()),
event.senderFromMemoryOrFallback.calcDisplayname(),
),
icon: const Icon(Icons.phone_outlined),
textColor: buttonTextColor,
onPressed: () => onInfoTab!(event),
);
});
},
);
default:
return FutureBuilder<User?>(
future: event.fetchSenderUser(),
@ -274,12 +288,14 @@ class MessageContent extends StatelessWidget {
label: L10n.of(context)!.userSentUnknownEvent(
snapshot.data?.calcDisplayname() ??
event.senderFromMemoryOrFallback.calcDisplayname(),
event.type),
event.type,
),
icon: const Icon(Icons.info_outlined),
textColor: buttonTextColor,
onPressed: () => onInfoTab!(event),
);
});
},
);
}
}
}

View File

@ -46,7 +46,10 @@ class MessageReactions extends StatelessWidget {
final reactionList = reactionMap.values.toList();
reactionList.sort((a, b) => b.count - a.count > 0 ? 1 : -1);
return Wrap(spacing: 4.0, runSpacing: 4.0, children: [
return Wrap(
spacing: 4.0,
runSpacing: 4.0,
children: [
...reactionList
.map(
(r) => _Reaction(
@ -55,9 +58,11 @@ class MessageReactions extends StatelessWidget {
reacted: r.reacted,
onTap: () {
if (r.reacted) {
final evt = allReactionEvents.firstWhereOrNull((e) =>
final evt = allReactionEvents.firstWhereOrNull(
(e) =>
e.senderId == e.room.client.userID &&
e.content['m.relates_to']['key'] == r.key);
e.content['m.relates_to']['key'] == r.key,
);
if (evt != null) {
showFutureLoadingDialog(
context: context,
@ -84,7 +89,8 @@ class MessageReactions extends StatelessWidget {
child: CircularProgressIndicator.adaptive(strokeWidth: 1),
),
),
]);
],
);
}
}
@ -121,11 +127,13 @@ class _Reaction extends StatelessWidget {
height: fontSize,
),
const SizedBox(width: 4),
Text(count.toString(),
Text(
count.toString(),
style: TextStyle(
color: textColor,
fontSize: DefaultTextStyle.of(context).style.fontSize,
)),
),
),
],
);
} else {
@ -133,11 +141,13 @@ class _Reaction extends StatelessWidget {
if (renderKey.length > 10) {
renderKey = renderKey.getRange(0, 9) + Characters('');
}
content = Text('$renderKey $count',
content = Text(
'$renderKey $count',
style: TextStyle(
color: textColor,
fontSize: DefaultTextStyle.of(context).style.fontSize,
));
),
);
}
return InkWell(
onTap: () => onTap != null ? onTap!() : null,

View File

@ -98,7 +98,8 @@ class ReplyContent extends StatelessWidget {
fontSize: fontSize,
),
);
}),
},
),
replyBody,
],
),

View File

@ -40,7 +40,8 @@ class StateMessage extends StatelessWidget {
event.redacted ? TextDecoration.lineThrough : null,
),
);
}),
},
),
),
),
);

View File

@ -9,9 +9,11 @@ class VerificationRequestContent extends StatelessWidget {
final Event event;
final Timeline timeline;
const VerificationRequestContent(
{required this.event, required this.timeline, Key? key})
: super(key: key);
const VerificationRequestContent({
required this.event,
required this.timeline,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -43,18 +45,22 @@ class VerificationRequestContent extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.lock_outlined,
Icon(
Icons.lock_outlined,
color: canceled
? Colors.red
: (fullyDone ? Colors.green : Colors.grey)),
: (fullyDone ? Colors.green : Colors.grey),
),
const SizedBox(width: 8),
Text(canceled
Text(
canceled
? 'Error ${cancel.first.content.tryGet<String>('code')}: ${cancel.first.content.tryGet<String>('reason')}'
: (fullyDone
? L10n.of(context)!.verifySuccess
: (started
? L10n.of(context)!.loadingPleaseWait
: L10n.of(context)!.newVerificationRequest)))
: L10n.of(context)!.newVerificationRequest)),
)
],
),
),

View File

@ -39,7 +39,8 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
} else {
final tempDir = await getTemporaryDirectory();
final fileName = Uri.encodeComponent(
widget.event.attachmentOrThumbnailMxcUrl()!.pathSegments.last);
widget.event.attachmentOrThumbnailMxcUrl()!.pathSegments.last,
);
final file = File('${tempDir.path}/${fileName}_${videoFile.name}');
if (await file.exists() == false) {
await file.writeAsBytes(videoFile.bytes);
@ -62,13 +63,17 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
);
}
} on MatrixConnectionException catch (e) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toLocalizedString(context)),
));
),
);
} catch (e, s) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toLocalizedString(context)),
));
),
);
Logs().w('Error while playing video', e, s);
} finally {
// Workaround for Chewie needs time to get the aspectRatio
@ -120,14 +125,16 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
width: 24,
height: 24,
child: CircularProgressIndicator.adaptive(
strokeWidth: 2),
strokeWidth: 2,
),
)
: const Icon(Icons.download_outlined),
label: Text(
_isDownloading
? L10n.of(context)!.loadingPleaseWait
: L10n.of(context)!.videoWithSize(
widget.event.sizeString ?? '?MB'),
widget.event.sizeString ?? '?MB',
),
),
onPressed: _isDownloading ? null : _downloadAction,
),

View File

@ -117,8 +117,10 @@ class InputBar extends StatelessWidget {
}
// aside of emote packs, also propose normal (tm) unicode emojis
final matchingUnicodeEmojis = Emoji.all()
.where((element) => [element.name, ...element.keywords]
.any((element) => element.toLowerCase().contains(emoteSearch)))
.where(
(element) => [element.name, ...element.keywords]
.any((element) => element.toLowerCase().contains(emoteSearch)),
)
.toList();
// sort by the index of the search term in the name in order to have
// best matches first
@ -186,12 +188,14 @@ class InputBar extends StatelessWidget {
.toLowerCase()
.contains(roomSearch)) ||
(state.content['alt_aliases'] is List &&
state.content['alt_aliases'].any((l) =>
state.content['alt_aliases'].any(
(l) =>
l is String &&
l
.split(':')[0]
.toLowerCase()
.contains(roomSearch))))) ||
.contains(roomSearch),
)))) ||
(r.name.toLowerCase().contains(roomSearch))) {
ret.add({
'type': 'room',
@ -226,8 +230,10 @@ class InputBar extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('/$command',
style: const TextStyle(fontFamily: 'monospace')),
Text(
'/$command',
style: const TextStyle(fontFamily: 'monospace'),
),
Text(
hint,
maxLines: 1,
@ -273,8 +279,8 @@ class InputBar extends StatelessWidget {
child: suggestion['pack_avatar_url'] != null
? Avatar(
mxContent: Uri.tryParse(
suggestion.tryGet<String>('pack_avatar_url') ??
''),
suggestion.tryGet<String>('pack_avatar_url') ?? '',
),
name: suggestion.tryGet<String>('pack_display_name'),
size: size * 0.9,
client: client,
@ -397,7 +403,8 @@ class InputBar extends StatelessWidget {
actions: !useShortCuts
? {}
: {
NewLineIntent: CallbackAction(onInvoke: (i) {
NewLineIntent: CallbackAction(
onInvoke: (i) {
final val = controller!.value;
final selection = val.selection.start;
final messageWithoutNewLine =
@ -409,11 +416,14 @@ class InputBar extends StatelessWidget {
),
);
return null;
}),
SubmitLineIntent: CallbackAction(onInvoke: (i) {
},
),
SubmitLineIntent: CallbackAction(
onInvoke: (i) {
onSubmitted!(controller!.text);
return null;
}),
},
),
},
child: TypeAheadField<Map<String, String?>>(
direction: AxisDirection.up,

View File

@ -18,14 +18,17 @@ class PinnedEvents extends StatelessWidget {
const PinnedEvents(this.controller, {Key? key}) : super(key: key);
Future<void> _displayPinnedEventsDialog(
BuildContext context, List<Event?> events) async {
BuildContext context,
List<Event?> events,
) async {
final eventId = events.length == 1
? events.single?.eventId
: await showConfirmationDialog<String>(
context: context,
title: L10n.of(context)!.pinMessage,
actions: events
.map((event) => AlertDialogAction(
.map(
(event) => AlertDialogAction(
key: event?.eventId ?? '',
label: event?.calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)!),
@ -33,8 +36,10 @@ class PinnedEvents extends StatelessWidget {
hideReply: true,
) ??
'UNKNOWN',
))
.toList());
),
)
.toList(),
);
if (eventId != null) controller.scrollToEventId(eventId);
}
@ -106,9 +111,8 @@ class PinnedEvents extends StatelessWidget {
),
maxLines: 2,
textStyle: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurfaceVariant,
color:
Theme.of(context).colorScheme.onSurfaceVariant,
overflow: TextOverflow.ellipsis,
fontSize: fontSize,
decoration: event.redacted
@ -116,25 +120,25 @@ class PinnedEvents extends StatelessWidget {
: null,
),
linkStyle: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurfaceVariant,
color:
Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: fontSize,
decoration: TextDecoration.underline,
decorationColor: Theme.of(context)
.colorScheme
.onSurfaceVariant,
decorationColor:
Theme.of(context).colorScheme.onSurfaceVariant,
),
onLinkTap: (url) =>
UrlLauncher(context, url).launchUrl(),
);
}),
},
),
),
),
],
),
),
);
});
},
);
}
}

View File

@ -26,37 +26,45 @@ class ReactionsPicker extends StatelessWidget {
height: (display) ? 56 : 0,
child: Material(
color: Colors.transparent,
child: Builder(builder: (context) {
child: Builder(
builder: (context) {
if (!display) {
return Container();
}
final proposals = proposeEmojis(
controller.selectedEvents.first.plaintextBody,
number: 25,
languageCodes: EmojiProposalLanguageCodes.values.toSet());
languageCodes: EmojiProposalLanguageCodes.values.toSet(),
);
final emojis = proposals.isNotEmpty
? proposals.map((e) => e.char).toList()
: List<String>.from(AppEmojis.emojis);
final allReactionEvents = controller.selectedEvents.first
.aggregatedEvents(
controller.timeline!, RelationshipTypes.reaction)
.where((event) =>
controller.timeline!,
RelationshipTypes.reaction,
)
.where(
(event) =>
event.senderId == event.room.client.userID &&
event.type == 'm.reaction');
event.type == 'm.reaction',
);
for (final event in allReactionEvents) {
try {
emojis.remove(event.content['m.relates_to']['key']);
} catch (_) {}
}
return Row(children: [
return Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).secondaryHeaderColor,
borderRadius: const BorderRadius.only(
bottomRight:
Radius.circular(AppConfig.borderRadius))),
bottomRight: Radius.circular(AppConfig.borderRadius),
),
),
padding: const EdgeInsets.only(right: 1),
child: ListView.builder(
scrollDirection: Axis.horizontal,
@ -74,7 +82,9 @@ class ReactionsPicker extends StatelessWidget {
),
),
),
))),
),
),
),
InkWell(
borderRadius: BorderRadius.circular(8),
child: Container(
@ -88,9 +98,12 @@ class ReactionsPicker extends StatelessWidget {
child: const Icon(Icons.add_outlined),
),
onTap: () =>
controller.pickEmojiReactionAction(allReactionEvents))
]);
}),
controller.pickEmojiReactionAction(allReactionEvents),
)
],
);
},
),
),
);
}

View File

@ -130,7 +130,8 @@ class RecordingDialogState extends State<RecordingDialog> {
.take(26)
.toList()
.reversed
.map((amplitude) => Container(
.map(
(amplitude) => Container(
margin: const EdgeInsets.only(left: 2),
width: 4,
decoration: BoxDecoration(
@ -138,7 +139,9 @@ class RecordingDialogState extends State<RecordingDialog> {
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
),
height: maxDecibalWidth * (amplitude / 100)))
height: maxDecibalWidth * (amplitude / 100),
),
)
.toList(),
),
),

View File

@ -33,10 +33,14 @@ class ReplyDisplay extends StatelessWidget {
),
Expanded(
child: controller.replyEvent != null
? ReplyContent(controller.replyEvent!,
timeline: controller.timeline!)
: _EditContent(controller.editEvent
?.getDisplayEvent(controller.timeline!)),
? ReplyContent(
controller.replyEvent!,
timeline: controller.timeline!,
)
: _EditContent(
controller.editEvent
?.getDisplayEvent(controller.timeline!),
),
),
],
),
@ -83,7 +87,8 @@ class _EditContent extends StatelessWidget {
color: Theme.of(context).textTheme.bodyMedium!.color,
),
);
}),
},
),
],
);
}

View File

@ -37,7 +37,8 @@ class SendFileDialogState extends State<SendFileDialog> {
future: () async {
file = await file.resizeVideo();
thumbnail = await file.getVideoThumbnail();
});
},
);
}
final scaffoldMessenger = ScaffoldMessenger.of(context);
widget.room
@ -79,7 +80,9 @@ class SendFileDialogState extends State<SendFileDialog> {
}
Widget contentWidget;
if (allFilesAreImages) {
contentWidget = Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
contentWidget = Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Flexible(
child: Image.memory(
widget.files.first.bytes,
@ -98,7 +101,8 @@ class SendFileDialogState extends State<SendFileDialog> {
),
],
)
]);
],
);
} else {
contentWidget = Text('$fileName ($sizeString)');
}

View File

@ -28,12 +28,13 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
final pack = stickerPacks[packSlugs[packIndex]]!;
final filteredImagePackImageEntried = pack.images.entries.toList();
if (searchFilter?.isNotEmpty ?? false) {
filteredImagePackImageEntried.removeWhere((e) =>
!(e.key.toLowerCase().contains(searchFilter!.toLowerCase()) ||
filteredImagePackImageEntried.removeWhere(
(e) => !(e.key.toLowerCase().contains(searchFilter!.toLowerCase()) ||
(e.value.body
?.toLowerCase()
.contains(searchFilter!.toLowerCase()) ??
false)));
false)),
);
}
final imageKeys =
filteredImagePackImageEntried.map((e) => e.key).toList();
@ -57,7 +58,8 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
GridView.builder(
itemCount: imageKeys.length,
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 100),
maxCrossAxisExtent: 100,
),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int imageIndex) {
@ -130,7 +132,8 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
delegate: SliverChildBuilderDelegate(
packBuilder,
childCount: packSlugs.length,
)),
),
),
],
),
),

View File

@ -58,7 +58,8 @@ class ChatDetailsController extends State<ChatDetails> {
);
if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context)!.displaynameHasBeenChanged)));
SnackBar(content: Text(L10n.of(context)!.displaynameHasBeenChanged)),
);
}
}
@ -212,8 +213,11 @@ class ChatDetailsController extends State<ChatDetails> {
future: () => room.setDescription(input.single),
);
if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(L10n.of(context)!.groupDescriptionHasBeenChanged)));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.groupDescriptionHasBeenChanged),
),
);
}
}
@ -325,7 +329,9 @@ class ChatDetailsController extends State<ChatDetails> {
void requestMoreMembersAction() async {
final room = Matrix.of(context).client.getRoomById(roomId!);
final participants = await showFutureLoadingDialog(
context: context, future: () => room!.requestParticipants());
context: context,
future: () => room!.requestParticipants(),
);
if (participants.error == null) {
setState(() => members = participants.result);
}

View File

@ -69,16 +69,17 @@ class ChatDetailsView extends StatelessWidget {
icon: Icon(Icons.adaptive.share_outlined),
onPressed: () => FluffyShare.share(
AppConfig.inviteLinkPrefix + room.canonicalAlias,
context),
context,
),
),
ChatSettingsPopupMenu(room, false)
],
title: Text(
room.getLocalizedDisplayname(
MatrixLocals(L10n.of(context)!)),
MatrixLocals(L10n.of(context)!),
),
backgroundColor:
Theme.of(context).appBarTheme.backgroundColor,
),
backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
flexibleSpace: FlexibleSpaceBar(
background: ContentBanner(
mxContent: room.avatar,
@ -114,8 +115,7 @@ class ChatDetailsView extends StatelessWidget {
title: Text(
L10n.of(context)!.groupDescription,
style: TextStyle(
color:
Theme.of(context).colorScheme.secondary,
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
@ -123,7 +123,8 @@ class ChatDetailsView extends StatelessWidget {
if (room.topic.isNotEmpty)
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0),
horizontal: 16.0,
),
child: LinkText(
text: room.topic.isEmpty
? L10n.of(context)!.addGroupDescription
@ -151,47 +152,53 @@ class ChatDetailsView extends StatelessWidget {
title: Text(
L10n.of(context)!.settings,
style: TextStyle(
color:
Theme.of(context).colorScheme.secondary,
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
trailing: Icon(controller.displaySettings
trailing: Icon(
controller.displaySettings
? Icons.keyboard_arrow_down_outlined
: Icons.keyboard_arrow_right_outlined),
: Icons.keyboard_arrow_right_outlined,
),
onTap: controller.toggleDisplaySettings,
),
if (controller.displaySettings) ...[
if (room.canSendEvent('m.room.name'))
ListTile(
leading: CircleAvatar(
backgroundColor: Theme.of(context)
.scaffoldBackgroundColor,
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(
Icons.people_outline_outlined),
Icons.people_outline_outlined,
),
),
title: Text(
L10n.of(context)!.changeTheNameOfTheGroup,
),
subtitle: Text(
room.getLocalizedDisplayname(
MatrixLocals(L10n.of(context)!),
),
),
title: Text(L10n.of(context)!
.changeTheNameOfTheGroup),
subtitle: Text(room.getLocalizedDisplayname(
MatrixLocals(L10n.of(context)!))),
onTap: controller.setDisplaynameAction,
),
if (room.joinRules == JoinRules.public)
ListTile(
leading: CircleAvatar(
backgroundColor: Theme.of(context)
.scaffoldBackgroundColor,
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(Icons.link_outlined),
),
onTap: controller.editAliases,
title:
Text(L10n.of(context)!.editRoomAliases),
title: Text(L10n.of(context)!.editRoomAliases),
subtitle: Text(
(room.canonicalAlias.isNotEmpty)
? room.canonicalAlias
: L10n.of(context)!.none),
: L10n.of(context)!.none,
),
),
ListTile(
leading: CircleAvatar(
@ -199,11 +206,11 @@ class ChatDetailsView extends StatelessWidget {
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(
Icons.insert_emoticon_outlined),
Icons.insert_emoticon_outlined,
),
),
title: Text(L10n.of(context)!.emoteSettings),
subtitle:
Text(L10n.of(context)!.setCustomEmotes),
subtitle: Text(L10n.of(context)!.setCustomEmotes),
onTap: controller.goToEmoteSettings,
),
PopupMenuButton(
@ -213,81 +220,99 @@ class ChatDetailsView extends StatelessWidget {
if (room.canChangeJoinRules)
PopupMenuItem<JoinRules>(
value: JoinRules.public,
child: Text(JoinRules.public
.getLocalizedString(
MatrixLocals(L10n.of(context)!))),
child: Text(
JoinRules.public.getLocalizedString(
MatrixLocals(L10n.of(context)!),
),
),
),
if (room.canChangeJoinRules)
PopupMenuItem<JoinRules>(
value: JoinRules.invite,
child: Text(JoinRules.invite
.getLocalizedString(
MatrixLocals(L10n.of(context)!))),
child: Text(
JoinRules.invite.getLocalizedString(
MatrixLocals(L10n.of(context)!),
),
),
),
],
child: ListTile(
leading: CircleAvatar(
backgroundColor: Theme.of(context)
.scaffoldBackgroundColor,
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(Icons.shield_outlined)),
title: Text(L10n.of(context)!
.whoIsAllowedToJoinThisGroup),
child: const Icon(Icons.shield_outlined),
),
title: Text(
L10n.of(context)!.whoIsAllowedToJoinThisGroup,
),
subtitle: Text(
room.joinRules?.getLocalizedString(
MatrixLocals(L10n.of(context)!)) ??
MatrixLocals(L10n.of(context)!),
) ??
L10n.of(context)!.none,
),
),
),
PopupMenuButton(
onSelected:
controller.setHistoryVisibilityAction,
onSelected: controller.setHistoryVisibilityAction,
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<HistoryVisibility>>[
if (room.canChangeHistoryVisibility)
PopupMenuItem<HistoryVisibility>(
value: HistoryVisibility.invited,
child: Text(HistoryVisibility.invited
child: Text(
HistoryVisibility.invited
.getLocalizedString(
MatrixLocals(L10n.of(context)!))),
MatrixLocals(L10n.of(context)!),
),
),
),
if (room.canChangeHistoryVisibility)
PopupMenuItem<HistoryVisibility>(
value: HistoryVisibility.joined,
child: Text(HistoryVisibility.joined
child: Text(
HistoryVisibility.joined
.getLocalizedString(
MatrixLocals(L10n.of(context)!))),
MatrixLocals(L10n.of(context)!),
),
),
),
if (room.canChangeHistoryVisibility)
PopupMenuItem<HistoryVisibility>(
value: HistoryVisibility.shared,
child: Text(HistoryVisibility.shared
child: Text(
HistoryVisibility.shared
.getLocalizedString(
MatrixLocals(L10n.of(context)!))),
MatrixLocals(L10n.of(context)!),
),
),
),
if (room.canChangeHistoryVisibility)
PopupMenuItem<HistoryVisibility>(
value: HistoryVisibility.worldReadable,
child: Text(HistoryVisibility
.worldReadable
child: Text(
HistoryVisibility.worldReadable
.getLocalizedString(
MatrixLocals(L10n.of(context)!))),
MatrixLocals(L10n.of(context)!),
),
),
),
],
child: ListTile(
leading: CircleAvatar(
backgroundColor: Theme.of(context)
.scaffoldBackgroundColor,
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child:
const Icon(Icons.visibility_outlined),
child: const Icon(Icons.visibility_outlined),
),
title: Text(
L10n.of(context)!.visibilityOfTheChatHistory,
),
title: Text(L10n.of(context)!
.visibilityOfTheChatHistory),
subtitle: Text(
room.historyVisibility?.getLocalizedString(
MatrixLocals(L10n.of(context)!)) ??
MatrixLocals(L10n.of(context)!),
) ??
L10n.of(context)!.none,
),
),
@ -301,9 +326,11 @@ class ChatDetailsView extends StatelessWidget {
PopupMenuItem<GuestAccess>(
value: GuestAccess.canJoin,
child: Text(
GuestAccess.canJoin
.getLocalizedString(MatrixLocals(
L10n.of(context)!)),
GuestAccess.canJoin.getLocalizedString(
MatrixLocals(
L10n.of(context)!,
),
),
),
),
if (room.canChangeGuestAccess)
@ -311,8 +338,11 @@ class ChatDetailsView extends StatelessWidget {
value: GuestAccess.forbidden,
child: Text(
GuestAccess.forbidden
.getLocalizedString(MatrixLocals(
L10n.of(context)!)),
.getLocalizedString(
MatrixLocals(
L10n.of(context)!,
),
),
),
),
],
@ -322,13 +352,16 @@ class ChatDetailsView extends StatelessWidget {
.scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(
Icons.person_add_alt_1_outlined),
Icons.person_add_alt_1_outlined,
),
),
title: Text(
L10n.of(context)!.areGuestsAllowedToJoin,
),
title: Text(L10n.of(context)!
.areGuestsAllowedToJoin),
subtitle: Text(
room.guestAccess.getLocalizedString(
MatrixLocals(L10n.of(context)!)),
MatrixLocals(L10n.of(context)!),
),
),
),
),
@ -336,13 +369,15 @@ class ChatDetailsView extends StatelessWidget {
title:
Text(L10n.of(context)!.editChatPermissions),
subtitle: Text(
L10n.of(context)!.whoCanPerformWhichAction),
L10n.of(context)!.whoCanPerformWhichAction,
),
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(
Icons.edit_attributes_outlined),
Icons.edit_attributes_outlined,
),
),
onTap: () =>
VRouter.of(context).to('permissions'),
@ -353,19 +388,18 @@ class ChatDetailsView extends StatelessWidget {
title: Text(
actualMembersCount > 1
? L10n.of(context)!.countParticipants(
actualMembersCount.toString())
actualMembersCount.toString(),
)
: L10n.of(context)!.emptyChat,
style: TextStyle(
color:
Theme.of(context).colorScheme.secondary,
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
),
room.canInvite
? ListTile(
title:
Text(L10n.of(context)!.inviteContact),
title: Text(L10n.of(context)!.inviteContact),
leading: CircleAvatar(
backgroundColor:
Theme.of(context).primaryColor,
@ -373,8 +407,7 @@ class ChatDetailsView extends StatelessWidget {
radius: Avatar.defaultSize / 2,
child: const Icon(Icons.add_outlined),
),
onTap: () =>
VRouter.of(context).to('invite'),
onTap: () => VRouter.of(context).to('invite'),
)
: Container(),
],
@ -382,11 +415,13 @@ class ChatDetailsView extends StatelessWidget {
: i < controller.members!.length + 1
? ParticipantListItem(controller.members![i - 1])
: ListTile(
title: Text(L10n.of(context)!
.loadCountMoreParticipants(
title: Text(
L10n.of(context)!.loadCountMoreParticipants(
(actualMembersCount -
controller.members!.length)
.toString())),
.toString(),
),
),
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
@ -401,6 +436,7 @@ class ChatDetailsView extends StatelessWidget {
),
),
);
});
},
);
}
}

View File

@ -51,7 +51,8 @@ class ParticipantListItem extends StatelessWidget {
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Theme.of(context).colorScheme.primary,
)),
),
),
child: Text(
permissionBatch,
style: TextStyle(

View File

@ -20,19 +20,19 @@ class ChatEncryptionSettingsView extends StatelessWidget {
final room = controller.room;
return StreamBuilder<Object>(
stream: room.client.onSync.stream.where(
(s) => s.rooms?.join?[room.id] != null || s.deviceLists != null),
(s) => s.rooms?.join?[room.id] != null || s.deviceLists != null,
),
builder: (context, _) => Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.close_outlined),
onPressed: () => VRouter.of(context)
.toSegments(['rooms', controller.roomId!]),
onPressed: () =>
VRouter.of(context).toSegments(['rooms', controller.roomId!]),
),
title: Text(L10n.of(context)!.endToEndEncryption),
actions: [
TextButton(
onPressed: () =>
launchUrlString(AppConfig.encryptionTutorial),
onPressed: () => launchUrlString(AppConfig.encryptionTutorial),
child: Text(L10n.of(context)!.help),
),
],
@ -43,9 +43,9 @@ class ChatEncryptionSettingsView extends StatelessWidget {
secondary: CircleAvatar(
foregroundColor:
Theme.of(context).colorScheme.onPrimaryContainer,
backgroundColor:
Theme.of(context).colorScheme.primaryContainer,
child: const Icon(Icons.lock_outlined)),
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
child: const Icon(Icons.lock_outlined),
),
title: Text(L10n.of(context)!.encryptThisChat),
value: room.encrypted,
onChanged: controller.enableEncryption,
@ -81,20 +81,22 @@ class ChatEncryptionSettingsView extends StatelessWidget {
),
StreamBuilder(
stream: room.onUpdate.stream,
builder: (context, snapshot) => FutureBuilder<
List<DeviceKeys>>(
builder: (context, snapshot) => FutureBuilder<List<DeviceKeys>>(
future: room.getUserDeviceKeys(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(
'${L10n.of(context)!.oopsSomethingWentWrong}: ${snapshot.error}'),
'${L10n.of(context)!.oopsSomethingWentWrong}: ${snapshot.error}',
),
);
}
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator.adaptive(
strokeWidth: 2));
strokeWidth: 2,
),
);
}
final deviceKeys = snapshot.data!;
return ListView.builder(
@ -135,11 +137,11 @@ class ChatEncryptionSettingsView extends StatelessWidget {
child: Material(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
AppConfig.borderRadius),
AppConfig.borderRadius,
),
side: BorderSide(
color: Theme.of(context)
.colorScheme
.primary,
color:
Theme.of(context).colorScheme.primary,
),
),
color: Theme.of(context)
@ -152,9 +154,8 @@ class ChatEncryptionSettingsView extends StatelessWidget {
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.primary,
color:
Theme.of(context).colorScheme.primary,
fontSize: 12,
fontStyle: FontStyle.italic,
),
@ -166,17 +167,16 @@ class ChatEncryptionSettingsView extends StatelessWidget {
),
subtitle: Text(
deviceKeys[i].ed25519Key?.beautified ??
L10n.of(context)!
.unknownEncryptionAlgorithm,
L10n.of(context)!.unknownEncryptionAlgorithm,
style: TextStyle(
fontFamily: 'RobotoMono',
color:
Theme.of(context).colorScheme.secondary,
color: Theme.of(context).colorScheme.secondary,
),
),
),
);
}),
},
),
),
] else
Padding(
@ -192,6 +192,7 @@ class ChatEncryptionSettingsView extends StatelessWidget {
),
],
),
));
),
);
}
}

View File

@ -175,8 +175,10 @@ class ChatListController extends State<ChatList>
hintText: Matrix.of(context).client.homeserver?.host,
initialText: searchServer,
keyboardType: TextInputType.url,
autocorrect: false)
]);
autocorrect: false,
)
],
);
if (newServer == null) return;
Store().setItem(_serverStoreNamespace, newServer.single);
setState(() {
@ -382,9 +384,11 @@ class ChatListController extends State<ChatList>
}
void toggleSelection(String roomId) {
setState(() => selectedRoomIds.contains(roomId)
setState(
() => selectedRoomIds.contains(roomId)
? selectedRoomIds.remove(roomId)
: selectedRoomIds.add(roomId));
: selectedRoomIds.add(roomId),
);
}
Future<void> toggleUnread() async {
@ -465,7 +469,8 @@ class ChatListController extends State<ChatList>
DialogTextField(
hintText: L10n.of(context)!.statusExampleMessage,
),
]);
],
);
if (input == null) return;
await showFutureLoadingDialog(
context: context,
@ -506,7 +511,8 @@ class ChatListController extends State<ChatList>
.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
),
)
.toList());
.toList(),
);
if (selectedSpace == null) return;
final result = await showFutureLoadingDialog(
context: context,
@ -532,14 +538,19 @@ class ChatListController extends State<ChatList>
}
bool get anySelectedRoomNotMarkedUnread => selectedRoomIds.any(
(roomId) => !Matrix.of(context).client.getRoomById(roomId)!.markedUnread);
(roomId) =>
!Matrix.of(context).client.getRoomById(roomId)!.markedUnread,
);
bool get anySelectedRoomNotFavorite => selectedRoomIds.any(
(roomId) => !Matrix.of(context).client.getRoomById(roomId)!.isFavourite);
(roomId) => !Matrix.of(context).client.getRoomById(roomId)!.isFavourite,
);
bool get anySelectedRoomNotMuted => selectedRoomIds.any((roomId) =>
bool get anySelectedRoomNotMuted => selectedRoomIds.any(
(roomId) =>
Matrix.of(context).client.getRoomById(roomId)!.pushRuleState ==
PushRuleState.notify);
PushRuleState.notify,
);
bool waitForFirstSync = false;
@ -626,7 +637,8 @@ class ChatListController extends State<ChatList>
final bundle = await showTextInputDialog(
context: context,
title: l10n.bundleName,
textFields: [DialogTextField(hintText: l10n.bundleName)]);
textFields: [DialogTextField(hintText: l10n.bundleName)],
);
if (bundle == null || bundle.isEmpty || bundle.single.isEmpty) return;
await showFutureLoadingDialog(
context: context,

View File

@ -46,9 +46,11 @@ class ChatListViewBody extends StatelessWidget {
);
},
child: StreamBuilder(
key: ValueKey(client.userID.toString() +
key: ValueKey(
client.userID.toString() +
controller.activeFilter.toString() +
controller.activeSpaceId.toString()),
controller.activeSpaceId.toString(),
),
stream: client.onSync.stream
.where((s) => s.hasRoomUpdate)
.rateLimit(const Duration(seconds: 1)),
@ -98,11 +100,10 @@ class ChatListViewBody extends StatelessWidget {
itemCount: roomSearchResult.chunk.length,
itemBuilder: (context, i) => _SearchItem(
title: roomSearchResult.chunk[i].name ??
roomSearchResult.chunk[i]
.canonicalAlias?.localpart ??
roomSearchResult.chunk[i].canonicalAlias
?.localpart ??
L10n.of(context)!.group,
avatar:
roomSearchResult.chunk[i].avatarUrl,
avatar: roomSearchResult.chunk[i].avatarUrl,
onPressed: () => showAdaptiveBottomSheet(
context: context,
builder: (c) => PublicRoomBottomSheet(
@ -145,8 +146,8 @@ class ChatListViewBody extends StatelessWidget {
onPressed: () => showAdaptiveBottomSheet(
context: context,
builder: (c) => ProfileBottomSheet(
userId: userSearchResult
.results[i].userId,
userId:
userSearchResult.results[i].userId,
outerContext: context,
),
),
@ -175,10 +176,8 @@ class ChatListViewBody extends StatelessWidget {
child: ListTile(
leading: const Icon(Icons.vpn_key),
title: Text(L10n.of(context)!.dehydrateTor),
subtitle:
Text(L10n.of(context)!.dehydrateTorLong),
trailing:
const Icon(Icons.chevron_right_outlined),
subtitle: Text(L10n.of(context)!.dehydrateTorLong),
trailing: const Icon(Icons.chevron_right_outlined),
onTap: controller.dehydrate,
),
),
@ -210,7 +209,8 @@ class ChatListViewBody extends StatelessWidget {
.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!))
.toLowerCase()
.contains(
controller.searchController.text.toLowerCase())) {
controller.searchController.text.toLowerCase(),
)) {
return Container();
}
return ChatListItem(
@ -286,7 +286,8 @@ class ChatListViewBody extends StatelessWidget {
),
),
);
}),
},
),
);
}
}

View File

@ -120,22 +120,28 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
),
IconButton(
tooltip: L10n.of(context)!.toggleUnread,
icon: Icon(controller.anySelectedRoomNotMarkedUnread
icon: Icon(
controller.anySelectedRoomNotMarkedUnread
? Icons.mark_chat_read_outlined
: Icons.mark_chat_unread_outlined),
: Icons.mark_chat_unread_outlined,
),
onPressed: controller.toggleUnread,
),
IconButton(
tooltip: L10n.of(context)!.toggleFavorite,
icon: Icon(controller.anySelectedRoomNotFavorite
icon: Icon(
controller.anySelectedRoomNotFavorite
? Icons.push_pin_outlined
: Icons.push_pin),
: Icons.push_pin,
),
onPressed: controller.toggleFavouriteRoom,
),
IconButton(
icon: Icon(controller.anySelectedRoomNotMuted
icon: Icon(
controller.anySelectedRoomNotMuted
? Icons.notifications_off_outlined
: Icons.notifications_outlined),
: Icons.notifications_outlined,
),
tooltip: L10n.of(context)!.toggleMuted,
onPressed: controller.toggleMuted,
),

View File

@ -46,7 +46,8 @@ class ChatListItem extends StatelessWidget {
);
await room.join();
await waitForRoom;
});
},
);
if (joinResult.error != null) return;
}
@ -107,7 +108,9 @@ class ChatListItem extends StatelessWidget {
);
if (confirmed == OkCancelResult.cancel) return;
await showFutureLoadingDialog(
context: context, future: () => room.leave());
context: context,
future: () => room.leave(),
);
return;
}
}
@ -183,7 +186,8 @@ class ChatListItem extends StatelessWidget {
if (room.isFavourite)
Padding(
padding: EdgeInsets.only(
right: room.notificationCount > 0 ? 4.0 : 0.0),
right: room.notificationCount > 0 ? 4.0 : 0.0,
),
child: Icon(
Icons.push_pin,
size: 16,
@ -282,7 +286,8 @@ class ChatListItem extends StatelessWidget {
: null,
),
);
}),
},
),
),
const SizedBox(width: 8),
AnimatedContainer(

View File

@ -109,8 +109,10 @@ class ChatListView extends StatelessWidget {
children: [
if (FluffyThemes.isColumnMode(context) &&
FluffyThemes.getDisplayNavigationRail(context)) ...[
Builder(builder: (context) {
final allSpaces = client.rooms.where((room) => room.isSpace);
Builder(
builder: (context) {
final allSpaces =
client.rooms.where((room) => room.isSpace);
final rootSpaces = allSpaces
.where(
(space) => !allSpaces.any(
@ -142,7 +144,8 @@ class ChatListView extends StatelessWidget {
rootSpaces[i].id == controller.activeSpaceId;
return NaviRailItem(
toolTip: rootSpaces[i].getLocalizedDisplayname(
MatrixLocals(L10n.of(context)!)),
MatrixLocals(L10n.of(context)!),
),
isSelected: isSelected,
onTap: () =>
controller.setActiveSpace(rootSpaces[i].id),
@ -158,7 +161,8 @@ class ChatListView extends StatelessWidget {
},
),
);
}),
},
),
Container(
color: Theme.of(context).dividerColor,
width: 1,

View File

@ -20,11 +20,13 @@ class ClientChooserButton extends StatelessWidget {
List<PopupMenuEntry<Object>> _bundleMenuItems(BuildContext context) {
final matrix = Matrix.of(context);
final bundles = matrix.accountBundles.keys.toList()
..sort((a, b) => a!.isValidMatrixId == b!.isValidMatrixId
..sort(
(a, b) => a!.isValidMatrixId == b!.isValidMatrixId
? 0
: a.isValidMatrixId && !b.isValidMatrixId
? -1
: 1);
: 1,
);
return <PopupMenuEntry<Object>>[
PopupMenuItem(
value: SettingsAction.newStory,
@ -142,7 +144,9 @@ class ClientChooserButton extends StatelessWidget {
IconButton(
icon: const Icon(Icons.edit_outlined),
onPressed: () => controller.editBundlesForAccount(
client.userID, bundle),
client.userID,
bundle,
),
),
],
),
@ -270,9 +274,12 @@ class ClientChooserButton extends StatelessWidget {
break;
case SettingsAction.invite:
FluffyShare.share(
L10n.of(context)!.inviteText(Matrix.of(context).client.userID!,
'https://matrix.to/#/${Matrix.of(context).client.userID}?client=im.fluffychat'),
context);
L10n.of(context)!.inviteText(
Matrix.of(context).client.userID!,
'https://matrix.to/#/${Matrix.of(context).client.userID}?client=im.fluffychat',
),
context,
);
break;
case SettingsAction.settings:
VRouter.of(context).to('/settings');
@ -290,11 +297,13 @@ class ClientChooserButton extends StatelessWidget {
BuildContext context,
) {
final bundles = matrix.accountBundles.keys.toList()
..sort((a, b) => a!.isValidMatrixId == b!.isValidMatrixId
..sort(
(a, b) => a!.isValidMatrixId == b!.isValidMatrixId
? 0
: a.isValidMatrixId && !b.isValidMatrixId
? -1
: 1);
: 1,
);
// beginning from end if negative
if (index < 0) {
int clientCount = 0;
@ -320,11 +329,13 @@ class ClientChooserButton extends StatelessWidget {
int index = 0;
final bundles = matrix.accountBundles.keys.toList()
..sort((a, b) => a!.isValidMatrixId == b!.isValidMatrixId
..sort(
(a, b) => a!.isValidMatrixId == b!.isValidMatrixId
? 0
: a.isValidMatrixId && !b.isValidMatrixId
? -1
: 1);
: 1,
);
for (final bundleName in bundles) {
final bundle = matrix.accountBundles[bundleName];
if (bundle == null) return null;

View File

@ -58,7 +58,8 @@ class NaviRailItem extends StatelessWidget {
vertical: 8.0,
),
child: isSelected ? selectedIcon ?? icon : icon,
)),
),
),
),
),
],

View File

@ -45,13 +45,15 @@ class SearchTitle extends StatelessWidget {
children: [
icon,
const SizedBox(width: 16),
Text(title,
Text(
title,
textAlign: TextAlign.left,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontSize: 12,
fontWeight: FontWeight.bold,
)),
),
),
if (trailing != null)
Expanded(
child: Align(

View File

@ -53,11 +53,14 @@ class _SpaceViewState extends State<SpaceView> {
final result = await showFutureLoadingDialog(
context: context,
future: () async {
await client.joinRoom(spaceChild.roomId,
await client.joinRoom(
spaceChild.roomId,
serverName: space?.spaceChildren
.firstWhereOrNull(
(child) => child.roomId == spaceChild.roomId)
?.via);
(child) => child.roomId == spaceChild.roomId,
)
?.via,
);
if (client.getRoomById(spaceChild.roomId) == null) {
// Wait for room actually appears in sync
await client.waitForRoomInSync(spaceChild.roomId, join: true);
@ -78,8 +81,10 @@ class _SpaceViewState extends State<SpaceView> {
VRouter.of(context).toSegments(['rooms', spaceChild.roomId]);
}
void _onSpaceChildContextMenu(
[SpaceRoomsChunk? spaceChild, Room? room]) async {
void _onSpaceChildContextMenu([
SpaceRoomsChunk? spaceChild,
Room? room,
]) async {
final client = Matrix.of(context).client;
final activeSpaceId = widget.controller.activeSpaceId;
final activeSpace =
@ -169,8 +174,10 @@ class _SpaceViewState extends State<SpaceView> {
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(L10n.of(context)!
.numChats(rootSpace.spaceChildren.length.toString())),
subtitle: Text(
L10n.of(context)!
.numChats(rootSpace.spaceChildren.length.toString()),
),
onTap: () => widget.controller.setActiveSpace(rootSpace.id),
onLongPress: () => _onSpaceChildContextMenu(null, rootSpace),
trailing: const Icon(Icons.chevron_right_outlined),
@ -203,9 +210,10 @@ class _SpaceViewState extends State<SpaceView> {
if (response == null) {
return const Center(child: CircularProgressIndicator.adaptive());
}
final parentSpace = allSpaces.firstWhereOrNull((space) => space
.spaceChildren
.any((child) => child.roomId == activeSpaceId));
final parentSpace = allSpaces.firstWhereOrNull(
(space) =>
space.spaceChildren.any((child) => child.roomId == activeSpaceId),
);
final spaceChildren = response.rooms;
final canLoadMore = response.nextBatch != null;
return VWidgetGuard(
@ -226,17 +234,18 @@ class _SpaceViewState extends State<SpaceView> {
onPressed: () =>
widget.controller.setActiveSpace(parentSpace?.id),
),
title: Text(parentSpace == null
title: Text(
parentSpace == null
? L10n.of(context)!.allSpaces
: parentSpace.getLocalizedDisplayname(
MatrixLocals(L10n.of(context)!),
)),
),
),
trailing: IconButton(
icon: snapshot.connectionState != ConnectionState.done
? const CircularProgressIndicator.adaptive()
: const Icon(Icons.refresh_outlined),
onPressed:
snapshot.connectionState != ConnectionState.done
onPressed: snapshot.connectionState != ConnectionState.done
? null
: _refresh,
),
@ -258,20 +267,17 @@ class _SpaceViewState extends State<SpaceView> {
if (room != null && !room.isSpace) {
return ChatListItem(
room,
onLongPress: () =>
_onSpaceChildContextMenu(spaceChild, room),
onLongPress: () => _onSpaceChildContextMenu(spaceChild, room),
activeChat: widget.controller.activeChat == room.id,
);
}
final isSpace = spaceChild.roomType == 'm.space';
final topic = spaceChild.topic?.isEmpty ?? true
? null
: spaceChild.topic;
final topic =
spaceChild.topic?.isEmpty ?? true ? null : spaceChild.topic;
if (spaceChild.roomId == activeSpaceId) {
return SearchTitle(
title: spaceChild.name ??
spaceChild.canonicalAlias ??
'Space',
title:
spaceChild.name ?? spaceChild.canonicalAlias ?? 'Space',
icon: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Avatar(
@ -322,8 +328,7 @@ class _SpaceViewState extends State<SpaceView> {
],
),
onTap: () => _onJoinSpaceChild(spaceChild),
onLongPress: () =>
_onSpaceChildContextMenu(spaceChild, room),
onLongPress: () => _onSpaceChildContextMenu(spaceChild, room),
subtitle: Text(
topic ??
(isSpace
@ -331,15 +336,17 @@ class _SpaceViewState extends State<SpaceView> {
: L10n.of(context)!.enterRoom),
maxLines: 1,
style: TextStyle(
color: Theme.of(context).colorScheme.onBackground),
color: Theme.of(context).colorScheme.onBackground,
),
trailing: isSpace
? const Icon(Icons.chevron_right_outlined)
: null,
),
trailing:
isSpace ? const Icon(Icons.chevron_right_outlined) : null,
);
}),
},
),
);
},
);
});
}
}

View File

@ -22,12 +22,17 @@ class ChatPermissionsSettings extends StatefulWidget {
class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
String? get roomId => VRouter.of(context).pathParameters['roomid'];
void editPowerLevel(BuildContext context, String key, int currentLevel,
{String? category}) async {
void editPowerLevel(
BuildContext context,
String key,
int currentLevel, {
String? category,
}) async {
final room = Matrix.of(context).client.getRoomById(roomId!)!;
if (!room.canSendEvent(EventTypes.RoomPowerLevels)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context)!.noPermission)));
SnackBar(content: Text(L10n.of(context)!.noPermission)),
);
return;
}
final newLevel = await showPermissionChooser(
@ -36,7 +41,8 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
);
if (newLevel == null) return;
final content = Map<String, dynamic>.from(
room.getState(EventTypes.RoomPowerLevels)!.content);
room.getState(EventTypes.RoomPowerLevels)!.content,
);
if (category != null) {
if (!content.containsKey(category)) {
content[category] = <String, dynamic>{};
@ -74,10 +80,13 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
title: L10n.of(context)!.replaceRoomWithNewerVersion,
actions: capabilities.mRoomVersions!.available.entries
.where((r) => r.key != roomVersion)
.map((version) => AlertDialogAction(
.map(
(version) => AlertDialogAction(
key: version.key,
label:
'${version.key} (${version.value.toString().split('.').last})'))
'${version.key} (${version.value.toString().split('.').last})',
),
)
.toList(),
);
if (newVersion == null ||

View File

@ -41,7 +41,8 @@ class ChatPermissionsSettingsView extends StatelessWidget {
return Center(child: Text(L10n.of(context)!.noRoomsFound));
}
final powerLevelsContent = Map<String, dynamic>.from(
room.getState(EventTypes.RoomPowerLevels)!.content);
room.getState(EventTypes.RoomPowerLevels)!.content,
);
final powerLevels = Map<String, dynamic>.from(powerLevelsContent)
..removeWhere((k, v) => v is! int);
final eventsPowerLevels =
@ -57,7 +58,10 @@ class ChatPermissionsSettingsView extends StatelessWidget {
permissionKey: entry.key,
permission: entry.value,
onTap: () => controller.editPowerLevel(
context, entry.key, entry.value),
context,
entry.key,
entry.value,
),
),
const Divider(thickness: 1),
ListTile(
@ -69,7 +73,8 @@ class ChatPermissionsSettingsView extends StatelessWidget {
),
),
),
Builder(builder: (context) {
Builder(
builder: (context) {
const key = 'rooms';
final int value = powerLevelsContent
.containsKey('notifications')
@ -80,10 +85,14 @@ class ChatPermissionsSettingsView extends StatelessWidget {
permission: value,
category: 'notifications',
onTap: () => controller.editPowerLevel(
context, key, value,
category: 'notifications'),
context,
key,
value,
category: 'notifications',
),
);
}),
},
),
const Divider(thickness: 1),
ListTile(
title: Text(
@ -100,8 +109,11 @@ class ChatPermissionsSettingsView extends StatelessWidget {
category: 'events',
permission: entry.value,
onTap: () => controller.editPowerLevel(
context, entry.key, entry.value,
category: 'events'),
context,
entry.key,
entry.value,
category: 'events',
),
),
if (room.canSendEvent(EventTypes.RoomTombstone)) ...{
const Divider(thickness: 1),
@ -111,7 +123,9 @@ class ChatPermissionsSettingsView extends StatelessWidget {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator.adaptive(
strokeWidth: 2));
strokeWidth: 2,
),
);
}
final String roomVersion = room
.getState(EventTypes.RoomCreate)!
@ -120,7 +134,8 @@ class ChatPermissionsSettingsView extends StatelessWidget {
return ListTile(
title: Text(
'${L10n.of(context)!.roomVersion}: $roomVersion'),
'${L10n.of(context)!.roomVersion}: $roomVersion',
),
onTap: () =>
controller.updateRoomAction(snapshot.data!),
);

View File

@ -118,8 +118,9 @@ class ConnectPageController extends State<ConnectPage> {
List<IdentityProvider>? get identityProviders {
final loginTypes = _rawLoginTypes;
if (loginTypes == null) return null;
final rawProviders = loginTypes.tryGetList('flows')!.singleWhere((flow) =>
flow['type'] == AuthenticationTypes.sso)['identity_providers'];
final rawProviders = loginTypes.tryGetList('flows')!.singleWhere(
(flow) => flow['type'] == AuthenticationTypes.sso,
)['identity_providers'];
final list = (rawProviders as List)
.map((json) => IdentityProvider.fromJson(json))
.toList();
@ -163,9 +164,11 @@ class ConnectPageController extends State<ConnectPage> {
RequestType.GET,
'/client/r0/login',
)
.then((loginTypes) => setState(() {
.then(
(loginTypes) => setState(() {
_rawLoginTypes = loginTypes;
}));
}),
);
}
}

View File

@ -174,17 +174,20 @@ class ConnectPageView extends StatelessWidget {
)
: Image.network(
Uri.parse(identityProviders.single.icon!)
.getDownloadLink(Matrix.of(context)
.getLoginClient())
.getDownloadLink(
Matrix.of(context).getLoginClient(),
)
.toString(),
width: 32,
height: 32,
),
onPressed: () => controller
.ssoLoginAction(identityProviders.single.id!),
label: Text(identityProviders.single.name ??
label: Text(
identityProviders.single.name ??
identityProviders.single.brand ??
L10n.of(context)!.loginWithOneClick),
L10n.of(context)!.loginWithOneClick,
),
),
)
: Wrap(

View File

@ -37,7 +37,8 @@ class SsoButton extends StatelessWidget {
: Image.network(
Uri.parse(identityProvider.icon!)
.getDownloadLink(
Matrix.of(context).getLoginClient())
Matrix.of(context).getLoginClient(),
)
.toString(),
width: 32,
height: 32,

View File

@ -35,7 +35,8 @@ class DevicesSettingsView extends StatelessWidget {
}
if (!snapshot.hasData || controller.devices == null) {
return const Center(
child: CircularProgressIndicator.adaptive(strokeWidth: 2));
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
);
}
return ListView.builder(
itemCount: controller.notThisDevice.length + 1,
@ -63,12 +64,14 @@ class DevicesSettingsView extends StatelessWidget {
),
trailing: controller.loadingDeletingDevices
? const CircularProgressIndicator.adaptive(
strokeWidth: 2)
strokeWidth: 2,
)
: const Icon(Icons.delete_outline),
onTap: controller.loadingDeletingDevices
? null
: () => controller.removeDevicesAction(
controller.notThisDevice),
controller.notThisDevice,
),
)
else
Center(

View File

@ -136,7 +136,8 @@ class UserDeviceListItem extends StatelessWidget {
subtitle: Text(
L10n.of(context)!.lastActiveAgo(
DateTime.fromMillisecondsSinceEpoch(userDevice.lastSeenTs ?? 0)
.localizedTimeShort(context)),
.localizedTimeShort(context),
),
style: const TextStyle(fontWeight: FontWeight.w300),
),
);

View File

@ -36,9 +36,12 @@ import 'package:fluffychat/widgets/avatar.dart';
import 'pip/pip_view.dart';
class _StreamView extends StatelessWidget {
const _StreamView(this.wrappedStream,
{Key? key, this.mainView = false, required this.matrixClient})
: super(key: key);
const _StreamView(
this.wrappedStream, {
Key? key,
this.mainView = false,
required this.matrixClient,
}) : super(key: key);
final WrappedMediaStream wrappedStream;
final Client matrixClient;
@ -94,16 +97,21 @@ class _StreamView extends StatelessWidget {
client: matrixClient,
// textSize: mainView ? 36 : 24,
// matrixClient: matrixClient,
)),
),
),
if (!isScreenSharing)
Positioned(
left: 4.0,
bottom: 4.0,
child: Icon(audioMuted ? Icons.mic_off : Icons.mic,
color: Colors.white, size: 18.0),
child: Icon(
audioMuted ? Icons.mic_off : Icons.mic,
color: Colors.white,
size: 18.0,
),
)
],
));
),
);
}
}
@ -114,14 +122,14 @@ class Calling extends StatefulWidget {
final CallSession call;
final Client client;
const Calling(
{required this.context,
const Calling({
required this.context,
required this.call,
required this.client,
required this.callId,
this.onClear,
Key? key})
: super(key: key);
Key? key,
}) : super(key: key);
@override
MyCallingPage createState() => MyCallingPage();
@ -206,7 +214,8 @@ class MyCallingPage extends State<Calling> {
event == CallEvent.kRemoteHoldUnhold) {
setState(() {});
Logs().i(
'Call hold event: local ${call.localHold}, remote ${call.remoteOnHold}');
'Call hold event: local ${call.localHold}, remote ${call.remoteOnHold}',
);
}
});
_state = call.state;
@ -239,7 +248,9 @@ class MyCallingPage extends State<Calling> {
void _resizeLocalVideo(Orientation orientation) {
final shortSide = min(
MediaQuery.of(context).size.width, MediaQuery.of(context).size.height);
MediaQuery.of(context).size.width,
MediaQuery.of(context).size.height,
);
_localVideoMargin = remoteStream != null
? const EdgeInsets.only(top: 20.0, right: 20.0)
: EdgeInsets.zero;
@ -305,7 +316,8 @@ class MyCallingPage extends State<Calling> {
);
FlutterForegroundTask.startService(
notificationTitle: L10n.of(context)!.screenSharingTitle,
notificationText: L10n.of(context)!.screenSharingDetail);
notificationText: L10n.of(context)!.screenSharingDetail,
);
} else {
FlutterForegroundTask.stopService();
}
@ -331,7 +343,8 @@ class MyCallingPage extends State<Calling> {
void _switchCamera() async {
if (call.localUserMediaStream != null) {
await Helper.switchCamera(
call.localUserMediaStream!.stream!.getVideoTracks()[0]);
call.localUserMediaStream!.stream!.getVideoTracks()[0],
);
if (PlatformInfos.isMobile) {
call.facingMode == 'user'
? call.facingMode = 'environment'
@ -473,8 +486,11 @@ class MyCallingPage extends State<Calling> {
} else if (call.remoteOnHold) {
title = 'You held the call.';
}
stackWidgets.add(Center(
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
stackWidgets.add(
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.pause,
size: 48.0,
@ -487,8 +503,10 @@ class MyCallingPage extends State<Calling> {
fontSize: 24.0,
),
)
]),
));
],
),
),
);
return stackWidgets;
}
@ -502,10 +520,15 @@ class MyCallingPage extends State<Calling> {
}
if (primaryStream != null) {
stackWidgets.add(Center(
child: _StreamView(primaryStream,
mainView: true, matrixClient: widget.client),
));
stackWidgets.add(
Center(
child: _StreamView(
primaryStream,
mainView: true,
matrixClient: widget.client,
),
),
);
}
if (isFloating || !connected) {
@ -522,37 +545,47 @@ class MyCallingPage extends State<Calling> {
if (call.remoteScreenSharingStream != null) {
final remoteUserMediaStream = call.remoteUserMediaStream;
secondaryStreamViews.add(SizedBox(
secondaryStreamViews.add(
SizedBox(
width: _localVideoWidth,
height: _localVideoHeight,
child: _StreamView(remoteUserMediaStream!, matrixClient: widget.client),
));
child:
_StreamView(remoteUserMediaStream!, matrixClient: widget.client),
),
);
secondaryStreamViews.add(const SizedBox(height: 10));
}
final localStream =
call.localUserMediaStream ?? call.localScreenSharingStream;
if (localStream != null && !isFloating) {
secondaryStreamViews.add(SizedBox(
secondaryStreamViews.add(
SizedBox(
width: _localVideoWidth,
height: _localVideoHeight,
child: _StreamView(localStream, matrixClient: widget.client),
));
),
);
secondaryStreamViews.add(const SizedBox(height: 10));
}
if (call.localScreenSharingStream != null && !isFloating) {
secondaryStreamViews.add(SizedBox(
secondaryStreamViews.add(
SizedBox(
width: _localVideoWidth,
height: _localVideoHeight,
child: _StreamView(call.remoteUserMediaStream!,
matrixClient: widget.client),
));
child: _StreamView(
call.remoteUserMediaStream!,
matrixClient: widget.client,
),
),
);
secondaryStreamViews.add(const SizedBox(height: 10));
}
if (secondaryStreamViews.isNotEmpty) {
stackWidgets.add(Container(
stackWidgets.add(
Container(
padding: const EdgeInsets.fromLTRB(0, 20, 0, 120),
alignment: Alignment.bottomRight,
child: Container(
@ -562,7 +595,8 @@ class MyCallingPage extends State<Calling> {
children: secondaryStreamViews,
),
),
));
),
);
}
return stackWidgets;
@ -570,7 +604,8 @@ class MyCallingPage extends State<Calling> {
@override
Widget build(BuildContext context) {
return PIPView(builder: (context, isFloating) {
return PIPView(
builder: (context, isFloating) {
return Scaffold(
resizeToAvoidBottomInset: !isFloating,
floatingActionButtonLocation:
@ -580,14 +615,17 @@ class MyCallingPage extends State<Calling> {
height: 150.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: _buildActionButtons(isFloating))),
children: _buildActionButtons(isFloating),
),
),
body: OrientationBuilder(
builder: (BuildContext context, Orientation orientation) {
return Container(
decoration: const BoxDecoration(
color: Colors.black87,
),
child: Stack(children: [
child: Stack(
children: [
..._buildContent(orientation, isFloating),
if (!isFloating)
Positioned(
@ -599,9 +637,15 @@ class MyCallingPage extends State<Calling> {
onPressed: () {
PIPView.of(context)?.setFloating(true);
},
))
]));
}));
});
),
)
],
),
);
},
),
);
},
);
}
}

View File

@ -198,9 +198,11 @@ class PIPViewState extends State<PIPView> with TickerProviderStateMixin {
: Tween<Offset>(
begin: _dragOffset,
end: calculatedOffset,
).transform(_dragAnimationController.isAnimating
).transform(
_dragAnimationController.isAnimating
? dragAnimationValue
: toggleFloatingAnimationValue);
: toggleFloatingAnimationValue,
);
final borderRadius = Tween<double>(
begin: 0,
end: 10,

View File

@ -21,7 +21,8 @@ class HomeserverBottomSheet extends StatelessWidget {
appBar: AppBar(
title: Text(homeserver.homeserver.baseUrl.host),
),
body: ListView(children: [
body: ListView(
children: [
if (description != null && description.isNotEmpty)
ListTile(
leading: const Icon(Icons.info_outlined),
@ -66,7 +67,8 @@ class HomeserverBottomSheet extends StatelessWidget {
leading: const Icon(Icons.timer_outlined),
title: Text('${responseTime.inMilliseconds}ms'),
),
]),
],
),
);
}
}

View File

@ -54,7 +54,8 @@ class HomeserverPickerController extends State<HomeserverPicker> {
context: context,
title: L10n.of(context)!.indexedDbErrorTitle,
message: L10n.of(context)!.indexedDbErrorLong,
onWillPop: () async => false);
onWillPop: () async => false,
);
_checkTorBrowser();
},
);
@ -85,9 +86,11 @@ class HomeserverPickerController extends State<HomeserverPicker> {
});
List<HomeserverBenchmarkResult> get filteredHomeservers => benchmarkResults!
.where((element) =>
.where(
(element) =>
element.homeserver.baseUrl.host.contains(searchTerm) ||
(element.homeserver.description?.contains(searchTerm) ?? false))
(element.homeserver.description?.contains(searchTerm) ?? false),
)
.toList();
void _loadHomeserverList() async {
@ -195,6 +198,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
} catch (e, s) {
Logs().e('Future error:', e, s);
}
});
},
);
}
}

View File

@ -66,9 +66,10 @@ class HomeserverPickerView extends StatelessWidget {
? const Center(
child: Padding(
padding: EdgeInsets.all(12.0),
child:
CircularProgressIndicator.adaptive(),
))
child: CircularProgressIndicator
.adaptive(),
),
)
: Column(
children: controller.filteredHomeservers
.map(
@ -82,19 +83,20 @@ class HomeserverPickerView extends StatelessWidget {
.showServerInfo(server),
),
onTap: () => controller.setServer(
server
.homeserver.baseUrl.host),
server.homeserver.baseUrl.host,
),
title: Text(
server.homeserver.baseUrl.host,
style: const TextStyle(
color: Colors.black),
color: Colors.black,
),
),
subtitle: Text(
server.homeserver.description ??
'',
style: TextStyle(
color:
Colors.grey.shade700),
color: Colors.grey.shade700,
),
),
),
)

View File

@ -47,7 +47,8 @@ class ImageViewerView extends StatelessWidget {
tooltip: L10n.of(context)!.share,
color: Colors.white,
icon: Icon(Icons.adaptive.share_outlined),
))
),
)
],
),
body: InteractiveViewer(

View File

@ -71,8 +71,11 @@ class InvitationSelectionController extends State<InvitationSelection> {
future: () => room.invite(id),
);
if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(L10n.of(context)!.contactHasBeenInvitedToTheGroup)));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.contactHasBeenInvitedToTheGroup),
),
);
}
}
@ -99,7 +102,8 @@ class InvitationSelectionController extends State<InvitationSelection> {
response = await matrix.client.searchUserDirectory(text, limit: 10);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text((e).toLocalizedString(context))));
SnackBar(content: Text((e).toLocalizedString(context))),
);
return;
} finally {
setState(() => loading = false);
@ -108,19 +112,25 @@ class InvitationSelectionController extends State<InvitationSelection> {
foundProfiles = List<Profile>.from(response.results);
if (text.isValidMatrixId &&
foundProfiles.indexWhere((profile) => text == profile.userId) == -1) {
setState(() => foundProfiles = [
setState(
() => foundProfiles = [
Profile.fromJson({'user_id': text}),
]);
],
);
}
final participants = Matrix.of(context)
.client
.getRoomById(roomId!)!
.getParticipants()
.where((user) =>
[Membership.join, Membership.invite].contains(user.membership))
.where(
(user) =>
[Membership.join, Membership.invite].contains(user.membership),
)
.toList();
foundProfiles.removeWhere((profile) =>
participants.indexWhere((u) => u.id == profile.userId) != -1);
foundProfiles.removeWhere(
(profile) =>
participants.indexWhere((u) => u.id == profile.userId) != -1,
);
});
}

View File

@ -75,7 +75,9 @@ class InvitationSelectionView extends StatelessWidget {
),
subtitle: Text(controller.foundProfiles[i].userId),
onTap: () => controller.inviteAction(
context, controller.foundProfiles[i].userId),
context,
controller.foundProfiles[i].userId,
),
),
)
: FutureBuilder<List<User>>(
@ -106,7 +108,8 @@ class InvitationSelectionView extends StatelessWidget {
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary),
color: Theme.of(context).colorScheme.secondary,
),
),
onTap: () =>
controller.inviteAction(context, contacts[i].id),

View File

@ -82,7 +82,8 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
valid = false;
}
return valid;
});
},
);
if (valid.error != null) {
await showOkAlertDialog(
useRootNavigator: false,
@ -117,8 +118,10 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(L10n.of(context)!.askSSSSSign,
style: const TextStyle(fontSize: 20)),
Text(
L10n.of(context)!.askSSSSSign,
style: const TextStyle(fontSize: 20),
),
Container(height: 10),
TextField(
controller: textEditingController,
@ -141,18 +144,22 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
],
),
);
buttons.add(TextButton(
buttons.add(
TextButton(
child: Text(
L10n.of(context)!.submit,
),
onPressed: () => checkInput(textEditingController.text),
));
buttons.add(TextButton(
),
);
buttons.add(
TextButton(
child: Text(
L10n.of(context)!.skip,
),
onPressed: () => widget.request.openSSSS(skip: true),
));
),
);
break;
case KeyVerificationState.askAccept:
title = Text(L10n.of(context)!.newVerificationRequest);
@ -171,19 +178,23 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
)
],
);
buttons.add(TextButton.icon(
buttons.add(
TextButton.icon(
icon: const Icon(Icons.close),
style: TextButton.styleFrom(foregroundColor: Colors.red),
label: Text(L10n.of(context)!.reject),
onPressed: () => widget.request
.rejectVerification()
.then((_) => Navigator.of(context, rootNavigator: false).pop()),
));
buttons.add(TextButton.icon(
),
);
buttons.add(
TextButton.icon(
icon: const Icon(Icons.check),
label: Text(L10n.of(context)!.accept),
onPressed: () => widget.request.acceptVerification(),
));
),
);
break;
case KeyVerificationState.waitingAccept:
body = Center(
@ -245,19 +256,23 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
),
],
);
buttons.add(TextButton.icon(
buttons.add(
TextButton.icon(
icon: const Icon(Icons.close),
style: TextButton.styleFrom(
foregroundColor: Colors.red,
),
label: Text(L10n.of(context)!.theyDontMatch),
onPressed: () => widget.request.rejectSas(),
));
buttons.add(TextButton.icon(
),
);
buttons.add(
TextButton.icon(
icon: const Icon(Icons.check_outlined),
label: Text(L10n.of(context)!.theyMatch),
onPressed: () => widget.request.acceptSas(),
));
),
);
break;
case KeyVerificationState.waitingSas:
final acceptText = widget.request.sasTypes.contains('emoji')
@ -279,8 +294,11 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
body = Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Icon(Icons.check_circle_outlined,
color: Colors.green, size: 128.0),
const Icon(
Icons.check_circle_outlined,
color: Colors.green,
size: 128.0,
),
const SizedBox(height: 10),
Text(
L10n.of(context)!.verifySuccess,
@ -288,12 +306,14 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
),
],
);
buttons.add(TextButton(
buttons.add(
TextButton(
child: Text(
L10n.of(context)!.close,
),
onPressed: () => Navigator.of(context, rootNavigator: false).pop(),
));
),
);
break;
case KeyVerificationState.error:
body = Column(
@ -307,12 +327,14 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
),
],
);
buttons.add(TextButton(
buttons.add(
TextButton(
child: Text(
L10n.of(context)!.close,
),
onPressed: () => Navigator.of(context, rootNavigator: false).pop(),
));
),
);
break;
}
return Scaffold(
@ -350,7 +372,8 @@ class _Emoji extends StatelessWidget {
return emoji.name;
}
final translations = Map<String, String?>.from(
sasEmoji[emoji.number]['translated_descriptions']);
sasEmoji[emoji.number]['translated_descriptions'],
);
translations['en'] = emoji.name;
for (final locale in window.locales) {
final wantLocaleParts = locale.toString().split('_');

View File

@ -67,7 +67,8 @@ class LoginController extends State<Login> {
} else {
identifier = AuthenticationUserIdentifier(user: username);
}
await matrix.getLoginClient().login(LoginType.mLoginPassword,
await matrix.getLoginClient().login(
LoginType.mLoginPassword,
identifier: identifier,
// To stay compatible with older server versions
// ignore: deprecated_member_use
@ -75,7 +76,8 @@ class LoginController extends State<Login> {
? username
: null,
password: passwordController.text,
initialDeviceDisplayName: PlatformInfos.clientName);
initialDeviceDisplayName: PlatformInfos.clientName,
);
} on MatrixException catch (exception) {
setState(() => passwordError = exception.errorMessage);
return setState(() => loading = false);
@ -121,7 +123,8 @@ class LoginController extends State<Login> {
Matrix.of(context).getLoginClient().homeserver = oldHomeserver;
// okay, the server we checked does not appear to be a matrix server
Logs().v(
'$newDomain is not running a homeserver, asking to use $oldHomeserver');
'$newDomain is not running a homeserver, asking to use $oldHomeserver',
);
final dialogResult = await showOkCancelAlertDialog(
context: context,
useRootNavigator: false,
@ -230,7 +233,8 @@ class LoginController extends State<Login> {
);
if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context)!.passwordHasBeenChanged)));
SnackBar(content: Text(L10n.of(context)!.passwordHasBeenChanged)),
);
usernameController.text = input.single;
passwordController.text = password.single;
login();

View File

@ -19,14 +19,17 @@ class LoginView extends StatelessWidget {
automaticallyImplyLeading: !controller.loading,
centerTitle: true,
title: Text(
L10n.of(context)!.logInTo(Matrix.of(context)
L10n.of(context)!.logInTo(
Matrix.of(context)
.getLoginClient()
.homeserver
.toString()
.replaceFirst('https://', '')),
.replaceFirst('https://', ''),
),
),
body: Builder(builder: (context) {
),
body: Builder(
builder: (context) {
return AutofillGroup(
child: ListView(
children: <Widget>[
@ -85,7 +88,8 @@ class LoginView extends StatelessWidget {
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
foregroundColor:
Theme.of(context).colorScheme.onPrimary,
),
onPressed: controller.loading ? null : controller.login,
icon: const Icon(Icons.login_outlined),
@ -124,8 +128,9 @@ class LoginView extends StatelessWidget {
Padding(
padding: const EdgeInsets.all(12.0),
child: ElevatedButton.icon(
onPressed:
controller.loading ? () {} : controller.passwordForgotten,
onPressed: controller.loading
? () {}
: controller.passwordForgotten,
style: ElevatedButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.error,
backgroundColor: Theme.of(context).colorScheme.onError,
@ -137,7 +142,8 @@ class LoginView extends StatelessWidget {
],
),
);
}),
},
),
);
}
}

View File

@ -31,7 +31,8 @@ class NewGroupView extends StatelessWidget {
decoration: InputDecoration(
labelText: L10n.of(context)!.optionalGroupName,
prefixIcon: const Icon(Icons.people_outlined),
hintText: L10n.of(context)!.enterAGroupName),
hintText: L10n.of(context)!.enterAGroupName,
),
),
),
SwitchListTile.adaptive(

View File

@ -31,7 +31,8 @@ class NewSpaceView extends StatelessWidget {
decoration: InputDecoration(
labelText: L10n.of(context)!.spaceName,
prefixIcon: const Icon(Icons.people_outlined),
hintText: L10n.of(context)!.enterASpacepName),
hintText: L10n.of(context)!.enterASpacepName,
),
),
),
SwitchListTile.adaptive(

View File

@ -43,8 +43,8 @@ class SettingsView extends StatelessWidget {
future: controller.profileFuture,
builder: (context, snapshot) {
final profile = snapshot.data;
final mxid = Matrix.of(context).client.userID ??
L10n.of(context)!.user;
final mxid =
Matrix.of(context).client.userID ?? L10n.of(context)!.user;
final displayname =
profile?.displayName ?? mxid.localpart ?? mxid;
return Row(
@ -65,7 +65,8 @@ class SettingsView extends StatelessWidget {
color: Theme.of(context).dividerColor,
),
borderRadius: BorderRadius.circular(
Avatar.defaultSize * 2.5),
Avatar.defaultSize * 2.5,
),
),
child: Avatar(
mxContent: profile?.avatarUrl,
@ -131,7 +132,8 @@ class SettingsView extends StatelessWidget {
),
],
);
}),
},
),
const Divider(thickness: 1),
if (showChatBackupBanner == null)
ListTile(

View File

@ -83,7 +83,8 @@ class Settings3PidController extends State<Settings3Pid> {
future: () => Matrix.of(context).client.delete3pidFromAccount(
identifier.address,
identifier.medium,
));
),
);
if (success.error != null) return;
setState(() => request = null);
}

View File

@ -30,8 +30,10 @@ class Settings3PidView extends StatelessWidget {
body: MaxWidthBody(
child: FutureBuilder<List<ThirdPartyIdentifier>?>(
future: controller.request,
builder: (BuildContext context,
AsyncSnapshot<List<ThirdPartyIdentifier>?> snapshot) {
builder: (
BuildContext context,
AsyncSnapshot<List<ThirdPartyIdentifier>?> snapshot,
) {
if (snapshot.hasError) {
return Center(
child: Text(
@ -42,7 +44,8 @@ class Settings3PidView extends StatelessWidget {
}
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator.adaptive(strokeWidth: 2));
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
);
}
final identifier = snapshot.data!;
return Column(
@ -74,7 +77,8 @@ class Settings3PidView extends StatelessWidget {
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Colors.grey,
child: Icon(identifier[i].iconData)),
child: Icon(identifier[i].iconData),
),
title: Text(identifier[i].address),
trailing: IconButton(
tooltip: L10n.of(context)!.delete,

View File

@ -60,13 +60,20 @@ class EmotesSettingsController extends State<EmotesSettings> {
await showFutureLoadingDialog(
context: context,
future: () => client.setRoomStateWithKey(
room!.id, 'im.ponies.room_emotes', stateKey ?? '', pack!.toJson()),
room!.id,
'im.ponies.room_emotes',
stateKey ?? '',
pack!.toJson(),
),
);
} else {
await showFutureLoadingDialog(
context: context,
future: () => client.setAccountData(
client.userID!, 'im.ponies.user_emotes', pack!.toJson()),
client.userID!,
'im.ponies.user_emotes',
pack!.toJson(),
),
);
}
}
@ -95,7 +102,10 @@ class EmotesSettingsController extends State<EmotesSettings> {
await showFutureLoadingDialog(
context: context,
future: () => client.setAccountData(
client.userID!, 'im.ponies.emote_rooms', content),
client.userID!,
'im.ponies.emote_rooms',
content,
),
);
setState(() {});
}
@ -197,7 +207,8 @@ class EmotesSettingsController extends State<EmotesSettings> {
}
void imagePickerAction(
ValueNotifier<ImagePackImageContent?> controller) async {
ValueNotifier<ImagePackImageContent?> controller,
) async {
final result =
await FilePickerCross.importFromStorage(type: FileTypeCross.image);
if (result.fileName == null) return;

View File

@ -140,8 +140,8 @@ class EmotesSettingsView extends StatelessWidget {
actions: !useShortCuts
? {}
: {
SubmitLineIntent:
CallbackAction(onInvoke: (i) {
SubmitLineIntent: CallbackAction(
onInvoke: (i) {
controller.submitImageAction(
imageCode,
textEditingController.text,
@ -149,7 +149,8 @@ class EmotesSettingsView extends StatelessWidget {
textEditingController,
);
return null;
}),
},
),
},
child: TextField(
readOnly: controller.readonly,

View File

@ -72,7 +72,8 @@ class SettingsIgnoreListView extends StatelessWidget {
name: s.data?.displayName ?? client.ignoredUsers[i],
),
title: Text(
s.data?.displayName ?? client.ignoredUsers[i]),
s.data?.displayName ?? client.ignoredUsers[i],
),
trailing: IconButton(
tooltip: L10n.of(context)!.delete,
icon: const Icon(Icons.delete_forever_outlined),
@ -85,7 +86,8 @@ class SettingsIgnoreListView extends StatelessWidget {
),
),
);
}),
},
),
),
],
),

View File

@ -36,8 +36,7 @@ class MultipleEmotesSettingsView extends StatelessWidget {
itemCount: keys.length,
itemBuilder: (BuildContext context, int i) {
final event = packs[keys[i]];
String? packName =
keys[i].isNotEmpty ? keys[i] : 'Default Pack';
String? packName = keys[i].isNotEmpty ? keys[i] : 'Default Pack';
if (event != null && event.content['pack'] is Map) {
if (event.content['pack']['displayname'] is String) {
packName = event.content['pack']['displayname'];
@ -49,10 +48,12 @@ class MultipleEmotesSettingsView extends StatelessWidget {
title: Text(packName!),
onTap: () async {
VRouter.of(context).toSegments(
['rooms', room.id, 'details', 'emotes', keys[i]]);
['rooms', room.id, 'details', 'emotes', keys[i]],
);
},
);
},
);
});
},
),
);

View File

@ -36,14 +36,14 @@ class SettingsNotificationsView extends StatelessWidget {
SwitchListTile.adaptive(
value: !Matrix.of(context).client.allPushNotificationsMuted,
title: Text(
L10n.of(context)!.notificationsEnabledForThisAccount),
L10n.of(context)!.notificationsEnabledForThisAccount,
),
onChanged: (_) => showFutureLoadingDialog(
context: context,
future: () =>
Matrix.of(context).client.setMuteAllPushNotifications(
!Matrix.of(context)
future: () => Matrix.of(context)
.client
.allPushNotificationsMuted,
.setMuteAllPushNotifications(
!Matrix.of(context).client.allPushNotificationsMuted,
),
),
),
@ -90,7 +90,9 @@ class SettingsNotificationsView extends StatelessWidget {
if (snapshot.connectionState != ConnectionState.done) {
const Center(
child: CircularProgressIndicator.adaptive(
strokeWidth: 2));
strokeWidth: 2,
),
);
}
final pushers = snapshot.data ?? [];
return ListView.builder(
@ -99,7 +101,8 @@ class SettingsNotificationsView extends StatelessWidget {
itemCount: pushers.length,
itemBuilder: (_, i) => ListTile(
title: Text(
'${pushers[i].appDisplayName} - ${pushers[i].appId}'),
'${pushers[i].appDisplayName} - ${pushers[i].appId}',
),
subtitle: Text(pushers[i].data.url.toString()),
onTap: () => controller.onPusherTap(pushers[i]),
),
@ -108,7 +111,8 @@ class SettingsNotificationsView extends StatelessWidget {
),
],
);
}),
},
),
),
);
}

View File

@ -56,7 +56,8 @@ class SettingsSecurityController extends State<SettingsSecurity> {
);
if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context)!.passwordHasBeenChanged)));
SnackBar(content: Text(L10n.of(context)!.passwordHasBeenChanged)),
);
}
}
@ -151,7 +152,8 @@ class SettingsSecurityController extends State<SettingsSecurity> {
auth: AuthenticationPassword(
password: input.single,
identifier: AuthenticationUserIdentifier(
user: Matrix.of(context).client.userID!),
user: Matrix.of(context).client.userID!,
),
),
),
);
@ -184,7 +186,8 @@ class SettingsSecurityController extends State<SettingsSecurity> {
Uint8List.fromList(const Utf8Codec().encode(export!)),
path:
'/fluffychat-export-${DateFormat(DateFormat.YEAR_MONTH_DAY).format(DateTime.now())}.fluffybackup',
fileExtension: 'fluffybackup');
fileExtension: 'fluffybackup',
);
await filePickerCross.exportToStorage(
subject: L10n.of(context)!.dehydrateShare,
);

View File

@ -39,7 +39,8 @@ class SettingsStoriesController extends State<SettingsStories> {
setState(() {
users[user] = false;
});
});
},
);
return;
}
@ -54,7 +55,8 @@ class SettingsStoriesController extends State<SettingsStories> {
setState(() {
users[user] = true;
});
});
},
);
return;
}

View File

@ -40,7 +40,8 @@ class SettingsStoriesView extends StatelessWidget {
return const Center(
child: CircularProgressIndicator.adaptive(
strokeWidth: 2,
));
),
);
}
return ListView.builder(
itemCount: controller.users.length,

View File

@ -67,8 +67,10 @@ class SettingsStyleView extends StatelessWidget {
Icons.check,
size: 16,
color: Colors.white,
))
: null),
),
)
: null,
),
),
),
),
@ -118,7 +120,8 @@ class SettingsStyleView extends StatelessWidget {
),
onTap: controller.deleteWallpaperAction,
),
Builder(builder: (context) {
Builder(
builder: (context) {
return ListTile(
title: Text(L10n.of(context)!.changeWallpaper),
trailing: Icon(
@ -127,7 +130,8 @@ class SettingsStyleView extends StatelessWidget {
),
onTap: controller.setWallpaperAction,
);
}),
},
),
const Divider(height: 1),
ListTile(
title: Text(

View File

@ -46,10 +46,11 @@ class StoryPageController extends State<StoryPage> {
Timeline? timeline;
Event? get currentEvent => index < events.length ? events[index] : null;
StoryThemeData get storyThemeData =>
StoryThemeData.fromJson(currentEvent?.content
StoryThemeData get storyThemeData => StoryThemeData.fromJson(
currentEvent?.content
.tryGetMap<String, dynamic>(StoryThemeData.contentKey) ??
{});
{},
);
bool replyLoading = false;
bool _modalOpened = false;
@ -84,7 +85,8 @@ class StoryPageController extends State<StoryPage> {
final roomId = await client.startDirectChat(currentEvent.senderId);
var replyText = L10n.of(context)!.storyFrom(
currentEvent.originServerTs.localizedTime(context),
currentEvent.content.tryGet<String>('body') ?? '');
currentEvent.content.tryGet<String>('body') ?? '',
);
replyText = replyText.split('\n').map((line) => '> $line').join('\n');
message = '$replyText\n\n$message';
await client.getRoomById(roomId)!.sendTextEvent(message);
@ -325,7 +327,8 @@ class StoryPageController extends State<StoryPage> {
key: 0,
label: L10n.of(context)!.inoffensive,
),
]);
],
);
if (score == null) return;
final reason = await showTextInputDialog(
useRootNavigator: false,
@ -333,7 +336,8 @@ class StoryPageController extends State<StoryPage> {
title: L10n.of(context)!.whyDoYouWantToReportThis,
okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context)!.cancel,
textFields: [DialogTextField(hintText: L10n.of(context)!.reason)]);
textFields: [DialogTextField(hintText: L10n.of(context)!.reason)],
);
if (reason == null || reason.single.isEmpty) return;
final result = await showFutureLoadingDialog(
context: context,
@ -352,7 +356,9 @@ class StoryPageController extends State<StoryPage> {
}
Future<MatrixFile> downloadAndDecryptAttachment(
Event event, bool getThumbnail) async {
Event event,
bool getThumbnail,
) async {
return _fileCache[event.eventId] ??=
event.downloadAndDecryptAttachment(getThumbnail: getThumbnail);
}
@ -400,10 +406,12 @@ class StoryPageController extends State<StoryPage> {
final timeline = this.timeline = await room.getTimeline();
timeline.requestKeys();
var events = timeline.events
.where((e) =>
.where(
(e) =>
e.type == EventTypes.Message &&
!e.redacted &&
e.status == EventStatus.synced)
e.status == EventStatus.synced,
)
.toList();
final hasOutdatedEvents = events.removeOutdatedEvents();
@ -432,12 +440,16 @@ class StoryPageController extends State<StoryPage> {
// Preload images and videos
events
.where((event) => {MessageTypes.Image, MessageTypes.Video}
.contains(event.messageType))
.forEach((event) => downloadAndDecryptAttachment(
.where(
(event) => {MessageTypes.Image, MessageTypes.Video}
.contains(event.messageType),
)
.forEach(
(event) => downloadAndDecryptAttachment(
event,
event.messageType == MessageTypes.Video &&
PlatformInfos.isMobile));
event.messageType == MessageTypes.Video && PlatformInfos.isMobile,
),
);
// Reverse list
this.events.clear();
@ -502,9 +514,11 @@ class StoryPageController extends State<StoryPage> {
extension on List<Event> {
bool removeOutdatedEvents() {
final outdatedIndex = indexWhere((event) =>
final outdatedIndex = indexWhere(
(event) =>
DateTime.now().difference(event.originServerTs).inHours >
ClientStoriesExtension.lifeTimeInHours);
ClientStoriesExtension.lifeTimeInHours,
);
if (outdatedIndex != -1) {
removeRange(outdatedIndex, length);
return true;

View File

@ -146,7 +146,8 @@ class StoryView extends StatelessWidget {
return const Center(
child: CircularProgressIndicator.adaptive(
strokeWidth: 2,
));
),
);
}
if (events.isEmpty) {
return Center(
@ -218,7 +219,9 @@ class StoryView extends StatelessWidget {
!PlatformInfos.isMobile))
FutureBuilder<MatrixFile>(
future: controller.downloadAndDecryptAttachment(
event, event.messageType == MessageTypes.Video),
event,
event.messageType == MessageTypes.Video,
),
builder: (context, snapshot) {
final matrixFile = snapshot.data;
if (matrixFile == null) {
@ -364,7 +367,8 @@ class StoryView extends StatelessWidget {
height: 16,
child: Center(
child: CircularProgressIndicator.adaptive(
strokeWidth: 2),
strokeWidth: 2,
),
),
)
: IconButton(

View File

@ -70,7 +70,8 @@ class UserBottomSheetController extends State<UserBottomSheet> {
key: 0,
label: L10n.of(context)!.inoffensive,
),
]);
],
);
if (score == null) return;
final reason = await showTextInputDialog(
useRootNavigator: false,
@ -78,7 +79,8 @@ class UserBottomSheetController extends State<UserBottomSheet> {
title: L10n.of(context)!.whyDoYouWantToReportThis,
okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context)!.cancel,
textFields: [DialogTextField(hintText: L10n.of(context)!.reason)]);
textFields: [DialogTextField(hintText: L10n.of(context)!.reason)],
);
if (reason == null || reason.single.isEmpty) return;
final result = await showFutureLoadingDialog(
context: context,
@ -91,7 +93,8 @@ class UserBottomSheetController extends State<UserBottomSheet> {
);
if (result.error != null) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context)!.contentHasBeenReported)));
SnackBar(content: Text(L10n.of(context)!.contentHasBeenReported)),
);
break;
case UserBottomSheetAction.mention:
Navigator.of(context, rootNavigator: false).pop();
@ -152,8 +155,8 @@ class UserBottomSheetController extends State<UserBottomSheet> {
if (await askConfirmation()) {
await showFutureLoadingDialog(
context: context,
future: () =>
Matrix.of(context).client.ignoreUser(widget.user.id));
future: () => Matrix.of(context).client.ignoreUser(widget.user.id),
);
}
}
}

View File

@ -55,10 +55,12 @@ extension AccountBundlesExtension on Client {
}
ret ??= [];
if (ret.isEmpty) {
ret.add(AccountBundle(
ret.add(
AccountBundle(
name: userID,
priority: 0,
));
),
);
}
return ret;
}

View File

@ -78,7 +78,8 @@ class BackgroundPush {
firebase?.setListeners(
onMessage: (message) => pushHelper(
PushNotification.fromJson(
Map<String, dynamic>.from(message['data'] ?? message)),
Map<String, dynamic>.from(message['data'] ?? message),
),
client: client,
l10n: l10n,
activeRoomId: router?.currentState?.pathParameters['roomid'],
@ -331,7 +332,8 @@ class BackgroundPush {
}
} catch (e) {
Logs().i(
'[Push] No self-hosted unified push gateway present: $newEndpoint');
'[Push] No self-hosted unified push gateway present: $newEndpoint',
);
}
Logs().i('[Push] UnifiedPush using endpoint $endpoint');
final oldTokens = <String?>{};
@ -366,7 +368,8 @@ class BackgroundPush {
Future<void> _onUpMessage(Uint8List message, String i) async {
upAction = true;
final data = Map<String, dynamic>.from(
json.decode(utf8.decode(message))['notification']);
json.decode(utf8.decode(message))['notification'],
);
// UP may strip the devices list
data['devices'] ??= [];
await pushHelper(
@ -382,8 +385,11 @@ class BackgroundPush {
/// IDs we map the [roomId] to a number and store this number.
late Map<String, int> idMap;
Future<void> _loadIdMap() async {
idMap = Map<String, int>.from(json.decode(
(await store.getItem(SettingKeys.notificationCurrentIds)) ?? '{}'));
idMap = Map<String, int>.from(
json.decode(
(await store.getItem(SettingKeys.notificationCurrentIds)) ?? '{}',
),
);
}
Future<int> mapRoomIdToInt(String roomId) async {
@ -441,7 +447,8 @@ class BackgroundPush {
if (syncErrored) {
try {
Logs().v(
'[Push] failed to sync for fallback push, fetching notifications endpoint...');
'[Push] failed to sync for fallback push, fetching notifications endpoint...',
);
final notifications = await client.getNotifications(limit: 20);
final notificationRooms =
notifications.notifications.map((n) => n.roomId).toSet();
@ -451,7 +458,8 @@ class BackgroundPush {
} catch (e) {
Logs().v(
'[Push] failed to fetch pending notifications for clearing push, falling back...',
e);
e,
);
emptyRooms = client.rooms
.where((r) => r.notificationCount == 0)
.map((r) => r.id);
@ -474,7 +482,9 @@ class BackgroundPush {
}
if (changed) {
await store.setItem(
SettingKeys.notificationCurrentIds, json.encode(idMap));
SettingKeys.notificationCurrentIds,
json.encode(idMap),
);
}
} finally {
_clearingPushLock = false;

View File

@ -39,19 +39,25 @@ abstract class ClientManager {
}
final clients = clientNames.map(createClient).toList();
if (initialize) {
await Future.wait(clients.map((client) => client
await Future.wait(
clients.map(
(client) => client
.init(
waitForFirstSync: false,
waitUntilLoadCompletedLoaded: false,
)
.catchError(
(e, s) => Logs().e('Unable to initialize client', e, s))));
(e, s) => Logs().e('Unable to initialize client', e, s),
),
),
);
}
if (clients.length > 1 && clients.any((c) => !c.isLogged())) {
final loggedOutClients = clients.where((c) => !c.isLogged()).toList();
for (final client in loggedOutClients) {
Logs().w(
'Multi account is enabled but client ${client.userID} is not logged in. Removing...');
'Multi account is enabled but client ${client.userID} is not logged in. Removing...',
);
clientNames.remove(client.clientName);
clients.remove(client);
}

View File

@ -5,7 +5,8 @@ import 'package:matrix/matrix.dart';
import 'package:native_imaging/native_imaging.dart' as native;
Future<MatrixImageFileResizedResponse?> customImageResizer(
MatrixImageFileResizeArguments arguments) async {
MatrixImageFileResizeArguments arguments,
) async {
await native.init();
late native.Image nativeImg;
@ -21,7 +22,10 @@ Future<MatrixImageFileResizedResponse?> customImageResizer(
return null;
}
final rgba = Uint8List.view(
rgbaData.buffer, rgbaData.offsetInBytes, rgbaData.lengthInBytes);
rgbaData.buffer,
rgbaData.offsetInBytes,
rgbaData.lengthInBytes,
);
final width = dartFrame.image.width;
final height = dartFrame.image.height;

View File

@ -77,10 +77,15 @@ extension DateTimeExtension on DateTime {
}
} else if (sameYear) {
return L10n.of(context)!.dateWithoutYear(
month.toString().padLeft(2, '0'), day.toString().padLeft(2, '0'));
month.toString().padLeft(2, '0'),
day.toString().padLeft(2, '0'),
);
}
return L10n.of(context)!.dateWithYear(year.toString(),
month.toString().padLeft(2, '0'), day.toString().padLeft(2, '0'));
return L10n.of(context)!.dateWithYear(
year.toString(),
month.toString().padLeft(2, '0'),
day.toString().padLeft(2, '0'),
);
}
/// If the DateTime is today, this returns [localizedTimeOfDay()], if not it also
@ -95,7 +100,9 @@ extension DateTimeExtension on DateTime {
if (sameDay) return localizedTimeOfDay(context);
return L10n.of(context)!.dateAndTimeOfDay(
localizedTimeShort(context), localizedTimeOfDay(context));
localizedTimeShort(context),
localizedTimeOfDay(context),
);
}
static String _z(int i) => i < 10 ? '0${i.toString()}' : i.toString();

View File

@ -19,7 +19,8 @@ abstract class FluffyShare {
ClipboardData(text: text),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context)!.copiedToClipboard)));
SnackBar(content: Text(L10n.of(context)!.copiedToClipboard)),
);
return;
}
}

View File

@ -15,8 +15,10 @@ extension ClientStoriesExtension on Client {
List<User> get contacts => rooms
.where((room) => room.isDirectChat)
.map((room) =>
room.unsafeGetUserFromMemoryOrFallback(room.directChatMatrixID!))
.map(
(room) =>
room.unsafeGetUserFromMemoryOrFallback(room.directChatMatrixID!),
)
.toList();
List<Room> get storiesRooms =>
@ -78,10 +80,15 @@ extension ClientStoriesExtension on Client {
}
Future<Room?> getStoriesRoom(BuildContext context) async {
final candidates = rooms.where((room) =>
room.getState(EventTypes.RoomCreate)?.content.tryGet<String>('type') ==
final candidates = rooms.where(
(room) =>
room
.getState(EventTypes.RoomCreate)
?.content
.tryGet<String>('type') ==
storiesRoomType &&
room.ownPowerLevel >= 100);
room.ownPowerLevel >= 100,
);
if (candidates.isEmpty) return null;
if (candidates.length == 1) return candidates.single;
return await showModalActionSheet<Room>(
@ -92,9 +99,11 @@ extension ClientStoriesExtension on Client {
label: room.getLocalizedDisplayname(
MatrixLocals(L10n.of(context)!),
),
key: room),
key: room,
),
)
.toList());
.toList(),
);
}
}

View File

@ -23,7 +23,8 @@ class FlutterHiveCollectionsDatabase extends HiveCollectionsDatabase {
static const String _cipherStorageKey = 'hive_encryption_key';
static Future<FlutterHiveCollectionsDatabase> databaseBuilder(
Client client) async {
Client client,
) async {
Logs().d('Open Hive...');
HiveAesCipher? hiverCipher;
try {
@ -96,9 +97,9 @@ class FlutterHiveCollectionsDatabase extends HiveCollectionsDatabase {
}
}
// do not destroy your stable FluffyChat in debug mode
directory = Directory(directory.uri
.resolve(kDebugMode ? 'hive_debug' : 'hive')
.toFilePath());
directory = Directory(
directory.uri.resolve(kDebugMode ? 'hive_debug' : 'hive').toFilePath(),
);
directory.create(recursive: true);
path = directory.path;
}

View File

@ -8,8 +8,9 @@ extension IosBadgeClientExtension on Client {
void updateIosBadge() {
if (PlatformInfos.isIOS) {
// Workaround for iOS not clearing notifications with fcm_shared_isolate
if (!rooms.any((r) =>
r.membership == Membership.invite || (r.notificationCount > 0))) {
if (!rooms.any(
(r) => r.membership == Membership.invite || (r.notificationCount > 0),
)) {
// ignore: unawaited_futures
FlutterLocalNotificationsPlugin().cancelAll();
FlutterAppBadger.removeBadge();

View File

@ -62,7 +62,9 @@ class MatrixLocals extends MatrixLocalizations {
@override
String changedTheGuestAccessRulesTo(
String senderName, String localizedString) {
String senderName,
String localizedString,
) {
return l10n.changedTheGuestAccessRulesTo(senderName, localizedString);
}
@ -73,7 +75,9 @@ class MatrixLocals extends MatrixLocalizations {
@override
String changedTheHistoryVisibilityTo(
String senderName, String localizedString) {
String senderName,
String localizedString,
) {
return l10n.changedTheHistoryVisibilityTo(senderName, localizedString);
}

View File

@ -111,7 +111,9 @@ Future<void> _tryPushHelper(
await flutterLocalNotificationsPlugin.cancelAll();
final store = await SharedPreferences.getInstance();
await store.setString(
SettingKeys.notificationCurrentIds, json.encode({}));
SettingKeys.notificationCurrentIds,
json.encode({}),
);
}
}
return;
@ -237,7 +239,8 @@ Future<void> _tryPushHelper(
Future<int> mapRoomIdToInt(String roomId) async {
final store = await SharedPreferences.getInstance();
final idMap = Map<String, int>.from(
jsonDecode(store.getString(SettingKeys.notificationCurrentIds) ?? '{}'));
jsonDecode(store.getString(SettingKeys.notificationCurrentIds) ?? '{}'),
);
int? currentInt;
try {
currentInt = idMap[roomId];

View File

@ -55,11 +55,13 @@ extension RoomStatusExtension on Room {
} else if (typingUsers.length == 2) {
typingText = L10n.of(context)!.userAndUserAreTyping(
typingUsers.first.calcDisplayname(),
typingUsers[1].calcDisplayname());
typingUsers[1].calcDisplayname(),
);
} else if (typingUsers.length > 2) {
typingText = L10n.of(context)!.userAndOthersAreTyping(
typingUsers.first.calcDisplayname(),
(typingUsers.length - 1).toString());
(typingUsers.length - 1).toString(),
);
}
return typingText;
}
@ -76,8 +78,10 @@ extension RoomStatusExtension on Room {
break;
}
}
lastReceipts.removeWhere((user) =>
user.id == client.userID || user.id == timeline.events.first.senderId);
lastReceipts.removeWhere(
(user) =>
user.id == client.userID || user.id == timeline.events.first.senderId,
);
return lastReceipts.toList();
}
}

View File

@ -33,9 +33,11 @@ extension StreamExtension on Stream {
gotMessage = true;
}
};
final subscription = listen((_) => onMessage?.call(),
final subscription = listen(
(_) => onMessage?.call(),
onDone: () => controller.close(),
onError: (e, s) => controller.addError(e, s));
onError: (e, s) => controller.addError(e, s),
);
// add proper cleanup to the subscription and the controller, to not memory leak
controller.onCancel = () {
subscription.cancel();

View File

@ -82,7 +82,8 @@ extension UiaRequestManager on MatrixState {
);
default:
final url = Uri.parse(
'${client.homeserver}/_matrix/client/r0/auth/$stage/fallback/web?session=${uiaRequest.session}');
'${client.homeserver}/_matrix/client/r0/auth/$stage/fallback/web?session=${uiaRequest.session}',
);
launchUrlString(url.toString());
if (OkCancelResult.ok ==
await showOkCancelAlertDialog(

View File

@ -20,7 +20,8 @@ class UpdateCheckerNoStore {
static const gitLabHost = 'gitlab.com';
static Uri get tagsUri => Uri.parse(
'https://$gitLabHost/projects/$gitLabProjectId/repository/tags');
'https://$gitLabHost/projects/$gitLabProjectId/repository/tags',
);
final BuildContext context;

View File

@ -33,7 +33,8 @@ class UrlLauncher {
if (uri == null) {
// we can't open this thing
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context)!.cantOpenUri(url!))));
SnackBar(content: Text(L10n.of(context)!.cantOpenUri(url!))),
);
return;
}
if (!{'https', 'http'}.contains(uri.scheme)) {
@ -61,7 +62,8 @@ class UrlLauncher {
// transmute geo URIs on desktop to openstreetmap links, as those usually can't handle
// geo URIs
launchUrlString(
'https://www.openstreetmap.org/?mlat=${latlong.first}&mlon=${latlong.last}#map=16/${latlong.first}/${latlong.last}');
'https://www.openstreetmap.org/?mlat=${latlong.first}&mlon=${latlong.last}#map=16/${latlong.first}/${latlong.last}',
);
}
return;
}
@ -71,7 +73,8 @@ class UrlLauncher {
}
if (uri.host.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context)!.cantOpenUri(url!))));
SnackBar(content: Text(L10n.of(context)!.cantOpenUri(url!))),
);
return;
}
// okay, we have either an http or an https URI.
@ -86,8 +89,10 @@ class UrlLauncher {
}).join('.');
// Force LaunchMode.externalApplication, otherwise url_launcher will default
// to opening links in a webview on mobile platforms.
launchUrlString(uri.replace(host: newHost).toString(),
mode: LaunchMode.externalApplication);
launchUrlString(
uri.replace(host: newHost).toString(),
mode: LaunchMode.externalApplication,
);
}
void openMatrixToUrl() async {
@ -142,8 +147,10 @@ class UrlLauncher {
}
// we have the room, so....just open it
if (event != null) {
VRouter.of(context).toSegments(['rooms', room.id],
queryParameters: {'event': event});
VRouter.of(context).toSegments(
['rooms', room.id],
queryParameters: {'event': event},
);
} else {
VRouter.of(context).toSegments(['rooms', room.id]);
}
@ -176,10 +183,13 @@ class UrlLauncher {
// wait for two seconds so that it probably came down /sync
await showFutureLoadingDialog(
context: context,
future: () => Future.delayed(const Duration(seconds: 2)));
future: () => Future.delayed(const Duration(seconds: 2)),
);
if (event != null) {
VRouter.of(context).toSegments(['rooms', response.result!],
queryParameters: {'event': event});
VRouter.of(context).toSegments(
['rooms', response.result!],
queryParameters: {'event': event},
);
} else {
VRouter.of(context).toSegments(['rooms', response.result!]);
}

View File

@ -118,7 +118,8 @@ class CallKeepManager {
},
'android': alertOptions,
},
backgroundMode: true);
backgroundMode: true,
);
}
setupDone = true;
await displayIncomingCall(call);
@ -131,7 +132,8 @@ class CallKeepManager {
(event) {
if (event == CallEvent.kLocalHoldUnhold) {
Logs().i(
'Call hold event: local ${call.localHold}, remote ${call.remoteOnHold}');
'Call hold event: local ${call.localHold}, remote ${call.remoteOnHold}',
);
}
},
);
@ -169,10 +171,14 @@ class CallKeepManager {
_callKeep.on(CallKeepPerformAnswerCallAction(), answerCall);
_callKeep.on(CallKeepDidPerformDTMFAction(), didPerformDTMFAction);
_callKeep.on(
CallKeepDidReceiveStartCallAction(), didReceiveStartCallAction);
CallKeepDidReceiveStartCallAction(),
didReceiveStartCallAction,
);
_callKeep.on(CallKeepDidToggleHoldAction(), didToggleHoldCallAction);
_callKeep.on(
CallKeepDidPerformSetMutedCallAction(), didPerformSetMutedCallAction);
CallKeepDidPerformSetMutedCallAction(),
didPerformSetMutedCallAction,
);
_callKeep.on(CallKeepPerformEndCallAction(), endCall);
_callKeep.on(CallKeepPushKitToken(), onPushKitToken);
_callKeep.on(CallKeepDidDisplayIncomingCall(), didDisplayIncomingCall);
@ -209,11 +215,17 @@ class CallKeepManager {
Future<void> updateDisplay(String callUUID) async {
// Workaround because Android doesn't display well displayName, se we have to switch ...
if (isIOS) {
await _callKeep.updateDisplay(callUUID,
displayName: 'New Name', handle: callUUID);
await _callKeep.updateDisplay(
callUUID,
displayName: 'New Name',
handle: callUUID,
);
} else {
await _callKeep.updateDisplay(callUUID,
displayName: callUUID, handle: 'New Name');
await _callKeep.updateDisplay(
callUUID,
displayName: callUUID,
handle: 'New Name',
);
}
}
@ -250,7 +262,8 @@ class CallKeepManager {
const Divider(),
ListTile(
onTap: () => FlutterForegroundTask.openSystemAlertWindowSettings(
forceOpen: true),
forceOpen: true,
),
title: Text(L10n.of(context)!.appearOnTop),
subtitle: Text(L10n.of(context)!.appearOnTopDetails),
trailing: const Icon(Icons.file_upload_rounded),
@ -310,7 +323,8 @@ class CallKeepManager {
}
Future<void> didReceiveStartCallAction(
CallKeepDidReceiveStartCallAction event) async {
CallKeepDidReceiveStartCallAction event,
) async {
if (event.handle == null) {
// @TODO: sometime we receive `didReceiveStartCallAction` with handle` undefined`
return;
@ -328,7 +342,8 @@ class CallKeepManager {
}
Future<void> didPerformSetMutedCallAction(
CallKeepDidPerformSetMutedCallAction event) async {
CallKeepDidPerformSetMutedCallAction event,
) async {
final keeper = calls[event.callUUID];
if (event.muted!) {
keeper!.call.setMicrophoneMuted(true);
@ -339,7 +354,8 @@ class CallKeepManager {
}
Future<void> didToggleHoldCallAction(
CallKeepDidToggleHoldAction event) async {
CallKeepDidToggleHoldAction event,
) async {
final keeper = calls[event.callUUID];
if (event.hold!) {
keeper!.call.setRemoteOnHold(true);

View File

@ -89,7 +89,8 @@ class VoipPlugin with WidgetsBindingObserver implements WebRTCDelegate {
onClear: () {
overlayEntry?.remove();
overlayEntry = null;
}),
},
),
);
Overlay.of(context).insert(overlayEntry!);
}
@ -103,8 +104,9 @@ class VoipPlugin with WidgetsBindingObserver implements WebRTCDelegate {
@override
Future<RTCPeerConnection> createPeerConnection(
Map<String, dynamic> configuration,
[Map<String, dynamic> constraints = const {}]) =>
Map<String, dynamic> configuration, [
Map<String, dynamic> constraints = const {},
]) =>
webrtc_impl.createPeerConnection(configuration, constraints);
@override
@ -150,7 +152,9 @@ class VoipPlugin with WidgetsBindingObserver implements WebRTCDelegate {
try {
final wasForeground = await FlutterForegroundTask.isAppOnForeground;
await Store().setItem(
'wasForeground', wasForeground == true ? 'true' : 'false');
'wasForeground',
wasForeground == true ? 'true' : 'false',
);
FlutterForegroundTask.setOnLockScreenVisibility(true);
FlutterForegroundTask.wakeUpScreen();
FlutterForegroundTask.launchApp();
@ -162,10 +166,13 @@ class VoipPlugin with WidgetsBindingObserver implements WebRTCDelegate {
try {
if (!hasCallingAccount) {
ScaffoldMessenger.of(FluffyChatApp.routerKey.currentContext!)
.showSnackBar(const SnackBar(
.showSnackBar(
const SnackBar(
content: Text(
'No calling accounts found (used for native calls UI)',
)));
),
),
);
}
} catch (e) {
Logs().e('failed to show snackbar');

Some files were not shown because too many files have changed in this diff Show More