mirror of
https://gitlab.com/famedly/fluffychat.git
synced 2024-11-24 04:59:26 +01:00
design: Column mode auto padding
This commit is contained in:
parent
0146767e8a
commit
75258271af
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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]),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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]),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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()),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
38
lib/views/widgets/max_width_body.dart
Normal file
38
lib/views/widgets/max_width_body.dart
Normal 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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user