design: Column mode auto padding

This commit is contained in:
Christian Pauly 2021-04-09 18:26:44 +02:00
parent 0146767e8a
commit 75258271af
13 changed files with 1344 additions and 1239 deletions

View File

@ -10,6 +10,7 @@ import 'package:famedlysdk/famedlysdk.dart';
import 'package:file_picker_cross/file_picker_cross.dart';
import 'package:fluffychat/views/widgets/chat_settings_popup_menu.dart';
import 'package:fluffychat/views/widgets/content_banner.dart';
import 'package:fluffychat/views/widgets/max_width_body.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:fluffychat/views/widgets/list_items/participant_list_item.dart';
import 'package:fluffychat/utils/matrix_locals.dart';
@ -234,223 +235,188 @@ class _ChatDetailsState extends State<ChatDetails> {
),
),
],
body: ListView.builder(
itemCount: members.length + 1 + (canRequestMoreMembers ? 1 : 0),
itemBuilder: (BuildContext context, int i) => i == 0
? Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ListTile(
leading: room.canSendEvent('m.room.topic')
? CircleAvatar(
backgroundColor: Theme.of(context)
.scaffoldBackgroundColor,
foregroundColor: Colors.grey,
radius: Avatar.defaultSize / 2,
child: Icon(Icons.edit_outlined),
)
: null,
title: Text('${L10n.of(context).groupDescription}:',
style: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold)),
subtitle: LinkText(
text: room.topic?.isEmpty ?? true
? L10n.of(context).addGroupDescription
: room.topic,
linkStyle: TextStyle(color: Colors.blueAccent),
textStyle: TextStyle(
fontSize: 14,
color:
Theme.of(context).textTheme.bodyText2.color,
),
onLinkTap: (url) =>
UrlLauncher(context, url).launchUrl(),
),
onTap: room.canSendEvent('m.room.topic')
? () => setTopicAction(context)
: null,
),
Divider(thickness: 1),
ListTile(
title: Text(
L10n.of(context).settings,
style: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
),
),
if (room.canSendEvent('m.room.name'))
body: MaxWidthBody(
child: ListView.builder(
itemCount:
members.length + 1 + (canRequestMoreMembers ? 1 : 0),
itemBuilder: (BuildContext context, int i) => i == 0
? Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Colors.grey,
child: Icon(Icons.people_outlined),
),
leading: room.canSendEvent('m.room.topic')
? CircleAvatar(
backgroundColor: Theme.of(context)
.scaffoldBackgroundColor,
foregroundColor: Colors.grey,
radius: Avatar.defaultSize / 2,
child: Icon(Icons.edit_outlined),
)
: null,
title: Text(
L10n.of(context).changeTheNameOfTheGroup),
subtitle: Text(room.getLocalizedDisplayname(
MatrixLocals(L10n.of(context)))),
onTap: () => setDisplaynameAction(context),
),
if (room.canSendEvent('m.room.canonical_alias') &&
room.joinRules == JoinRules.public)
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Colors.grey,
child: Icon(Icons.link_outlined),
'${L10n.of(context).groupDescription}:',
style: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold)),
subtitle: LinkText(
text: room.topic?.isEmpty ?? true
? L10n.of(context).addGroupDescription
: room.topic,
linkStyle: TextStyle(color: Colors.blueAccent),
textStyle: TextStyle(
fontSize: 14,
color: Theme.of(context)
.textTheme
.bodyText2
.color,
),
onLinkTap: (url) =>
UrlLauncher(context, url).launchUrl(),
),
onTap: () => setCanonicalAliasAction(context),
title: Text(L10n.of(context).setInvitationLink),
subtitle: Text(
(room.canonicalAlias?.isNotEmpty ?? false)
? room.canonicalAlias
: L10n.of(context).none),
onTap: room.canSendEvent('m.room.topic')
? () => setTopicAction(context)
: null,
),
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Colors.grey,
child: Icon(Icons.insert_emoticon_outlined),
),
title: Text(L10n.of(context).emoteSettings),
subtitle: Text(L10n.of(context).setCustomEmotes),
onTap: () async {
// okay, we need to test if there are any emote state events other than the default one
// if so, we need to be directed to a selection screen for which pack we want to look at
// otherwise, we just open the normal one.
if ((room.states['im.ponies.room_emotes'] ??
<String, Event>{})
.keys
.any((String s) => s.isNotEmpty)) {
await AdaptivePageLayout.of(context)
.pushNamed('/rooms/${room.id}/emotes');
} else {
await AdaptivePageLayout.of(context).pushNamed(
'/settings/emotes',
arguments: {'room': room});
}
},
),
PopupMenuButton(
onSelected: (JoinRules joinRule) =>
showFutureLoadingDialog(
context: context,
future: () => room.setJoinRules(joinRule),
),
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<JoinRules>>[
if (room.canChangeJoinRules)
PopupMenuItem<JoinRules>(
value: JoinRules.public,
child: Text(JoinRules.public
.getLocalizedString(
MatrixLocals(L10n.of(context)))),
Divider(thickness: 1),
ListTile(
title: Text(
L10n.of(context).settings,
style: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
if (room.canChangeJoinRules)
PopupMenuItem<JoinRules>(
value: JoinRules.invite,
child: Text(JoinRules.invite
.getLocalizedString(
MatrixLocals(L10n.of(context)))),
),
],
child: ListTile(
leading: CircleAvatar(
),
),
if (room.canSendEvent('m.room.name'))
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Colors.grey,
child: Icon(Icons.public_outlined)),
title: Text(
L10n.of(context).whoIsAllowedToJoinThisGroup),
subtitle: Text(
room.joinRules.getLocalizedString(
MatrixLocals(L10n.of(context))),
child: Icon(Icons.people_outlined),
),
title: Text(
L10n.of(context).changeTheNameOfTheGroup),
subtitle: Text(room.getLocalizedDisplayname(
MatrixLocals(L10n.of(context)))),
onTap: () => setDisplaynameAction(context),
),
),
),
PopupMenuButton(
onSelected: (HistoryVisibility historyVisibility) =>
showFutureLoadingDialog(
context: context,
future: () =>
room.setHistoryVisibility(historyVisibility),
),
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<HistoryVisibility>>[
if (room.canChangeHistoryVisibility)
PopupMenuItem<HistoryVisibility>(
value: HistoryVisibility.invited,
child: Text(HistoryVisibility.invited
.getLocalizedString(
MatrixLocals(L10n.of(context)))),
if (room.canSendEvent('m.room.canonical_alias') &&
room.joinRules == JoinRules.public)
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Colors.grey,
child: Icon(Icons.link_outlined),
),
if (room.canChangeHistoryVisibility)
PopupMenuItem<HistoryVisibility>(
value: HistoryVisibility.joined,
child: Text(HistoryVisibility.joined
.getLocalizedString(
MatrixLocals(L10n.of(context)))),
),
if (room.canChangeHistoryVisibility)
PopupMenuItem<HistoryVisibility>(
value: HistoryVisibility.shared,
child: Text(HistoryVisibility.shared
.getLocalizedString(
MatrixLocals(L10n.of(context)))),
),
if (room.canChangeHistoryVisibility)
PopupMenuItem<HistoryVisibility>(
value: HistoryVisibility.world_readable,
child: Text(HistoryVisibility.world_readable
.getLocalizedString(
MatrixLocals(L10n.of(context)))),
),
],
child: ListTile(
onTap: () => setCanonicalAliasAction(context),
title: Text(L10n.of(context).setInvitationLink),
subtitle: Text(
(room.canonicalAlias?.isNotEmpty ?? false)
? room.canonicalAlias
: L10n.of(context).none),
),
ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Colors.grey,
child: Icon(Icons.visibility_outlined),
),
title: Text(
L10n.of(context).visibilityOfTheChatHistory),
subtitle: Text(
room.historyVisibility.getLocalizedString(
MatrixLocals(L10n.of(context))),
child: Icon(Icons.insert_emoticon_outlined),
),
title: Text(L10n.of(context).emoteSettings),
subtitle: Text(L10n.of(context).setCustomEmotes),
onTap: () async {
// okay, we need to test if there are any emote state events other than the default one
// if so, we need to be directed to a selection screen for which pack we want to look at
// otherwise, we just open the normal one.
if ((room.states['im.ponies.room_emotes'] ??
<String, Event>{})
.keys
.any((String s) => s.isNotEmpty)) {
await AdaptivePageLayout.of(context)
.pushNamed('/rooms/${room.id}/emotes');
} else {
await AdaptivePageLayout.of(context)
.pushNamed('/settings/emotes',
arguments: {'room': room});
}
},
),
),
if (room.joinRules == JoinRules.public)
PopupMenuButton(
onSelected: (GuestAccess guestAccess) =>
onSelected: (JoinRules joinRule) =>
showFutureLoadingDialog(
context: context,
future: () => room.setGuestAccess(guestAccess),
future: () => room.setJoinRules(joinRule),
),
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<GuestAccess>>[
if (room.canChangeGuestAccess)
PopupMenuItem<GuestAccess>(
value: GuestAccess.can_join,
child: Text(
GuestAccess.can_join.getLocalizedString(
MatrixLocals(L10n.of(context))),
),
<PopupMenuEntry<JoinRules>>[
if (room.canChangeJoinRules)
PopupMenuItem<JoinRules>(
value: JoinRules.public,
child: Text(JoinRules.public
.getLocalizedString(
MatrixLocals(L10n.of(context)))),
),
if (room.canChangeGuestAccess)
PopupMenuItem<GuestAccess>(
value: GuestAccess.forbidden,
child: Text(
GuestAccess.forbidden.getLocalizedString(
MatrixLocals(L10n.of(context))),
),
if (room.canChangeJoinRules)
PopupMenuItem<JoinRules>(
value: JoinRules.invite,
child: Text(JoinRules.invite
.getLocalizedString(
MatrixLocals(L10n.of(context)))),
),
],
child: ListTile(
leading: CircleAvatar(
backgroundColor: Theme.of(context)
.scaffoldBackgroundColor,
foregroundColor: Colors.grey,
child: Icon(Icons.public_outlined)),
title: Text(L10n.of(context)
.whoIsAllowedToJoinThisGroup),
subtitle: Text(
room.joinRules.getLocalizedString(
MatrixLocals(L10n.of(context))),
),
),
),
PopupMenuButton(
onSelected:
(HistoryVisibility historyVisibility) =>
showFutureLoadingDialog(
context: context,
future: () => room
.setHistoryVisibility(historyVisibility),
),
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<HistoryVisibility>>[
if (room.canChangeHistoryVisibility)
PopupMenuItem<HistoryVisibility>(
value: HistoryVisibility.invited,
child: Text(HistoryVisibility.invited
.getLocalizedString(
MatrixLocals(L10n.of(context)))),
),
if (room.canChangeHistoryVisibility)
PopupMenuItem<HistoryVisibility>(
value: HistoryVisibility.joined,
child: Text(HistoryVisibility.joined
.getLocalizedString(
MatrixLocals(L10n.of(context)))),
),
if (room.canChangeHistoryVisibility)
PopupMenuItem<HistoryVisibility>(
value: HistoryVisibility.shared,
child: Text(HistoryVisibility.shared
.getLocalizedString(
MatrixLocals(L10n.of(context)))),
),
if (room.canChangeHistoryVisibility)
PopupMenuItem<HistoryVisibility>(
value: HistoryVisibility.world_readable,
child: Text(HistoryVisibility.world_readable
.getLocalizedString(
MatrixLocals(L10n.of(context)))),
),
],
child: ListTile(
@ -458,75 +424,119 @@ class _ChatDetailsState extends State<ChatDetails> {
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Colors.grey,
child: Icon(Icons.info_outline),
child: Icon(Icons.visibility_outlined),
),
title: Text(
L10n.of(context).areGuestsAllowedToJoin),
title: Text(L10n.of(context)
.visibilityOfTheChatHistory),
subtitle: Text(
room.guestAccess.getLocalizedString(
room.historyVisibility.getLocalizedString(
MatrixLocals(L10n.of(context))),
),
),
),
ListTile(
title: Text(L10n.of(context).editChatPermissions),
subtitle:
Text(L10n.of(context).whoCanPerformWhichAction),
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Colors.grey,
child: Icon(Icons.edit_attributes_outlined),
),
onTap: () => AdaptivePageLayout.of(context)
.pushNamed('/rooms/${room.id}/permissions'),
),
Divider(thickness: 1),
ListTile(
title: Text(
actualMembersCount > 1
? L10n.of(context).countParticipants(
actualMembersCount.toString())
: L10n.of(context).emptyChat,
style: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
),
),
room.canInvite
? ListTile(
title: Text(L10n.of(context).inviteContact),
if (room.joinRules == JoinRules.public)
PopupMenuButton(
onSelected: (GuestAccess guestAccess) =>
showFutureLoadingDialog(
context: context,
future: () =>
room.setGuestAccess(guestAccess),
),
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<GuestAccess>>[
if (room.canChangeGuestAccess)
PopupMenuItem<GuestAccess>(
value: GuestAccess.can_join,
child: Text(
GuestAccess.can_join.getLocalizedString(
MatrixLocals(L10n.of(context))),
),
),
if (room.canChangeGuestAccess)
PopupMenuItem<GuestAccess>(
value: GuestAccess.forbidden,
child: Text(
GuestAccess.forbidden
.getLocalizedString(
MatrixLocals(L10n.of(context))),
),
),
],
child: ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).primaryColor,
foregroundColor: Colors.white,
radius: Avatar.defaultSize / 2,
child: Icon(Icons.add_outlined),
backgroundColor: Theme.of(context)
.scaffoldBackgroundColor,
foregroundColor: Colors.grey,
child: Icon(Icons.info_outline),
),
onTap: () => AdaptivePageLayout.of(context)
.pushNamed('/rooms/${room.id}/invite'),
)
: Container(),
],
)
: i < members.length + 1
? ParticipantListItem(members[i - 1])
: ListTile(
title: Text(L10n.of(context)
.loadCountMoreParticipants(
(actualMembersCount - members.length)
.toString())),
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
child: Icon(
Icons.refresh,
color: Colors.grey,
title: Text(
L10n.of(context).areGuestsAllowedToJoin),
subtitle: Text(
room.guestAccess.getLocalizedString(
MatrixLocals(L10n.of(context))),
),
),
),
ListTile(
title: Text(L10n.of(context).editChatPermissions),
subtitle: Text(
L10n.of(context).whoCanPerformWhichAction),
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Colors.grey,
child: Icon(Icons.edit_attributes_outlined),
),
onTap: () => AdaptivePageLayout.of(context)
.pushNamed('/rooms/${room.id}/permissions'),
),
Divider(thickness: 1),
ListTile(
title: Text(
actualMembersCount > 1
? L10n.of(context).countParticipants(
actualMembersCount.toString())
: L10n.of(context).emptyChat,
style: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
),
),
onTap: () => requestMoreMembersAction(context),
),
room.canInvite
? ListTile(
title: Text(L10n.of(context).inviteContact),
leading: CircleAvatar(
backgroundColor:
Theme.of(context).primaryColor,
foregroundColor: Colors.white,
radius: Avatar.defaultSize / 2,
child: Icon(Icons.add_outlined),
),
onTap: () => AdaptivePageLayout.of(context)
.pushNamed('/rooms/${room.id}/invite'),
)
: Container(),
],
)
: i < members.length + 1
? ParticipantListItem(members[i - 1])
: ListTile(
title: Text(L10n.of(context)
.loadCountMoreParticipants(
(actualMembersCount - members.length)
.toString())),
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
child: Icon(
Icons.refresh,
color: Colors.grey,
),
),
onTap: () => requestMoreMembersAction(context),
),
),
),
),
);

View File

@ -2,6 +2,7 @@ import 'package:famedlysdk/encryption.dart';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/views/widgets/avatar.dart';
import 'package:fluffychat/views/widgets/matrix.dart';
import 'package:fluffychat/views/widgets/max_width_body.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import '../views/widgets/dialogs/key_verification_dialog.dart';
@ -81,65 +82,132 @@ class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> {
),
),
),
body: StreamBuilder(
stream: room.onUpdate.stream,
builder: (context, snapshot) {
return FutureBuilder<List<DeviceKeys>>(
future: room.getUserDeviceKeys(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(L10n.of(context).oopsSomethingWentWrong +
': ' +
snapshot.error.toString()),
);
}
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
final deviceKeys = snapshot.data;
return ListView.builder(
itemCount: deviceKeys.length,
itemBuilder: (BuildContext context, int i) => Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
if (i == 0 ||
deviceKeys[i].userId != deviceKeys[i - 1].userId) ...{
Divider(height: 1, thickness: 1),
body: MaxWidthBody(
withScrolling: true,
child: StreamBuilder(
stream: room.onUpdate.stream,
builder: (context, snapshot) {
return FutureBuilder<List<DeviceKeys>>(
future: room.getUserDeviceKeys(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(L10n.of(context).oopsSomethingWentWrong +
': ' +
snapshot.error.toString()),
);
}
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
final deviceKeys = snapshot.data;
return ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: deviceKeys.length,
itemBuilder: (BuildContext context, int i) => Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
if (i == 0 ||
deviceKeys[i].userId !=
deviceKeys[i - 1].userId) ...{
Divider(height: 1, thickness: 1),
PopupMenuButton(
onSelected: (action) =>
onSelected(context, action, deviceKeys[i]),
itemBuilder: (c) {
var items = <PopupMenuEntry<String>>[];
if (room
.client
.userDeviceKeys[deviceKeys[i].userId]
.verified ==
UserVerifiedStatus.unknown) {
items.add(PopupMenuItem(
value: 'verify_user',
child: Text(L10n.of(context).verifyUser),
));
}
return items;
},
child: ListTile(
leading: Avatar(
room
.getUserByMXIDSync(deviceKeys[i].userId)
.avatarUrl,
room
.getUserByMXIDSync(deviceKeys[i].userId)
.calcDisplayname(),
),
title: Row(
children: [
Text(
room
.getUserByMXIDSync(deviceKeys[i].userId)
.calcDisplayname(),
),
Spacer(),
Text(
deviceKeys[i].userId,
style: TextStyle(
fontSize: 14,
color: Theme.of(context)
.textTheme
.bodyText1
.color
.withAlpha(150),
),
),
],
),
),
),
},
PopupMenuButton(
onSelected: (action) =>
onSelected(context, action, deviceKeys[i]),
itemBuilder: (c) {
var items = <PopupMenuEntry<String>>[];
if (room.client.userDeviceKeys[deviceKeys[i].userId]
.verified ==
UserVerifiedStatus.unknown) {
if (deviceKeys[i].blocked ||
!deviceKeys[i].verified) {
items.add(PopupMenuItem(
value: 'verify_user',
child: Text(L10n.of(context).verifyUser),
value:
deviceKeys[i].userId == room.client.userID
? 'verify'
: 'verify_user',
child: Text(L10n.of(context).verifyStart),
));
}
if (deviceKeys[i].blocked) {
items.add(PopupMenuItem(
value: 'unblock',
child: Text(L10n.of(context).unblockDevice),
));
}
if (!deviceKeys[i].blocked) {
items.add(PopupMenuItem(
value: 'block',
child: Text(L10n.of(context).blockDevice),
));
}
return items;
},
child: ListTile(
leading: Avatar(
room
.getUserByMXIDSync(deviceKeys[i].userId)
.avatarUrl,
room
.getUserByMXIDSync(deviceKeys[i].userId)
.calcDisplayname(),
leading: CircleAvatar(
foregroundColor:
Theme.of(context).textTheme.bodyText1.color,
backgroundColor:
Theme.of(context).secondaryHeaderColor,
child: Icon(deviceKeys[i].icon),
),
title: Row(
title: Text(
deviceKeys[i].displayname,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Row(
children: [
Text(
room
.getUserByMXIDSync(deviceKeys[i].userId)
.calcDisplayname(),
),
Spacer(),
Text(
deviceKeys[i].userId,
deviceKeys[i].deviceId,
style: TextStyle(
fontSize: 14,
color: Theme.of(context)
@ -149,91 +217,33 @@ class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> {
.withAlpha(150),
),
),
Spacer(),
Text(
deviceKeys[i].blocked
? L10n.of(context).blocked
: deviceKeys[i].verified
? L10n.of(context).verified
: L10n.of(context).unknownDevice,
style: TextStyle(
fontSize: 14,
color: deviceKeys[i].blocked
? Colors.red
: deviceKeys[i].verified
? Colors.green
: Colors.orange,
),
),
],
),
),
),
},
PopupMenuButton(
onSelected: (action) =>
onSelected(context, action, deviceKeys[i]),
itemBuilder: (c) {
var items = <PopupMenuEntry<String>>[];
if (deviceKeys[i].blocked ||
!deviceKeys[i].verified) {
items.add(PopupMenuItem(
value: deviceKeys[i].userId == room.client.userID
? 'verify'
: 'verify_user',
child: Text(L10n.of(context).verifyStart),
));
}
if (deviceKeys[i].blocked) {
items.add(PopupMenuItem(
value: 'unblock',
child: Text(L10n.of(context).unblockDevice),
));
}
if (!deviceKeys[i].blocked) {
items.add(PopupMenuItem(
value: 'block',
child: Text(L10n.of(context).blockDevice),
));
}
return items;
},
child: ListTile(
leading: CircleAvatar(
foregroundColor:
Theme.of(context).textTheme.bodyText1.color,
backgroundColor:
Theme.of(context).secondaryHeaderColor,
child: Icon(deviceKeys[i].icon),
),
title: Text(
deviceKeys[i].displayname,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Row(
children: [
Text(
deviceKeys[i].deviceId,
style: TextStyle(
fontSize: 14,
color: Theme.of(context)
.textTheme
.bodyText1
.color
.withAlpha(150),
),
),
Spacer(),
Text(
deviceKeys[i].blocked
? L10n.of(context).blocked
: deviceKeys[i].verified
? L10n.of(context).verified
: L10n.of(context).unknownDevice,
style: TextStyle(
fontSize: 14,
color: deviceKeys[i].blocked
? Colors.red
: deviceKeys[i].verified
? Colors.green
: Colors.orange,
),
),
],
),
),
),
],
),
);
},
);
}),
],
),
);
},
);
}),
),
);
}
}

