refactor: Implement avatar image viewer and adjust design
Signed-off-by: Krille <c.kussowski@famedly.com>
This commit is contained in:
parent
2873a047f8
commit
3594fa4f6d
12 changed files with 151 additions and 59 deletions
|
|
@ -18,6 +18,7 @@ import '../key_verification/key_verification_dialog.dart';
|
||||||
class BootstrapDialog extends StatefulWidget {
|
class BootstrapDialog extends StatefulWidget {
|
||||||
final bool wipe;
|
final bool wipe;
|
||||||
final Client client;
|
final Client client;
|
||||||
|
|
||||||
const BootstrapDialog({
|
const BootstrapDialog({
|
||||||
super.key,
|
super.key,
|
||||||
this.wipe = false,
|
this.wipe = false,
|
||||||
|
|
@ -132,7 +133,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
minLines: 2,
|
minLines: 2,
|
||||||
maxLines: 4,
|
maxLines: 4,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
style: const TextStyle(fontFamily: 'UbuntuMono'),
|
style: const TextStyle(fontFamily: 'RobotoMono'),
|
||||||
controller: TextEditingController(text: key),
|
controller: TextEditingController(text: key),
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
contentPadding: EdgeInsets.all(16),
|
contentPadding: EdgeInsets.all(16),
|
||||||
|
|
@ -257,7 +258,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
? null
|
? null
|
||||||
: [AutofillHints.password],
|
: [AutofillHints.password],
|
||||||
controller: _recoveryKeyTextEditingController,
|
controller: _recoveryKeyTextEditingController,
|
||||||
style: const TextStyle(fontFamily: 'UbuntuMono'),
|
style: const TextStyle(fontFamily: 'RobotoMono'),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
contentPadding: const EdgeInsets.all(16),
|
contentPadding: const EdgeInsets.all(16),
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
|
|
|
||||||
|
|
@ -295,7 +295,7 @@ class HtmlMessage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
textStyle: TextStyle(
|
textStyle: TextStyle(
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
fontFamily: 'UbuntuMono',
|
fontFamily: 'RobotoMono',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -235,7 +235,7 @@ class InputBar extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
commandExample(command),
|
commandExample(command),
|
||||||
style: const TextStyle(fontFamily: 'UbuntuMono'),
|
style: const TextStyle(fontFamily: 'RobotoMono'),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
hint,
|
hint,
|
||||||
|
|
@ -255,7 +255,7 @@ class InputBar extends StatelessWidget {
|
||||||
waitDuration: const Duration(days: 1), // don't show on hover
|
waitDuration: const Duration(days: 1), // don't show on hover
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
child: Text(label, style: const TextStyle(fontFamily: 'UbuntuMono')),
|
child: Text(label, style: const TextStyle(fontFamily: 'RobotoMono')),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import 'package:fluffychat/widgets/chat_settings_popup_menu.dart';
|
||||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
import 'package:fluffychat/widgets/matrix.dart';
|
||||||
import '../../utils/url_launcher.dart';
|
import '../../utils/url_launcher.dart';
|
||||||
|
import '../../widgets/mxc_image_viewer.dart';
|
||||||
import '../../widgets/qr_code_viewer.dart';
|
import '../../widgets/qr_code_viewer.dart';
|
||||||
|
|
||||||
class ChatDetailsView extends StatelessWidget {
|
class ChatDetailsView extends StatelessWidget {
|
||||||
|
|
@ -38,6 +39,7 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
final directChatMatrixID = room.directChatMatrixID;
|
final directChatMatrixID = room.directChatMatrixID;
|
||||||
|
final roomAvatar = room.avatar;
|
||||||
|
|
||||||
return StreamBuilder(
|
return StreamBuilder(
|
||||||
stream: room.client.onRoomState.stream
|
stream: room.client.onRoomState.stream
|
||||||
|
|
@ -108,6 +110,13 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
mxContent: room.avatar,
|
mxContent: room.avatar,
|
||||||
name: displayname,
|
name: displayname,
|
||||||
size: Avatar.defaultSize * 2.5,
|
size: Avatar.defaultSize * 2.5,
|
||||||
|
onTap: roomAvatar != null
|
||||||
|
? () => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) =>
|
||||||
|
MxcImageViewer(roomAvatar),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!room.isDirectChat &&
|
if (!room.isDirectChat &&
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ class ChatEncryptionSettingsView extends StatelessWidget {
|
||||||
deviceKeys[i].ed25519Key?.beautified ??
|
deviceKeys[i].ed25519Key?.beautified ??
|
||||||
L10n.of(context).unknownEncryptionAlgorithm,
|
L10n.of(context).unknownEncryptionAlgorithm,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: 'UbuntuMono',
|
fontFamily: 'RobotoMono',
|
||||||
color: theme.colorScheme.secondary,
|
color: theme.colorScheme.secondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import 'package:fluffychat/utils/platform_infos.dart';
|
||||||
import 'package:fluffychat/widgets/avatar.dart';
|
import 'package:fluffychat/widgets/avatar.dart';
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
import 'package:fluffychat/widgets/matrix.dart';
|
||||||
import 'package:fluffychat/widgets/navigation_rail.dart';
|
import 'package:fluffychat/widgets/navigation_rail.dart';
|
||||||
|
import '../../widgets/mxc_image_viewer.dart';
|
||||||
import 'settings.dart';
|
import 'settings.dart';
|
||||||
|
|
||||||
class SettingsView extends StatelessWidget {
|
class SettingsView extends StatelessWidget {
|
||||||
|
|
@ -65,6 +66,7 @@ class SettingsView extends StatelessWidget {
|
||||||
future: controller.profileFuture,
|
future: controller.profileFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final profile = snapshot.data;
|
final profile = snapshot.data;
|
||||||
|
final avatar = profile?.avatarUrl;
|
||||||
final mxid = Matrix.of(context).client.userID ??
|
final mxid = Matrix.of(context).client.userID ??
|
||||||
L10n.of(context).user;
|
L10n.of(context).user;
|
||||||
final displayname =
|
final displayname =
|
||||||
|
|
@ -76,9 +78,16 @@ class SettingsView extends StatelessWidget {
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Avatar(
|
Avatar(
|
||||||
mxContent: profile?.avatarUrl,
|
mxContent: avatar,
|
||||||
name: displayname,
|
name: displayname,
|
||||||
size: Avatar.defaultSize * 2.5,
|
size: Avatar.defaultSize * 2.5,
|
||||||
|
onTap: avatar != null
|
||||||
|
? () => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) =>
|
||||||
|
MxcImageViewer(avatar),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
if (profile != null)
|
if (profile != null)
|
||||||
Positioned(
|
Positioned(
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import 'settings_security.dart';
|
||||||
|
|
||||||
class SettingsSecurityView extends StatelessWidget {
|
class SettingsSecurityView extends StatelessWidget {
|
||||||
final SettingsSecurityController controller;
|
final SettingsSecurityController controller;
|
||||||
|
|
||||||
const SettingsSecurityView(this.controller, {super.key});
|
const SettingsSecurityView(this.controller, {super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -143,7 +144,7 @@ class SettingsSecurityView extends StatelessWidget {
|
||||||
leading: const Icon(Icons.vpn_key_outlined),
|
leading: const Icon(Icons.vpn_key_outlined),
|
||||||
subtitle: SelectableText(
|
subtitle: SelectableText(
|
||||||
Matrix.of(context).client.fingerprintKey.beautified,
|
Matrix.of(context).client.fingerprintKey.beautified,
|
||||||
style: const TextStyle(fontFamily: 'UbuntuMono'),
|
style: const TextStyle(fontFamily: 'RobotoMono'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (capabilities?.mChangePassword?.enabled != false ||
|
if (capabilities?.mChangePassword?.enabled != false ||
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ extension StringColor on String {
|
||||||
number += codeUnitAt(i);
|
number += codeUnitAt(i);
|
||||||
}
|
}
|
||||||
number = (number % 12) * 25.5;
|
number = (number % 12) * 25.5;
|
||||||
return HSLColor.fromAHSL(1, number, 1, light).toColor();
|
return HSLColor.fromAHSL(0.75, number, 1, light).toColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
Color get color {
|
Color get color {
|
||||||
|
|
@ -29,6 +29,6 @@ extension StringColor on String {
|
||||||
|
|
||||||
Color get lightColorAvatar {
|
Color get lightColorAvatar {
|
||||||
_colorCache[this] ??= {};
|
_colorCache[this] ??= {};
|
||||||
return _colorCache[this]![0.4] ??= _getColorLight(0.4);
|
return _colorCache[this]![0.45] ??= _getColorLight(0.45);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import '../../utils/url_launcher.dart';
|
||||||
import '../future_loading_dialog.dart';
|
import '../future_loading_dialog.dart';
|
||||||
import '../hover_builder.dart';
|
import '../hover_builder.dart';
|
||||||
import '../matrix.dart';
|
import '../matrix.dart';
|
||||||
|
import '../mxc_image_viewer.dart';
|
||||||
|
|
||||||
class UserDialog extends StatelessWidget {
|
class UserDialog extends StatelessWidget {
|
||||||
static Future<void> show({
|
static Future<void> show({
|
||||||
|
|
@ -45,6 +46,7 @@ class UserDialog extends StatelessWidget {
|
||||||
L10n.of(context).user;
|
L10n.of(context).user;
|
||||||
var copied = false;
|
var copied = false;
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final avatar = profile.avatarUrl;
|
||||||
return AlertDialog.adaptive(
|
return AlertDialog.adaptive(
|
||||||
title: ConstrainedBox(
|
title: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 256),
|
constraints: const BoxConstraints(maxWidth: 256),
|
||||||
|
|
@ -75,7 +77,9 @@ class UserDialog extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
HoverBuilder(
|
HoverBuilder(
|
||||||
builder: (context, hovered) => StatefulBuilder(
|
builder: (context, hovered) => StatefulBuilder(
|
||||||
builder: (context, setState) => GestureDetector(
|
builder: (context, setState) => MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Clipboard.setData(
|
Clipboard.setData(
|
||||||
ClipboardData(text: profile.userId),
|
ClipboardData(text: profile.userId),
|
||||||
|
|
@ -89,9 +93,11 @@ class UserDialog extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
WidgetSpan(
|
WidgetSpan(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(right: 4.0),
|
padding:
|
||||||
|
const EdgeInsets.only(right: 4.0),
|
||||||
child: AnimatedScale(
|
child: AnimatedScale(
|
||||||
duration: FluffyThemes.animationDuration,
|
duration:
|
||||||
|
FluffyThemes.animationDuration,
|
||||||
curve: FluffyThemes.animationCurve,
|
curve: FluffyThemes.animationCurve,
|
||||||
scale: hovered
|
scale: hovered
|
||||||
? 1.33
|
? 1.33
|
||||||
|
|
@ -118,11 +124,18 @@ class UserDialog extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
Center(
|
Center(
|
||||||
child: Avatar(
|
child: Avatar(
|
||||||
mxContent: profile.avatarUrl,
|
mxContent: avatar,
|
||||||
name: displayname,
|
name: displayname,
|
||||||
size: Avatar.defaultSize * 2,
|
size: Avatar.defaultSize * 2,
|
||||||
|
onTap: avatar != null
|
||||||
|
? () => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => MxcImageViewer(avatar),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (presenceText != null)
|
if (presenceText != null)
|
||||||
|
|
|
||||||
|
|
@ -62,18 +62,13 @@ class Avatar extends StatelessWidget {
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
child: noPic
|
child: noPic
|
||||||
? Container(
|
? Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(color: name!.lightColorAvatar),
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [name!.lightColorAvatar, name.color],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Text(
|
child: Text(
|
||||||
fallbackLetters,
|
fallbackLetters,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
fontFamily: 'RobotoMono',
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: (size / 2.5).roundToDouble(),
|
fontSize: (size / 2.5).roundToDouble(),
|
||||||
|
|
@ -143,10 +138,12 @@ class Avatar extends StatelessWidget {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
if (onTap == null) return container;
|
if (onTap == null) return container;
|
||||||
return InkWell(
|
return MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
borderRadius: borderRadius,
|
|
||||||
child: container,
|
child: container,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
60
lib/widgets/mxc_image_viewer.dart
Normal file
60
lib/widgets/mxc_image_viewer.dart
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
|
||||||
|
import 'mxc_image.dart';
|
||||||
|
|
||||||
|
class MxcImageViewer extends StatelessWidget {
|
||||||
|
final Uri mxContent;
|
||||||
|
|
||||||
|
const MxcImageViewer(this.mxContent, {super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final iconButtonStyle = IconButton.styleFrom(
|
||||||
|
backgroundColor: Colors.black.withAlpha(200),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
);
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => Navigator.of(context).pop(),
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: Colors.black.withAlpha(128),
|
||||||
|
extendBodyBehindAppBar: true,
|
||||||
|
appBar: AppBar(
|
||||||
|
elevation: 0,
|
||||||
|
leading: IconButton(
|
||||||
|
style: iconButtonStyle,
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
color: Colors.white,
|
||||||
|
tooltip: L10n.of(context).close,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
body: InteractiveViewer(
|
||||||
|
minScale: 1.0,
|
||||||
|
maxScale: 10.0,
|
||||||
|
onInteractionEnd: (endDetails) {
|
||||||
|
if (endDetails.velocity.pixelsPerSecond.dy >
|
||||||
|
MediaQuery.of(context).size.height * 1.5) {
|
||||||
|
Navigator.of(context, rootNavigator: false).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: GestureDetector(
|
||||||
|
// Ignore taps to not go back here:
|
||||||
|
onTap: () {},
|
||||||
|
child: MxcImage(
|
||||||
|
key: ValueKey(mxContent.toString()),
|
||||||
|
uri: mxContent,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
isThumbnail: false,
|
||||||
|
animated: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
|
||||||
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
||||||
import 'package:fluffychat/widgets/adaptive_dialogs/dialog_text_field.dart';
|
import 'package:fluffychat/widgets/adaptive_dialogs/dialog_text_field.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
||||||
|
|
||||||
Future<int?> showPermissionChooser(
|
Future<int?> showPermissionChooser(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue