feat: Implement experimental new design

This commit is contained in:
Christian Pauly
2021-02-01 19:28:39 +01:00
parent 2bafcabef4
commit 10cf8daf25
18 changed files with 1232 additions and 698 deletions

View File

@ -9,6 +9,7 @@ class DefaultAppBarSearchField extends StatefulWidget {
final String hintText;
final EdgeInsets padding;
final bool readOnly;
final Widget prefixIcon;
const DefaultAppBarSearchField({
Key key,
@ -20,6 +21,7 @@ class DefaultAppBarSearchField extends StatefulWidget {
this.hintText,
this.padding,
this.readOnly = false,
this.prefixIcon,
}) : super(key: key);
@override
@ -73,12 +75,18 @@ class _DefaultAppBarSearchFieldState extends State<DefaultAppBarSearchField> {
readOnly: widget.readOnly,
decoration: InputDecoration(
prefixText: widget.prefixText,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide:
BorderSide(color: Theme.of(context).secondaryHeaderColor),
),
contentPadding: EdgeInsets.only(
top: 8,
bottom: 8,
left: 16,
),
hintText: widget.hintText,
prefixIcon: widget.prefixIcon,
suffixIcon: !widget.readOnly &&
(_focusNode.hasFocus ||
(widget.suffix == null &&

View File

@ -1,94 +0,0 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'matrix.dart';
class DefaultDrawer extends StatelessWidget {
void _drawerTapAction(BuildContext context, String route) {
Navigator.of(context).pop();
AdaptivePageLayout.of(context).pushNamedAndRemoveUntilIsFirst(route);
}
void _setStatus(BuildContext context) async {
final client = Matrix.of(context).client;
final input = await showTextInputDialog(
title: L10n.of(context).setStatus,
context: context,
textFields: [
DialogTextField(
hintText: L10n.of(context).statusExampleMessage,
)
],
);
if (input == null || input.single.isEmpty) return;
await showFutureLoadingDialog(
context: context,
future: () => client.sendPresence(
client.userID,
PresenceType.online,
statusMsg: input.single,
),
);
Navigator.of(context).pop();
return;
}
@override
Widget build(BuildContext context) {
return Drawer(
child: SafeArea(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
ListTile(
leading: Icon(Icons.edit_outlined),
title: Text(L10n.of(context).setStatus),
onTap: () => _setStatus(context),
),
Divider(height: 1),
ListTile(
leading: Icon(Icons.people_outline),
title: Text(L10n.of(context).createNewGroup),
onTap: () => _drawerTapAction(context, '/newgroup'),
),
ListTile(
leading: Icon(Icons.person_add_outlined),
title: Text(L10n.of(context).newPrivateChat),
onTap: () => _drawerTapAction(context, '/newprivatechat'),
),
Divider(height: 1),
ListTile(
leading: Icon(Icons.archive_outlined),
title: Text(L10n.of(context).archive),
onTap: () => _drawerTapAction(
context,
'/archive',
),
),
ListTile(
leading: Icon(Icons.group_work_outlined),
title: Text(L10n.of(context).discoverGroups),
onTap: () => _drawerTapAction(
context,
'/discover',
),
),
Divider(height: 1),
ListTile(
leading: Icon(Icons.settings_outlined),
title: Text(L10n.of(context).settings),
onTap: () => _drawerTapAction(
context,
'/settings',
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,134 @@
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/components/avatar.dart';
import 'package:fluffychat/utils/fluffy_share.dart';
import 'package:fluffychat/utils/status.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import '../../utils/string_color.dart';
import '../../utils/date_time_extension.dart';
import '../matrix.dart';
class StatusListTile extends StatelessWidget {
final Status status;
const StatusListTile({Key key, @required this.status}) : super(key: key);
@override
Widget build(BuildContext context) {
final text = status.message;
final isImage = text.startsWith('mxc://') && text.split(' ').length == 1;
return FutureBuilder<Profile>(
future: Matrix.of(context).client.getProfileFromUserId(status.senderId),
builder: (context, snapshot) {
final displayname =
snapshot.data?.displayname ?? status.senderId.localpart;
final avatarUrl = snapshot.data?.avatarUrl;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Avatar(avatarUrl, displayname),
title: Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
children: [
Text(displayname,
style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(width: 4),
Text(status.dateTime.localizedTime(context),
style: TextStyle(fontSize: 14)),
],
),
subtitle: Text(status.senderId),
trailing: PopupMenuButton(
onSelected: (_) => AdaptivePageLayout.of(context).pushNamed(
'/settings/ignore',
arguments: status.senderId),
itemBuilder: (_) => [
PopupMenuItem(
child: Text(L10n.of(context).ignore),
value: 'ignore',
),
],
),
),
isImage
? CachedNetworkImage(
imageUrl: Uri.parse(text).getThumbnail(
Matrix.of(context).client,
width: 360,
height: 360,
method: ThumbnailMethod.scale,
),
fit: BoxFit.cover,
width: double.infinity,
)
: Container(
height: 256,
color: text.color,
alignment: Alignment.center,
child: SingleChildScrollView(
padding: EdgeInsets.all(12),
child: Text(
text,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
),
),
Padding(
padding: const EdgeInsets.only(right: 12.0, left: 12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: Icon(CupertinoIcons.chat_bubble),
onPressed: () async {
final result = await showFutureLoadingDialog(
context: context,
future: () => User(
status.senderId,
room:
Room(id: '', client: Matrix.of(context).client),
).startDirectChat(),
);
if (result.error == null) {
await AdaptivePageLayout.of(context)
.pushNamed('/rooms/${result.result}');
}
},
),
IconButton(
icon: Icon(Icons.ios_share),
onPressed: () => AdaptivePageLayout.of(context)
.pushNamed('/newstatus', arguments: status.message),
),
IconButton(
icon: Icon(Icons.share_outlined),
onPressed: () => FluffyShare.share(
'$displayname: ${status.message}',
context,
),
),
IconButton(
icon: Icon(Icons.delete_outlined),
onPressed: () => showFutureLoadingDialog(
context: context,
future: () => Matrix.of(context)
.removeStatusOfUser(status.senderId),
),
),
],
),
),
],
);
});
}
}

View File

@ -10,6 +10,7 @@ import 'package:fluffychat/utils/firebase_controller.dart';
import 'package:fluffychat/utils/matrix_locals.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/utils/sentry_controller.dart';
import 'package:fluffychat/utils/status.dart';
import 'package:flushbar/flushbar.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -126,6 +127,7 @@ class MatrixState extends State<Matrix> {
StreamSubscription onKeyVerificationRequestSub;
StreamSubscription onJitsiCallSub;
StreamSubscription onNotification;
StreamSubscription<Presence> onPresence;
StreamSubscription<LoginState> onLoginStateChanged;
StreamSubscription<UiaRequest> onUiaRequest;
StreamSubscription<html.Event> onFocusSub;
@ -288,6 +290,10 @@ class MatrixState extends State<Matrix> {
LoadingDialog.defaultBackLabel = L10n.of(context).close;
LoadingDialog.defaultOnError = (Object e) => e.toLocalizedString(context);
onPresence ??= client.onPresence.stream
.where((p) => p.presence?.statusMsg != null)
.listen(_onPresence);
onRoomKeyRequestSub ??=
client.onRoomKeyRequest.stream.listen((RoomKeyRequest request) async {
final room = request.room;
@ -395,6 +401,45 @@ class MatrixState extends State<Matrix> {
}
}
Map<String, Status> get statuses {
if (client.accountData.containsKey(Status.namespace)) {
try {
return client.accountData[Status.namespace].content
.map((k, v) => MapEntry(k, Status.fromJson(v)));
} catch (e, s) {
Logs()
.e('Unable to parse status account data. Clearing up now...', e, s);
client.setAccountData(client.userID, Status.namespace, {});
}
}
return {};
}
void _onPresence(Presence presence) async {
if (statuses[presence.senderId]?.message != presence.presence.statusMsg) {
Logs().v('Update status from ${presence.senderId}');
await client.setAccountData(
client.userID,
Status.namespace,
statuses.map((k, v) => MapEntry(k, v.toJson()))
..[presence.senderId] = Status(
presence.senderId,
presence.presence.statusMsg,
DateTime.now(),
),
);
}
}
Future<void> removeStatusOfUser(String userId) async {
await client.setAccountData(
client.userID,
Status.namespace,
statuses.map((k, v) => MapEntry(k, v.toJson()))..remove(userId),
);
return;
}
@override
void dispose() {
onRoomKeyRequestSub?.cancel();
@ -403,6 +448,7 @@ class MatrixState extends State<Matrix> {
onNotification?.cancel();
onFocusSub?.cancel();
onBlurSub?.cancel();
onPresence?.cancel();
super.dispose();
}