View File

@ -3,6 +3,7 @@ import 'dart:developer';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
import 'package:fluffychat/views/widgets/dialogs/permission_slider_dialog.dart';
import 'package:fluffychat/views/widgets/max_width_body.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:fluffychat/views/widgets/matrix.dart';
@ -52,133 +53,137 @@ class ChatPermissionsSettings extends StatelessWidget {
leading: BackButton(),
title: Text(L10n.of(context).editChatPermissions),
),
body: StreamBuilder(
stream: Matrix.of(context).client.onSync.stream.where(
(e) =>
(e?.rooms?.join?.containsKey(roomId) ?? false) &&
(e.rooms.join[roomId]?.timeline?.events
?.any((s) => s.type == EventTypes.RoomPowerLevels) ??
false),
),
builder: (context, _) {
final room = Matrix.of(context).client.getRoomById(roomId);
final powerLevelsContent = Map<String, dynamic>.from(
room.getState(EventTypes.RoomPowerLevels).content);
final powerLevels = Map<String, dynamic>.from(powerLevelsContent)
..removeWhere((k, v) => !(v is int));
final eventsPowerLevels =
Map<String, dynamic>.from(powerLevelsContent['events'])
..removeWhere((k, v) => !(v is int));
body: MaxWidthBody(
withScrolling: true,
child: StreamBuilder(
stream: Matrix.of(context).client.onSync.stream.where(
(e) =>
(e?.rooms?.join?.containsKey(roomId) ?? false) &&
(e.rooms.join[roomId]?.timeline?.events?.any(
(s) => s.type == EventTypes.RoomPowerLevels) ??
false),
),
builder: (context, _) {
final room = Matrix.of(context).client.getRoomById(roomId);
final powerLevelsContent = Map<String, dynamic>.from(
room.getState(EventTypes.RoomPowerLevels).content);
final powerLevels = Map<String, dynamic>.from(powerLevelsContent)
..removeWhere((k, v) => !(v is int));
final eventsPowerLevels =
Map<String, dynamic>.from(powerLevelsContent['events'])
..removeWhere((k, v) => !(v is int));
return ListView(
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
for (var entry in powerLevels.entries)
PermissionsListTile(
permissionKey: entry.key,
permission: entry.value,
onTap: () =>
_editPowerLevel(context, entry.key, entry.value),
),
Divider(thickness: 1),
ListTile(
title: Text(
L10n.of(context).notifications,
style: TextStyle(
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
),
),
Builder(builder: (context) {
final key = 'rooms';
final int value =
powerLevelsContent.containsKey('notifications')
? powerLevelsContent['notifications']['rooms'] ?? 0
: 0;
return PermissionsListTile(
permissionKey: key,
permission: value,
category: 'notifications',
onTap: () => _editPowerLevel(context, key, value,
category: 'notifications'),
);
}),
Divider(thickness: 1),
ListTile(
title: Text(
L10n.of(context).configureChat,
style: TextStyle(
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
),
),
if (eventsPowerLevels != null)
for (var entry in eventsPowerLevels.entries)
return Column(
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
for (var entry in powerLevels.entries)
PermissionsListTile(
permissionKey: entry.key,
category: 'events',
permission: entry.value,
onTap: () => _editPowerLevel(
context, entry.key, entry.value,
category: 'events'),
onTap: () =>
_editPowerLevel(context, entry.key, entry.value),
),
if (room.ownPowerLevel >= 100) ...{
Divider(thickness: 1),
FutureBuilder<ServerCapabilities>(
future: room.client.requestServerCapabilities(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
final String roomVersion = room
.getState(EventTypes.RoomCreate)
.content['room_version'] ??
'1';
final shouldHaveVersion =
snapshot.data.mRoomVersions.defaultVersion;
return ListTile(
title: Text('Current room version: $roomVersion'),
subtitle: roomVersion == shouldHaveVersion
? null
: Text(
'Upgrade to $shouldHaveVersion available!',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).accentColor),
),
onTap: () async {
final newVersion =
await showConfirmationDialog<String>(
context: context,
title: 'Choose Room Version',
actions: snapshot
.data.mRoomVersions.available.entries
.where((r) => r.key != roomVersion)
.map((version) => AlertDialogAction(
key: version.key,
label:
'${version.key} (${version.value.toString().split('.').last})')),
);
await showFutureLoadingDialog(
context: context,
future: () =>
room.client.upgradeRoom(roomId, newVersion),
).then((_) => AdaptivePageLayout.of(context).pop());
},
);
},
ListTile(
title: Text(
L10n.of(context).notifications,
style: TextStyle(
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
),
),
},
],
),
],
);
},
Builder(builder: (context) {
final key = 'rooms';
final int value = powerLevelsContent
.containsKey('notifications')
? powerLevelsContent['notifications']['rooms'] ?? 0
: 0;
return PermissionsListTile(
permissionKey: key,
permission: value,
category: 'notifications',
onTap: () => _editPowerLevel(context, key, value,
category: 'notifications'),
);
}),
Divider(thickness: 1),
ListTile(
title: Text(
L10n.of(context).configureChat,
style: TextStyle(
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
),
),
if (eventsPowerLevels != null)
for (var entry in eventsPowerLevels.entries)
PermissionsListTile(
permissionKey: entry.key,
category: 'events',
permission: entry.value,
onTap: () => _editPowerLevel(
context, entry.key, entry.value,
category: 'events'),
),
if (room.ownPowerLevel >= 100) ...{
Divider(thickness: 1),
FutureBuilder<ServerCapabilities>(
future: room.client.requestServerCapabilities(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
final String roomVersion = room
.getState(EventTypes.RoomCreate)
.content['room_version'] ??
'1';
final shouldHaveVersion =
snapshot.data.mRoomVersions.defaultVersion;
return ListTile(
title: Text('Current room version: $roomVersion'),
subtitle: roomVersion == shouldHaveVersion
? null
: Text(
'Upgrade to $shouldHaveVersion available!',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).accentColor),
),
onTap: () async {
final newVersion =
await showConfirmationDialog<String>(
context: context,
title: 'Choose Room Version',
actions: snapshot
.data.mRoomVersions.available.entries
.where((r) => r.key != roomVersion)
.map((version) => AlertDialogAction(
key: version.key,
label:
'${version.key} (${version.value.toString().split('.').last})')),
);
await showFutureLoadingDialog(
context: context,
future: () =>
room.client.upgradeRoom(roomId, newVersion),
).then(
(_) => AdaptivePageLayout.of(context).pop());
},
);
},
),
},
],
),
],
);
},
),
),
);
}

