Auth factor settings

This commit is contained in:
2025-06-06 01:21:22 +08:00
parent accb99bb24
commit bf77bfce64
20 changed files with 826 additions and 383 deletions

View File

@ -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);

View File

@ -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),

View 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),
],
),
);
}
}

View File

@ -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),
),
);
}