✨ Auth factor settings
This commit is contained in:
@ -1,5 +1,9 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/database/message.dart';
|
||||
@ -27,6 +31,7 @@ class MessageItem extends HookConsumerWidget {
|
||||
final Function(String action)? onAction;
|
||||
final Map<int, double>? progress;
|
||||
final bool showAvatar;
|
||||
final Function(String messageId) onJump;
|
||||
|
||||
const MessageItem({
|
||||
super.key,
|
||||
@ -35,6 +40,7 @@ class MessageItem extends HookConsumerWidget {
|
||||
required this.onAction,
|
||||
required this.progress,
|
||||
required this.showAvatar,
|
||||
required this.onJump,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -54,6 +60,8 @@ class MessageItem extends HookConsumerWidget {
|
||||
final remoteMessage = message.toRemoteMessage();
|
||||
final sender = remoteMessage.sender;
|
||||
|
||||
final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS);
|
||||
|
||||
return ContextMenuWidget(
|
||||
menuProvider: (_) {
|
||||
if (onAction == null) return Menu(children: []);
|
||||
@ -90,6 +98,17 @@ class MessageItem extends HookConsumerWidget {
|
||||
onAction!.call(MessageItemAction.forward);
|
||||
},
|
||||
),
|
||||
if (isMobile) MenuSeparator(),
|
||||
if (isMobile)
|
||||
MenuAction(
|
||||
title: 'copyMessage'.tr(),
|
||||
image: MenuImage.icon(Symbols.copy_all),
|
||||
callback: () {
|
||||
Clipboard.setData(
|
||||
ClipboardData(text: remoteMessage.content ?? ''),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
@ -355,39 +374,67 @@ class MessageQuoteWidget extends HookConsumerWidget {
|
||||
if (remoteMessage != null) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 6),
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.primaryFixedDim.withOpacity(0.4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (isReply)
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 4,
|
||||
children: [
|
||||
Icon(Symbols.reply, size: 16, color: textColor),
|
||||
Text(
|
||||
'Replying to ${remoteMessage.sender.account.nick}',
|
||||
).textColor(textColor).bold(),
|
||||
],
|
||||
).padding(right: 8)
|
||||
else
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 4,
|
||||
children: [
|
||||
Icon(Symbols.forward, size: 16, color: textColor),
|
||||
Text(
|
||||
'Forwarded from ${remoteMessage.sender.account.nick}',
|
||||
).textColor(textColor).bold(),
|
||||
],
|
||||
).padding(right: 8),
|
||||
if (_MessageItemContent.hasContent(remoteMessage))
|
||||
_MessageItemContent(item: remoteMessage),
|
||||
],
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
final messageId =
|
||||
isReply
|
||||
? message.toRemoteMessage().repliedMessageId!
|
||||
: message.toRemoteMessage().forwardedMessageId!;
|
||||
// Find the nearest MessageItem ancestor and call its onJump method
|
||||
final MessageItem? ancestor =
|
||||
context.findAncestorWidgetOfExactType<MessageItem>();
|
||||
if (ancestor != null) {
|
||||
ancestor.onJump(messageId);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 6),
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.primaryFixedDim.withOpacity(0.4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (isReply)
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 4,
|
||||
children: [
|
||||
Icon(Symbols.reply, size: 16, color: textColor),
|
||||
Text(
|
||||
'${'repliedTo'.tr()} ${remoteMessage.sender.account.nick}',
|
||||
).textColor(textColor).bold(),
|
||||
],
|
||||
).padding(right: 8)
|
||||
else
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 4,
|
||||
children: [
|
||||
Icon(Symbols.forward, size: 16, color: textColor),
|
||||
Text(
|
||||
'${'forwarded'.tr()} ${remoteMessage.sender.account.nick}',
|
||||
).textColor(textColor).bold(),
|
||||
],
|
||||
).padding(right: 8),
|
||||
if (_MessageItemContent.hasContent(remoteMessage))
|
||||
_MessageItemContent(item: remoteMessage),
|
||||
if (remoteMessage.attachments.isNotEmpty)
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Symbols.attach_file, size: 12, color: textColor),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'hasAttachments'.plural(
|
||||
remoteMessage.attachments.length,
|
||||
),
|
||||
style: TextStyle(color: textColor, fontSize: 12),
|
||||
),
|
||||
],
|
||||
).padding(vertical: 2),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
).padding(bottom: 4);
|
||||
|
@ -20,6 +20,9 @@ String _parseRemoteError(DioException err) {
|
||||
}
|
||||
|
||||
void showErrorAlert(dynamic err) async {
|
||||
if (err is Error) {
|
||||
log('${err.stackTrace}');
|
||||
}
|
||||
final text = switch (err) {
|
||||
String _ => err,
|
||||
DioException _ => _parseRemoteError(err),
|
||||
|
60
lib/widgets/content/sheet.dart
Normal file
60
lib/widgets/content/sheet.dart
Normal file
@ -0,0 +1,60 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
class SheetScaffold extends StatelessWidget {
|
||||
final Widget? title;
|
||||
final String? titleText;
|
||||
final List<Widget> actions;
|
||||
final Widget child;
|
||||
final double heightFactor;
|
||||
const SheetScaffold({
|
||||
super.key,
|
||||
this.title,
|
||||
this.titleText,
|
||||
required this.child,
|
||||
this.actions = const [],
|
||||
this.heightFactor = 0.8,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(title != null || titleText != null);
|
||||
|
||||
var titleWidget =
|
||||
title ??
|
||||
Text(
|
||||
titleText!,
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.5,
|
||||
),
|
||||
);
|
||||
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height * heightFactor,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
titleWidget,
|
||||
const Spacer(),
|
||||
...actions,
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Expanded(child: child),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
class TechicalReviewIntroWidget extends StatelessWidget {
|
||||
@ -8,55 +8,26 @@ class TechicalReviewIntroWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'技术性预览',
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.5,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text('👋').fontSize(32),
|
||||
Text('你好呀~').fontSize(24),
|
||||
Text('欢迎来使用 Solar Network 3.0 的技术性预览版。'),
|
||||
const Gap(24),
|
||||
Text('技术性预览的初衷是让我们更顺滑的将 3.0 发布出来,帮助我们一点一点的迁移数据。'),
|
||||
const Gap(24),
|
||||
Text('同时,既然是测试版,肯定有一系列的 Bug 和问题,请多多包涵,也欢迎积极反馈到 GitHub 上。'),
|
||||
Text('目前帐号数据已经迁移完毕,其他数据将在未来逐渐迁移。还请耐心等待,不要重复创建以免未来数据冲突。'),
|
||||
const Gap(24),
|
||||
Text('最后,感谢你愿意参与技术性预览,祝你使用愉快!'),
|
||||
const Gap(16),
|
||||
Text('关掉这个对话框就开始探索吧!').fontSize(11),
|
||||
],
|
||||
).padding(horizontal: 20, vertical: 24),
|
||||
),
|
||||
),
|
||||
],
|
||||
return SheetScaffold(
|
||||
titleText: '技术性预览',
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text('👋').fontSize(32),
|
||||
Text('你好呀~').fontSize(24),
|
||||
Text('欢迎来使用 Solar Network 3.0 的技术性预览版。'),
|
||||
const Gap(24),
|
||||
Text('技术性预览的初衷是让我们更顺滑的将 3.0 发布出来,帮助我们一点一点的迁移数据。'),
|
||||
const Gap(24),
|
||||
Text('同时,既然是测试版,肯定有一系列的 Bug 和问题,请多多包涵,也欢迎积极反馈到 GitHub 上。'),
|
||||
Text('目前帐号数据已经迁移完毕,其他数据将在未来逐渐迁移。还请耐心等待,不要重复创建以免未来数据冲突。'),
|
||||
const Gap(24),
|
||||
Text('最后,感谢你愿意参与技术性预览,祝你使用愉快!'),
|
||||
const Gap(16),
|
||||
Text('关掉这个对话框就开始探索吧!').fontSize(11),
|
||||
],
|
||||
).padding(horizontal: 20, vertical: 24),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user