View File

@ -5,6 +5,7 @@ import 'package:fluffychat/views/widgets/default_app_bar_search_field.dart';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/views/widgets/avatar.dart';
import 'package:fluffychat/views/widgets/max_width_body.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:fluffychat/views/widgets/matrix.dart';
import 'package:flutter/material.dart';
@ -128,45 +129,52 @@ class _InvitationSelectionState extends State<InvitationSelection> {
onChanged: (String text) => searchUserWithCoolDown(context, text),
),
),
body: foundProfiles.isNotEmpty
? ListView.builder(
itemCount: foundProfiles.length,
itemBuilder: (BuildContext context, int i) => ListTile(
leading: Avatar(
foundProfiles[i].avatarUrl,
foundProfiles[i].displayname ?? foundProfiles[i].userId,
),
title: Text(
foundProfiles[i].displayname ??
foundProfiles[i].userId.localpart,
),
subtitle: Text(foundProfiles[i].userId),
onTap: () => inviteAction(context, foundProfiles[i].userId),
),
)
: FutureBuilder<List<User>>(
future: getContacts(context),
builder: (BuildContext context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
var contacts = snapshot.data;
return ListView.builder(
itemCount: contacts.length,
itemBuilder: (BuildContext context, int i) => ListTile(
leading: Avatar(
contacts[i].avatarUrl,
contacts[i].calcDisplayname(),
),
title: Text(contacts[i].calcDisplayname()),
subtitle: Text(contacts[i].id),
onTap: () => inviteAction(context, contacts[i].id),
body: MaxWidthBody(
withScrolling: true,
child: foundProfiles.isNotEmpty
? ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: foundProfiles.length,
itemBuilder: (BuildContext context, int i) => ListTile(
leading: Avatar(
foundProfiles[i].avatarUrl,
foundProfiles[i].displayname ?? foundProfiles[i].userId,
),
);
},
),
title: Text(
foundProfiles[i].displayname ??
foundProfiles[i].userId.localpart,
),
subtitle: Text(foundProfiles[i].userId),
onTap: () => inviteAction(context, foundProfiles[i].userId),
),
)
: FutureBuilder<List<User>>(
future: getContacts(context),
builder: (BuildContext context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
var contacts = snapshot.data;
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: contacts.length,
itemBuilder: (BuildContext context, int i) => ListTile(
leading: Avatar(
contacts[i].avatarUrl,
contacts[i].calcDisplayname(),
),
title: Text(contacts[i].calcDisplayname()),
subtitle: Text(contacts[i].id),
onTap: () => inviteAction(context, contacts[i].id),
),
);
},
),
),
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
import 'package:famedlysdk/famedlysdk.dart' as sdk;
import 'package:fluffychat/views/widgets/max_width_body.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:fluffychat/views/widgets/matrix.dart';
import 'package:flutter/material.dart';
@ -45,32 +46,34 @@ class _NewGroupState extends State<NewGroup> {
title: Text(L10n.of(context).createNewGroup),
elevation: 0,
),
body: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(12.0),
child: TextField(
controller: controller,
autofocus: true,
autocorrect: false,
textInputAction: TextInputAction.go,
onSubmitted: (s) => submitAction(context),
decoration: InputDecoration(
labelText: L10n.of(context).optionalGroupName,
prefixIcon: Icon(Icons.people_outlined),
hintText: L10n.of(context).enterAGroupName),
body: MaxWidthBody(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(12.0),
child: TextField(
controller: controller,
autofocus: true,
autocorrect: false,
textInputAction: TextInputAction.go,
onSubmitted: (s) => submitAction(context),
decoration: InputDecoration(
labelText: L10n.of(context).optionalGroupName,
prefixIcon: Icon(Icons.people_outlined),
hintText: L10n.of(context).enterAGroupName),
),
),
),
SwitchListTile(
title: Text(L10n.of(context).groupIsPublic),
value: publicGroup,
onChanged: (bool b) => setState(() => publicGroup = b),
),
Expanded(
child: Image.asset('assets/new_group_wallpaper.png'),
),
],
SwitchListTile(
title: Text(L10n.of(context).groupIsPublic),
value: publicGroup,
onChanged: (bool b) => setState(() => publicGroup = b),
),
Expanded(
child: Image.asset('assets/new_group_wallpaper.png'),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => submitAction(context),

View File

@ -4,6 +4,7 @@ import 'package:adaptive_page_layout/adaptive_page_layout.dart';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/views/widgets/avatar.dart';
import 'package:fluffychat/views/widgets/contacts_list.dart';
import 'package:fluffychat/views/widgets/max_width_body.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:fluffychat/views/widgets/matrix.dart';
import 'package:fluffychat/utils/fluffy_share.dart';
@ -101,123 +102,127 @@ class _NewPrivateChatState extends State<NewPrivateChat> {
)
],
),
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(12.0),
child: Form(
key: _formKey,
child: TextFormField(
controller: controller,
//autofocus: true,
autocorrect: false,
onChanged: (String text) => searchUserWithCoolDown(context),
textInputAction: TextInputAction.go,
onFieldSubmitted: (s) => submitAction(context),
validator: (value) {
if (value.isEmpty) {
return L10n.of(context).pleaseEnterAMatrixIdentifier;
}
final matrix = Matrix.of(context);
var mxid = '@' + controller.text.trim();
if (mxid == matrix.client.userID) {
return L10n.of(context).youCannotInviteYourself;
}
if (!mxid.contains('@')) {
return L10n.of(context).makeSureTheIdentifierIsValid;
}
if (!mxid.contains(':')) {
return L10n.of(context).makeSureTheIdentifierIsValid;
}
return null;
},
decoration: InputDecoration(
labelText: L10n.of(context).enterAUsername,
prefixIcon: loading
? Container(
padding: const EdgeInsets.all(8.0),
width: 12,
height: 12,
child: CircularProgressIndicator(),
)
: correctMxId
? Padding(
padding: const EdgeInsets.all(8.0),
child: Avatar(
foundProfile.avatarUrl,
foundProfile.displayname ?? foundProfile.userId,
size: 12,
),
)
: Icon(Icons.account_circle_outlined),
prefixText: '@',
suffixIcon: IconButton(
onPressed: () => submitAction(context),
icon: Icon(Icons.arrow_forward_outlined),
body: MaxWidthBody(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(12.0),
child: Form(
key: _formKey,
child: TextFormField(
controller: controller,
//autofocus: true,
autocorrect: false,
onChanged: (String text) => searchUserWithCoolDown(context),
textInputAction: TextInputAction.go,
onFieldSubmitted: (s) => submitAction(context),
validator: (value) {
if (value.isEmpty) {
return L10n.of(context).pleaseEnterAMatrixIdentifier;
}
final matrix = Matrix.of(context);
var mxid = '@' + controller.text.trim();
if (mxid == matrix.client.userID) {
return L10n.of(context).youCannotInviteYourself;
}
if (!mxid.contains('@')) {
return L10n.of(context).makeSureTheIdentifierIsValid;
}
if (!mxid.contains(':')) {
return L10n.of(context).makeSureTheIdentifierIsValid;
}
return null;
},
decoration: InputDecoration(
labelText: L10n.of(context).enterAUsername,
prefixIcon: loading
? Container(
padding: const EdgeInsets.all(8.0),
width: 12,
height: 12,
child: CircularProgressIndicator(),
)
: correctMxId
? Padding(
padding: const EdgeInsets.all(8.0),
child: Avatar(
foundProfile.avatarUrl,
foundProfile.displayname ??
foundProfile.userId,
size: 12,
),
)
: Icon(Icons.account_circle_outlined),
prefixText: '@',
suffixIcon: IconButton(
onPressed: () => submitAction(context),
icon: Icon(Icons.arrow_forward_outlined),
),
hintText: '${L10n.of(context).username.toLowerCase()}',
),
hintText: '${L10n.of(context).username.toLowerCase()}',
),
),
),
),
Divider(height: 1),
ListTile(
leading: CircleAvatar(
radius: Avatar.defaultSize / 2,
foregroundColor: Theme.of(context).accentColor,
backgroundColor: Theme.of(context).secondaryHeaderColor,
child: Icon(Icons.share_outlined),
),
onTap: () => FluffyShare.share(
L10n.of(context).inviteText(Matrix.of(context).client.userID,
'https://matrix.to/#/${Matrix.of(context).client.userID}'),
context),
title: Text('${L10n.of(context).yourOwnUsername}:'),
subtitle: Text(
Matrix.of(context).client.userID,
style: TextStyle(color: Theme.of(context).accentColor),
),
),
Divider(height: 1),
if (foundProfiles.isNotEmpty)
Expanded(
child: ListView.builder(
itemCount: foundProfiles.length,
itemBuilder: (BuildContext context, int i) {
var foundProfile = foundProfiles[i];
return ListTile(
onTap: () {
setState(() {
controller.text = currentSearchTerm =
foundProfile.userId.substring(1);
});
},
leading: Avatar(
foundProfile.avatarUrl,
foundProfile.displayname ?? foundProfile.userId,
//size: 24,
),
title: Text(
foundProfile.displayname ?? foundProfile.userId.localpart,
style: TextStyle(),
maxLines: 1,
),
subtitle: Text(
foundProfile.userId,
maxLines: 1,
style: TextStyle(
fontSize: 12,
),
),
);
},
Divider(height: 1),
ListTile(
leading: CircleAvatar(
radius: Avatar.defaultSize / 2,
foregroundColor: Theme.of(context).accentColor,
backgroundColor: Theme.of(context).secondaryHeaderColor,
child: Icon(Icons.share_outlined),
),
onTap: () => FluffyShare.share(
L10n.of(context).inviteText(Matrix.of(context).client.userID,
'https://matrix.to/#/${Matrix.of(context).client.userID}'),
context),
title: Text('${L10n.of(context).yourOwnUsername}:'),
subtitle: Text(
Matrix.of(context).client.userID,
style: TextStyle(color: Theme.of(context).accentColor),
),
),
if (foundProfiles.isEmpty)
Expanded(
child: ContactsList(searchController: controller),
),
],
Divider(height: 1),
if (foundProfiles.isNotEmpty)
Expanded(
child: ListView.builder(
itemCount: foundProfiles.length,
itemBuilder: (BuildContext context, int i) {
var foundProfile = foundProfiles[i];
return ListTile(
onTap: () {
setState(() {
controller.text = currentSearchTerm =
foundProfile.userId.substring(1);
});
},
leading: Avatar(
foundProfile.avatarUrl,
foundProfile.displayname ?? foundProfile.userId,
//size: 24,
),
title: Text(
foundProfile.displayname ??
foundProfile.userId.localpart,
style: TextStyle(),
maxLines: 1,
),
subtitle: Text(
foundProfile.userId,
maxLines: 1,
style: TextStyle(
fontSize: 12,
),
),
);
},
),
),
if (foundProfiles.isEmpty)
Expanded(
child: ContactsList(searchController: controller),
),
],
),
),
);
}

View File

@ -1,5 +1,6 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/views/widgets/max_width_body.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:fluffychat/views/widgets/matrix.dart';
import 'package:flutter/material.dart';
@ -99,64 +100,67 @@ class _Settings3PidState extends State<Settings3Pid> {
)
],
),
body: FutureBuilder<List<ThirdPartyIdentifier>>(
future: _request,
builder: (BuildContext context,
AsyncSnapshot<List<ThirdPartyIdentifier>> snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(
snapshot.error.toString(),
textAlign: TextAlign.center,
),
);
}
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
final identifier = snapshot.data;
return Column(
children: [
ListTile(
leading: CircleAvatar(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
foregroundColor:
identifier.isEmpty ? Colors.orange : Colors.grey,
child: Icon(
body: MaxWidthBody(
child: FutureBuilder<List<ThirdPartyIdentifier>>(
future: _request,
builder: (BuildContext context,
AsyncSnapshot<List<ThirdPartyIdentifier>> snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(
snapshot.error.toString(),
textAlign: TextAlign.center,
),
);
}
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
final identifier = snapshot.data;
return Column(
children: [
ListTile(
leading: CircleAvatar(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
foregroundColor:
identifier.isEmpty ? Colors.orange : Colors.grey,
child: Icon(
identifier.isEmpty
? Icons.warning_outlined
: Icons.info_outlined,
),
),
title: Text(
identifier.isEmpty
? Icons.warning_outlined
: Icons.info_outlined,
? L10n.of(context).noPasswordRecoveryDescription
: L10n.of(context)
.withTheseAddressesRecoveryDescription,
),
),
title: Text(
identifier.isEmpty
? L10n.of(context).noPasswordRecoveryDescription
: L10n.of(context).withTheseAddressesRecoveryDescription,
),
),
Divider(height: 1),
Expanded(
child: ListView.builder(
itemCount: identifier.length,
itemBuilder: (BuildContext context, int i) => ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Colors.grey,
child: Icon(identifier[i].iconData)),
title: Text(identifier[i].address),
trailing: IconButton(
tooltip: L10n.of(context).delete,
icon: Icon(Icons.delete_forever_outlined),
color: Colors.red,
onPressed: () => _delete3Pid(context, identifier[i]),
Divider(height: 1),
Expanded(
child: ListView.builder(
itemCount: identifier.length,
itemBuilder: (BuildContext context, int i) => ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Colors.grey,
child: Icon(identifier[i].iconData)),
title: Text(identifier[i].address),
trailing: IconButton(
tooltip: L10n.of(context).delete,
icon: Icon(Icons.delete_forever_outlined),
color: Colors.red,
onPressed: () => _delete3Pid(context, identifier[i]),
),
),
),
),
),
],
);
},
],
);
},
),
),
);
}

View File

@ -2,6 +2,7 @@ import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:famedlysdk/encryption/utils/key_verification.dart';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/views/widgets/dialogs/key_verification_dialog.dart';
import 'package:fluffychat/views/widgets/max_width_body.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
@ -131,83 +132,86 @@ class DevicesSettingsState extends State<DevicesSettings> {
leading: BackButton(),
title: Text(L10n.of(context).devices),
),
body: FutureBuilder<bool>(
future: _loadUserDevices(context),
builder: (BuildContext context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.error_outlined),
Text(snapshot.error.toString()),
],
),
);
}
if (!snapshot.hasData || this.devices == null) {
return Center(child: CircularProgressIndicator());
}
Function isOwnDevice = (Device userDevice) =>
userDevice.deviceId == Matrix.of(context).client.deviceID;
final devices = List<Device>.from(this.devices);
var thisDevice = devices.firstWhere(isOwnDevice, orElse: () => null);
devices.removeWhere(isOwnDevice);
devices.sort((a, b) => b.lastSeenTs.compareTo(a.lastSeenTs));
return Column(
children: <Widget>[
if (thisDevice != null)
UserDeviceListItem(
thisDevice,
rename: (d) => _renameDeviceAction(context, d),
remove: (d) => _removeDevicesAction(context, [d]),
verify: (d) => _verifyDeviceAction(context, d),
block: (d) => _blockDeviceAction(context, d),
unblock: (d) => _unblockDeviceAction(context, d),
body: MaxWidthBody(
child: FutureBuilder<bool>(
future: _loadUserDevices(context),
builder: (BuildContext context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.error_outlined),
Text(snapshot.error.toString()),
],
),
Divider(height: 1),
if (devices.isNotEmpty)
ListTile(
title: Text(
_errorDeletingDevices ??
L10n.of(context).removeAllOtherDevices,
style: TextStyle(color: Colors.red),
);
}
if (!snapshot.hasData || this.devices == null) {
return Center(child: CircularProgressIndicator());
}
Function isOwnDevice = (Device userDevice) =>
userDevice.deviceId == Matrix.of(context).client.deviceID;
final devices = List<Device>.from(this.devices);
var thisDevice =
devices.firstWhere(isOwnDevice, orElse: () => null);
devices.removeWhere(isOwnDevice);
devices.sort((a, b) => b.lastSeenTs.compareTo(a.lastSeenTs));
return Column(
children: <Widget>[
if (thisDevice != null)
UserDeviceListItem(
thisDevice,
rename: (d) => _renameDeviceAction(context, d),
remove: (d) => _removeDevicesAction(context, [d]),
verify: (d) => _verifyDeviceAction(context, d),
block: (d) => _blockDeviceAction(context, d),
unblock: (d) => _unblockDeviceAction(context, d),
),
trailing: _loadingDeletingDevices
? CircularProgressIndicator()
: Icon(Icons.delete_outline),
onTap: _loadingDeletingDevices
? null
: () => _removeDevicesAction(context, devices),
Divider(height: 1),
if (devices.isNotEmpty)
ListTile(
title: Text(
_errorDeletingDevices ??
L10n.of(context).removeAllOtherDevices,
style: TextStyle(color: Colors.red),
),
trailing: _loadingDeletingDevices
? CircularProgressIndicator()
: Icon(Icons.delete_outline),
onTap: _loadingDeletingDevices
? null
: () => _removeDevicesAction(context, devices),
),
Divider(height: 1),
Expanded(
child: devices.isEmpty
? Center(
child: Icon(
Icons.devices_other,
size: 60,
color: Theme.of(context).secondaryHeaderColor,
),
)
: ListView.separated(
separatorBuilder: (BuildContext context, int i) =>
Divider(height: 1),
itemCount: devices.length,
itemBuilder: (BuildContext context, int i) =>
UserDeviceListItem(
devices[i],
rename: (d) => _renameDeviceAction(context, d),
remove: (d) => _removeDevicesAction(context, [d]),
verify: (d) => _verifyDeviceAction(context, d),
block: (d) => _blockDeviceAction(context, d),
unblock: (d) => _unblockDeviceAction(context, d),
),
),
),
Divider(height: 1),
Expanded(
child: devices.isEmpty
? Center(
child: Icon(
Icons.devices_other,
size: 60,
color: Theme.of(context).secondaryHeaderColor,
),
)
: ListView.separated(
separatorBuilder: (BuildContext context, int i) =>
Divider(height: 1),
itemCount: devices.length,
itemBuilder: (BuildContext context, int i) =>
UserDeviceListItem(
devices[i],
rename: (d) => _renameDeviceAction(context, d),
remove: (d) => _removeDevicesAction(context, [d]),
verify: (d) => _verifyDeviceAction(context, d),
block: (d) => _blockDeviceAction(context, d),
unblock: (d) => _unblockDeviceAction(context, d),
),
),
),
],
);
},
],
);
},
),
),
);
}

View File

@ -6,6 +6,7 @@ import 'package:famedlysdk/famedlysdk.dart';
import 'package:file_picker_cross/file_picker_cross.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/views/widgets/max_width_body.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
@ -184,223 +185,227 @@ class _EmotesSettingsState extends State<EmotesSettings> {
child: Icon(Icons.save_outlined, color: Colors.white),
)
: null,
body: StreamBuilder(
stream: widget.room?.onUpdate?.stream,
builder: (context, snapshot) {
return Column(
children: <Widget>[
if (!readonly)
Container(
padding: EdgeInsets.symmetric(
vertical: 8.0,
),
child: ListTile(
leading: Container(
width: 180.0,
height: 38,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)),
color: Theme.of(context).secondaryHeaderColor,
),
child: TextField(
controller: newEmoteController,
autocorrect: false,
minLines: 1,
maxLines: 1,
decoration: InputDecoration(
hintText: L10n.of(context).emoteShortcode,
prefixText: ': ',
suffixText: ':',
prefixStyle: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
suffixStyle: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
border: InputBorder.none,
),
),
body: MaxWidthBody(
child: StreamBuilder(
stream: widget.room?.onUpdate?.stream,
builder: (context, snapshot) {
return Column(
children: <Widget>[
if (!readonly)
Container(
padding: EdgeInsets.symmetric(
vertical: 8.0,
),
title: _EmoteImagePicker(newMxcController),
trailing: InkWell(
onTap: () async {
if (newEmoteController.text == null ||
newEmoteController.text.isEmpty ||
newMxcController.text == null ||
newMxcController.text.isEmpty) {
await showOkAlertDialog(
context: context,
message: L10n.of(context).emoteWarnNeedToPick,
okLabel: L10n.of(context).ok,
useRootNavigator: false,
);
return;
}
final emoteCode = ':${newEmoteController.text}:';
final mxc = newMxcController.text;
if (emotes.indexWhere((e) =>
e.emote == emoteCode && e.mxc != mxc) !=
-1) {
await showOkAlertDialog(
context: context,
message: L10n.of(context).emoteExists,
okLabel: L10n.of(context).ok,
useRootNavigator: false,
);
return;
}
if (!RegExp(r'^:[-\w]+:$').hasMatch(emoteCode)) {
await showOkAlertDialog(
context: context,
message: L10n.of(context).emoteInvalid,
okLabel: L10n.of(context).ok,
useRootNavigator: false,
);
return;
}
emotes.add(_EmoteEntry(emote: emoteCode, mxc: mxc));
await _save(context);
setState(() {
newEmoteController.text = '';
newMxcController.text = '';
showSave = false;
});
},
child: Icon(
Icons.add_outlined,
color: Colors.green,
size: 32.0,
),
),
),
),
if (widget.room != null)
ListTile(
title: Text(L10n.of(context).enableEmotesGlobally),
trailing: Switch(
value: isGloballyActive(client),
onChanged: (bool newValue) async {
await _setIsGloballyActive(context, newValue);
setState(() => null);
},
),
),
if (!readonly || widget.room != null)
Divider(
height: 2,
thickness: 2,
color: Theme.of(context).primaryColor,
),
Expanded(
child: emotes.isEmpty
? Center(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
L10n.of(context).noEmotesFound,
style: TextStyle(fontSize: 20),
),
child: ListTile(
leading: Container(
width: 180.0,
height: 38,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)),
color: Theme.of(context).secondaryHeaderColor,
),
)
: ListView.separated(
separatorBuilder: (BuildContext context, int i) =>
Container(),
itemCount: emotes.length + 1,
itemBuilder: (BuildContext context, int i) {
if (i >= emotes.length) {
return Container(height: 70);
}
final emote = emotes[i];
final controller = TextEditingController();
controller.text = emote.emoteClean;
return ListTile(
leading: Container(
width: 180.0,
height: 38,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(10)),
color: Theme.of(context).secondaryHeaderColor,
),
child: TextField(
readOnly: readonly,
controller: controller,
autocorrect: false,
minLines: 1,
maxLines: 1,
decoration: InputDecoration(
hintText: L10n.of(context).emoteShortcode,
prefixText: ': ',
suffixText: ':',
prefixStyle: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
suffixStyle: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
border: InputBorder.none,
),
onSubmitted: (s) {
final emoteCode = ':$s:';
if (emotes.indexWhere((e) =>
e.emote == emoteCode &&
e.mxc != emote.mxc) !=
-1) {
controller.text = emote.emoteClean;
showOkAlertDialog(
context: context,
message: L10n.of(context).emoteExists,
okLabel: L10n.of(context).ok,
useRootNavigator: false,
);
return;
}
if (!RegExp(r'^:[-\w]+:$')
.hasMatch(emoteCode)) {
controller.text = emote.emoteClean;
showOkAlertDialog(
context: context,
message: L10n.of(context).emoteInvalid,
okLabel: L10n.of(context).ok,
useRootNavigator: false,
);
return;
}
setState(() {
emote.emote = emoteCode;
showSave = true;
});
},
),
child: TextField(
controller: newEmoteController,
autocorrect: false,
minLines: 1,
maxLines: 1,
decoration: InputDecoration(
hintText: L10n.of(context).emoteShortcode,
prefixText: ': ',
suffixText: ':',
prefixStyle: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
title: _EmoteImage(emote.mxc),
trailing: readonly
? null
: InkWell(
onTap: () => setState(() {
emotes.removeWhere(
(e) => e.emote == emote.emote);
showSave = true;
}),
child: Icon(
Icons.delete_forever_outlined,
color: Colors.red,
size: 32.0,
),
),
);
},
suffixStyle: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
border: InputBorder.none,
),
),
),
),
],
);
}),
title: _EmoteImagePicker(newMxcController),
trailing: InkWell(
onTap: () async {
if (newEmoteController.text == null ||
newEmoteController.text.isEmpty ||
newMxcController.text == null ||
newMxcController.text.isEmpty) {
await showOkAlertDialog(
context: context,
message: L10n.of(context).emoteWarnNeedToPick,
okLabel: L10n.of(context).ok,
useRootNavigator: false,
);
return;
}
final emoteCode = ':${newEmoteController.text}:';
final mxc = newMxcController.text;
if (emotes.indexWhere((e) =>
e.emote == emoteCode && e.mxc != mxc) !=
-1) {
await showOkAlertDialog(
context: context,
message: L10n.of(context).emoteExists,
okLabel: L10n.of(context).ok,
useRootNavigator: false,
);
return;
}
if (!RegExp(r'^:[-\w]+:$').hasMatch(emoteCode)) {
await showOkAlertDialog(
context: context,
message: L10n.of(context).emoteInvalid,
okLabel: L10n.of(context).ok,
useRootNavigator: false,
);
return;
}
emotes.add(_EmoteEntry(emote: emoteCode, mxc: mxc));
await _save(context);
setState(() {
newEmoteController.text = '';
newMxcController.text = '';
showSave = false;
});
},
child: Icon(
Icons.add_outlined,
color: Colors.green,
size: 32.0,
),
),
),
),
if (widget.room != null)
ListTile(
title: Text(L10n.of(context).enableEmotesGlobally),
trailing: Switch(
value: isGloballyActive(client),
onChanged: (bool newValue) async {
await _setIsGloballyActive(context, newValue);
setState(() => null);
},
),
),
if (!readonly || widget.room != null)
Divider(
height: 2,
thickness: 2,
color: Theme.of(context).primaryColor,
),
Expanded(
child: emotes.isEmpty
? Center(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
L10n.of(context).noEmotesFound,
style: TextStyle(fontSize: 20),
),
),
)
: ListView.separated(
separatorBuilder: (BuildContext context, int i) =>
Container(),
itemCount: emotes.length + 1,
itemBuilder: (BuildContext context, int i) {
if (i >= emotes.length) {
return Container(height: 70);
}
final emote = emotes[i];
final controller = TextEditingController();
controller.text = emote.emoteClean;
return ListTile(
leading: Container(
width: 180.0,
height: 38,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(10)),
color:
Theme.of(context).secondaryHeaderColor,
),
child: TextField(
readOnly: readonly,
controller: controller,
autocorrect: false,
minLines: 1,
maxLines: 1,
decoration: InputDecoration(
hintText: L10n.of(context).emoteShortcode,
prefixText: ': ',
suffixText: ':',
prefixStyle: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
suffixStyle: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
border: InputBorder.none,
),
onSubmitted: (s) {
final emoteCode = ':$s:';
if (emotes.indexWhere((e) =>
e.emote == emoteCode &&
e.mxc != emote.mxc) !=
-1) {
controller.text = emote.emoteClean;
showOkAlertDialog(
context: context,
message: L10n.of(context).emoteExists,
okLabel: L10n.of(context).ok,
useRootNavigator: false,
);
return;
}
if (!RegExp(r'^:[-\w]+:$')
.hasMatch(emoteCode)) {
controller.text = emote.emoteClean;
showOkAlertDialog(
context: context,
message:
L10n.of(context).emoteInvalid,
okLabel: L10n.of(context).ok,
useRootNavigator: false,
);
return;
}
setState(() {
emote.emote = emoteCode;
showSave = true;
});
},
),
),
title: _EmoteImage(emote.mxc),
trailing: readonly
? null
: InkWell(
onTap: () => setState(() {
emotes.removeWhere(
(e) => e.emote == emote.emote);
showSave = true;
}),
child: Icon(
Icons.delete_forever_outlined,
color: Colors.red,
size: 32.0,
),
),
);
},
),
),
],
);
}),
),
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/views/widgets/avatar.dart';
import 'package:fluffychat/views/widgets/max_width_body.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
@ -45,71 +46,73 @@ class _SettingsIgnoreListState extends State<SettingsIgnoreList> {
leading: BackButton(),
title: Text(L10n.of(context).ignoredUsers),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _controller,
autocorrect: false,
textInputAction: TextInputAction.done,
onSubmitted: (_) => _ignoreUser(context),
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'bad_guy:domain.abc',
prefixText: '@',
labelText: L10n.of(context).ignoreUsername,
suffixIcon: IconButton(
tooltip: L10n.of(context).ignore,
icon: Icon(Icons.done_outlined),
onPressed: () => _ignoreUser(context),
body: MaxWidthBody(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _controller,
autocorrect: false,
textInputAction: TextInputAction.done,
onSubmitted: (_) => _ignoreUser(context),
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'bad_guy:domain.abc',
prefixText: '@',
labelText: L10n.of(context).ignoreUsername,
suffixIcon: IconButton(
tooltip: L10n.of(context).ignore,
icon: Icon(Icons.done_outlined),
onPressed: () => _ignoreUser(context),
),
),
),
),
SizedBox(height: 16),
Text(
L10n.of(context).ignoreListDescription,
style: TextStyle(color: Colors.orange),
),
],
SizedBox(height: 16),
Text(
L10n.of(context).ignoreListDescription,
style: TextStyle(color: Colors.orange),
),
],
),
),
),
Divider(height: 1),
Expanded(
child: StreamBuilder<Object>(
stream: client.onAccountData.stream
.where((a) => a.type == 'm.ignored_user_list'),
builder: (context, snapshot) {
return ListView.builder(
itemCount: client.ignoredUsers.length,
itemBuilder: (c, i) => FutureBuilder<Profile>(
future:
client.getProfileFromUserId(client.ignoredUsers[i]),
builder: (c, s) => ListTile(
leading: Avatar(
s.data?.avatarUrl ?? Uri.parse(''),
s.data?.displayname ?? client.ignoredUsers[i],
),
title:
Text(s.data?.displayname ?? client.ignoredUsers[i]),
trailing: IconButton(
tooltip: L10n.of(context).delete,
icon: Icon(Icons.delete_forever_outlined),
onPressed: () => showFutureLoadingDialog(
context: context,
future: () =>
client.unignoreUser(client.ignoredUsers[i]),
Divider(height: 1),
Expanded(
child: StreamBuilder<Object>(
stream: client.onAccountData.stream
.where((a) => a.type == 'm.ignored_user_list'),
builder: (context, snapshot) {
return ListView.builder(
itemCount: client.ignoredUsers.length,
itemBuilder: (c, i) => FutureBuilder<Profile>(
future:
client.getProfileFromUserId(client.ignoredUsers[i]),
builder: (c, s) => ListTile(
leading: Avatar(
s.data?.avatarUrl ?? Uri.parse(''),
s.data?.displayname ?? client.ignoredUsers[i],
),
title: Text(
s.data?.displayname ?? client.ignoredUsers[i]),
trailing: IconButton(
tooltip: L10n.of(context).delete,
icon: Icon(Icons.delete_forever_outlined),
onPressed: () => showFutureLoadingDialog(
context: context,
future: () =>
client.unignoreUser(client.ignoredUsers[i]),
),
),
),
),
),
);
}),
),
],
);
}),
),
],
),
),
);
}

View File

@ -1,6 +1,7 @@
import 'dart:io';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/views/widgets/max_width_body.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:flutter/foundation.dart';
@ -113,97 +114,101 @@ class SettingsNotifications extends StatelessWidget {
leading: BackButton(),
title: Text(L10n.of(context).notifications),
),
body: StreamBuilder(
stream: Matrix.of(context)
.client
.onAccountData
.stream
.where((event) => event.type == 'm.push_rules'),
builder: (BuildContext context, _) {
return ListView(
children: [
SwitchListTile(
value: !Matrix.of(context).client.allPushNotificationsMuted,
title:
Text(L10n.of(context).notificationsEnabledForThisAccount),
onChanged: (_) => showFutureLoadingDialog(
context: context,
future: () => Matrix.of(context)
.client
.setMuteAllPushNotifications(
!Matrix.of(context).client.allPushNotificationsMuted,
),
),
),
if (!Matrix.of(context).client.allPushNotificationsMuted) ...{
if (!kIsWeb && Platform.isAndroid)
ListTile(
title: Text(L10n.of(context).soundVibrationLedColor),
trailing: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Colors.grey,
child: Icon(Icons.edit_outlined),
),
onTap: () => _openAndroidNotificationSettingsAction(),
body: MaxWidthBody(
withScrolling: true,
child: StreamBuilder(
stream: Matrix.of(context)
.client
.onAccountData
.stream
.where((event) => event.type == 'm.push_rules'),
builder: (BuildContext context, _) {
return Column(
children: [
SwitchListTile(
value: !Matrix.of(context).client.allPushNotificationsMuted,
title: Text(
L10n.of(context).notificationsEnabledForThisAccount),
onChanged: (_) => showFutureLoadingDialog(
context: context,
future: () =>
Matrix.of(context).client.setMuteAllPushNotifications(
!Matrix.of(context)
.client
.allPushNotificationsMuted,
),
),
),
if (!Matrix.of(context).client.allPushNotificationsMuted) ...{
if (!kIsWeb && Platform.isAndroid)
ListTile(
title: Text(L10n.of(context).soundVibrationLedColor),
trailing: CircleAvatar(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
foregroundColor: Colors.grey,
child: Icon(Icons.edit_outlined),
),
onTap: () => _openAndroidNotificationSettingsAction(),
),
Divider(thickness: 1),
ListTile(
title: Text(
L10n.of(context).pushRules,
style: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
),
),
for (var item in items)
SwitchListTile(
value: _getNotificationSetting(context, item) ?? true,
title: Text(item.title(context)),
onChanged: (bool enabled) =>
_setNotificationSetting(context, item, enabled),
),
},
Divider(thickness: 1),
ListTile(
title: Text(
L10n.of(context).pushRules,
L10n.of(context).devices,
style: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
),
),
for (var item in items)
SwitchListTile(
value: _getNotificationSetting(context, item) ?? true,
title: Text(item.title(context)),
onChanged: (bool enabled) =>
_setNotificationSetting(context, item, enabled),
),
},
Divider(thickness: 1),
ListTile(
title: Text(
L10n.of(context).devices,
style: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
),
),
FutureBuilder<List<Pusher>>(
future: Matrix.of(context).client.requestPushers(),
builder: (context, snapshot) {
if (snapshot.hasError) {
Center(
child: Text(
snapshot.error.toLocalizedString(context),
FutureBuilder<List<Pusher>>(
future: Matrix.of(context).client.requestPushers(),
builder: (context, snapshot) {
if (snapshot.hasError) {
Center(
child: Text(
snapshot.error.toLocalizedString(context),
),
);
}
if (!snapshot.hasData) {
Center(child: CircularProgressIndicator());
}
final pushers = snapshot.data;
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: pushers.length,
itemBuilder: (_, i) => ListTile(
title: Text(
'${pushers[i].appDisplayName} - ${pushers[i].appId}'),
subtitle: Text(pushers[i].data.url.toString()),
),
);
}
if (!snapshot.hasData) {
Center(child: CircularProgressIndicator());
}
final pushers = snapshot.data;
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: pushers.length,
itemBuilder: (_, i) => ListTile(
title: Text(
'${pushers[i].appDisplayName} - ${pushers[i].appId}'),
subtitle: Text(pushers[i].data.url.toString()),
),
);
},
),
],
);
}),
},
),
],
);
}),
),
);
}
}

View File

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/views/widgets/max_width_body.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:image_picker/image_picker.dart';
@ -56,100 +57,104 @@ class _SettingsStyleState extends State<SettingsStyle> {
leading: BackButton(),
title: Text(L10n.of(context).changeTheme),
),
body: ListView(
children: [
RadioListTile<AdaptiveThemeMode>(
groupValue: _currentTheme,
value: AdaptiveThemeMode.system,
title: Text(L10n.of(context).systemTheme),
onChanged: (t) => _switchTheme(t, context),
),
RadioListTile<AdaptiveThemeMode>(
groupValue: _currentTheme,
value: AdaptiveThemeMode.light,
title: Text(L10n.of(context).lightTheme),
onChanged: (t) => _switchTheme(t, context),
),
RadioListTile<AdaptiveThemeMode>(
groupValue: _currentTheme,
value: AdaptiveThemeMode.dark,
title: Text(L10n.of(context).darkTheme),
onChanged: (t) => _switchTheme(t, context),
),
Divider(height: 1),
ListTile(
title: Text(
L10n.of(context).wallpaper,
style: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
body: MaxWidthBody(
withScrolling: true,
child: Column(
children: [
RadioListTile<AdaptiveThemeMode>(
groupValue: _currentTheme,
value: AdaptiveThemeMode.system,
title: Text(L10n.of(context).systemTheme),
onChanged: (t) => _switchTheme(t, context),
),
),
if (Matrix.of(context).wallpaper != null)
RadioListTile<AdaptiveThemeMode>(
groupValue: _currentTheme,
value: AdaptiveThemeMode.light,
title: Text(L10n.of(context).lightTheme),
onChanged: (t) => _switchTheme(t, context),
),
RadioListTile<AdaptiveThemeMode>(
groupValue: _currentTheme,
value: AdaptiveThemeMode.dark,
title: Text(L10n.of(context).darkTheme),
onChanged: (t) => _switchTheme(t, context),
),
Divider(height: 1),
ListTile(
title: Image.file(
Matrix.of(context).wallpaper,
height: 38,
fit: BoxFit.cover,
),
trailing: Icon(
Icons.delete_forever_outlined,
color: Colors.red,
),
onTap: () => deleteWallpaperAction(context),
),
Builder(builder: (context) {
return ListTile(
title: Text(L10n.of(context).changeWallpaper),
trailing: Icon(Icons.wallpaper_outlined),
onTap: () => setWallpaperAction(context),
);
}),
Divider(height: 1),
ListTile(
title: Text(
L10n.of(context).fontSize,
style: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
),
subtitle: Text('(*${AppConfig.fontSizeFactor})'),
),
Container(
alignment: Alignment.centerLeft,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 10),
decoration: BoxDecoration(
color: Theme.of(context).secondaryHeaderColor,
borderRadius: BorderRadius.circular(16),
),
child: Text(
'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor',
title: Text(
L10n.of(context).wallpaper,
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyText1.fontSize *
AppConfig.fontSizeFactor,
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
),
),
),
Slider(
min: 0.5,
max: 2.5,
divisions: 4,
value: AppConfig.fontSizeFactor,
semanticFormatterCallback: (d) => d.toString(),
onChanged: (d) {
setState(() => AppConfig.fontSizeFactor = d);
Matrix.of(context).store.setItem(
SettingKeys.fontSizeFactor,
AppConfig.fontSizeFactor.toString(),
);
},
),
],
if (Matrix.of(context).wallpaper != null)
ListTile(
title: Image.file(
Matrix.of(context).wallpaper,
height: 38,
fit: BoxFit.cover,
),
trailing: Icon(
Icons.delete_forever_outlined,
color: Colors.red,
),
onTap: () => deleteWallpaperAction(context),
),
Builder(builder: (context) {
return ListTile(
title: Text(L10n.of(context).changeWallpaper),
trailing: Icon(Icons.wallpaper_outlined),
onTap: () => setWallpaperAction(context),
);
}),
Divider(height: 1),
ListTile(
title: Text(
L10n.of(context).fontSize,
style: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
),
),
subtitle: Text('(*${AppConfig.fontSizeFactor})'),
),
Container(
alignment: Alignment.centerLeft,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding:
const EdgeInsets.symmetric(vertical: 6, horizontal: 10),
decoration: BoxDecoration(
color: Theme.of(context).secondaryHeaderColor,
borderRadius: BorderRadius.circular(16),
),
child: Text(
'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor',
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyText1.fontSize *
AppConfig.fontSizeFactor,
),
),
),
),
Slider(
min: 0.5,
max: 2.5,
divisions: 4,
value: AppConfig.fontSizeFactor,
semanticFormatterCallback: (d) => d.toString(),
onChanged: (d) {
setState(() => AppConfig.fontSizeFactor = d);
Matrix.of(context).store.setItem(
SettingKeys.fontSizeFactor,
AppConfig.fontSizeFactor.toString(),
);
},
),
],
),
),
);
}

View File

@ -0,0 +1,38 @@
import 'dart:math';
import 'package:flutter/material.dart';
class MaxWidthBody extends StatelessWidget {
final Widget child;
final double maxWidth;
final bool withScrolling;
const MaxWidthBody({
this.child,
this.maxWidth = 600,
this.withScrolling = false,
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final padding = EdgeInsets.symmetric(
horizontal: max(0, (constraints.maxWidth - maxWidth) / 2),
);
return withScrolling
? SingleChildScrollView(
physics: ScrollPhysics(),
child: Padding(
padding: padding,
child: child,
),
)
: Padding(
padding: padding,
child: child,
);
},
);
}
}