🎨 Formatted all the code
This commit is contained in:
@ -83,7 +83,7 @@ class _AccountScreenState extends State<AccountScreen> {
|
||||
title: AppLocalizations.of(context)!.signUp,
|
||||
caption: AppLocalizations.of(context)!.signUpCaption,
|
||||
onTap: () {
|
||||
launchUrl(getRequestUri('passport', '/sign-up'));
|
||||
router.pushNamed('auth.sign-up');
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -131,7 +131,8 @@ class NameCard extends StatelessWidget {
|
||||
children: [
|
||||
FutureBuilder(
|
||||
future: renderAvatar(context),
|
||||
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
|
||||
builder:
|
||||
(BuildContext context, AsyncSnapshot<Widget> snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return snapshot.data!;
|
||||
} else {
|
||||
@ -142,7 +143,8 @@ class NameCard extends StatelessWidget {
|
||||
const SizedBox(width: 20),
|
||||
FutureBuilder(
|
||||
future: renderLabel(context),
|
||||
builder: (BuildContext context, AsyncSnapshot<Column> snapshot) {
|
||||
builder:
|
||||
(BuildContext context, AsyncSnapshot<Column> snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return snapshot.data!;
|
||||
} else {
|
||||
@ -164,7 +166,12 @@ class ActionCard extends StatelessWidget {
|
||||
final String caption;
|
||||
final Function onTap;
|
||||
|
||||
const ActionCard({super.key, required this.onTap, required this.title, required this.caption, required this.icon});
|
||||
const ActionCard(
|
||||
{super.key,
|
||||
required this.onTap,
|
||||
required this.title,
|
||||
required this.caption,
|
||||
required this.icon});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -120,14 +120,16 @@ class _FriendScreenState extends State<FriendScreen> {
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: AppLocalizations.of(context)!.username,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
|
||||
),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
@ -155,7 +157,8 @@ class _FriendScreenState extends State<FriendScreen> {
|
||||
DismissDirection getDismissDirection(Friendship relation) {
|
||||
if (relation.status == 2) return DismissDirection.endToStart;
|
||||
if (relation.status == 1) return DismissDirection.startToEnd;
|
||||
if (relation.status == 0 && relation.relatedId != _selfId) return DismissDirection.startToEnd;
|
||||
if (relation.status == 0 && relation.relatedId != _selfId)
|
||||
return DismissDirection.startToEnd;
|
||||
return DismissDirection.horizontal;
|
||||
}
|
||||
|
||||
@ -220,12 +223,18 @@ class _FriendScreenState extends State<FriendScreen> {
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(),
|
||||
child: _isSubmitting
|
||||
? const LinearProgressIndicator().animate().scaleX()
|
||||
: Container(),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
|
||||
color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.8),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceVariant
|
||||
.withOpacity(0.8),
|
||||
child: Text(AppLocalizations.of(context)!.friendPending),
|
||||
),
|
||||
),
|
||||
@ -235,8 +244,12 @@ class _FriendScreenState extends State<FriendScreen> {
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
|
||||
color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.8),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceVariant
|
||||
.withOpacity(0.8),
|
||||
child: Text(AppLocalizations.of(context)!.friendActive),
|
||||
),
|
||||
),
|
||||
@ -246,8 +259,12 @@ class _FriendScreenState extends State<FriendScreen> {
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
|
||||
color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.8),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceVariant
|
||||
.withOpacity(0.8),
|
||||
child: Text(AppLocalizations.of(context)!.friendBlocked),
|
||||
),
|
||||
),
|
||||
@ -260,7 +277,10 @@ class _FriendScreenState extends State<FriendScreen> {
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.8),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceVariant
|
||||
.withOpacity(0.8),
|
||||
width: 0.3,
|
||||
)),
|
||||
),
|
||||
|
121
lib/screens/auth/signin.dart
Normal file
121
lib/screens/auth/signin.dart
Normal file
@ -0,0 +1,121 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/utils/service_url.dart';
|
||||
import 'package:solian/widgets/exts.dart';
|
||||
import 'package:solian/widgets/indent_wrapper.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class SignInScreen extends StatelessWidget {
|
||||
final _usernameController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
|
||||
SignInScreen({super.key});
|
||||
|
||||
void performSignIn(BuildContext context) {
|
||||
final auth = context.read<AuthProvider>();
|
||||
|
||||
final username = _usernameController.value.text;
|
||||
final password = _passwordController.value.text;
|
||||
if (username.isEmpty || password.isEmpty) return;
|
||||
auth.signin(context, username, password).then((_) {
|
||||
router.pop(true);
|
||||
}).catchError((e) {
|
||||
List<String> messages = e.toString().split('\n');
|
||||
if (messages.last.contains("risk")) {
|
||||
final ticketId = RegExp(r"ticketId=(\d+)").firstMatch(messages.last);
|
||||
if (ticketId == null) {
|
||||
context.showErrorDialog(
|
||||
"requested to multi-factor authenticate, but the ticket id was not found");
|
||||
}
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text(AppLocalizations.of(context)!.riskDetection),
|
||||
content: Text(AppLocalizations.of(context)!.signInRiskDetected),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(AppLocalizations.of(context)!.next),
|
||||
onPressed: () {
|
||||
launchUrlString(
|
||||
getRequestUri(
|
||||
'passport', '/mfa?ticket=${ticketId!.group(1)}')
|
||||
.toString(),
|
||||
);
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(messages.last),
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IndentWrapper(
|
||||
title: AppLocalizations.of(context)!.signIn,
|
||||
hideDrawer: true,
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.6,
|
||||
constraints: const BoxConstraints(maxWidth: 360),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Image.asset('assets/logo.png', width: 72, height: 72),
|
||||
),
|
||||
TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
controller: _usernameController,
|
||||
autofillHints: const [AutofillHints.username],
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: AppLocalizations.of(context)!.username,
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
obscureText: true,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
autofillHints: const [AutofillHints.password],
|
||||
controller: _passwordController,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: AppLocalizations.of(context)!.password,
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onSubmitted: (_) => performSignIn(context),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
child: Text(AppLocalizations.of(context)!.signIn),
|
||||
onPressed: () => performSignIn(context),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
113
lib/screens/auth/signup.dart
Normal file
113
lib/screens/auth/signup.dart
Normal file
@ -0,0 +1,113 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:solian/widgets/indent_wrapper.dart';
|
||||
|
||||
class SignUpScreen extends StatelessWidget {
|
||||
final _emailController = TextEditingController();
|
||||
final _usernameController = TextEditingController();
|
||||
final _nicknameController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
|
||||
SignUpScreen({super.key});
|
||||
|
||||
void performSignIn(BuildContext context) {
|
||||
final auth = context.read<AuthProvider>();
|
||||
|
||||
final email = _emailController.value.text;
|
||||
final username = _usernameController.value.text;
|
||||
final nickname = _passwordController.value.text;
|
||||
final password = _passwordController.value.text;
|
||||
if (email.isEmpty ||
|
||||
username.isEmpty ||
|
||||
nickname.isEmpty ||
|
||||
password.isEmpty) return;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IndentWrapper(
|
||||
title: AppLocalizations.of(context)!.signUp,
|
||||
hideDrawer: true,
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.6,
|
||||
constraints: const BoxConstraints(maxWidth: 360),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Image.asset('assets/logo.png', width: 72, height: 72),
|
||||
),
|
||||
TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
controller: _usernameController,
|
||||
autofillHints: const [AutofillHints.username],
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: AppLocalizations.of(context)!.username,
|
||||
prefixText: '@',
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
controller: _nicknameController,
|
||||
autofillHints: const [AutofillHints.nickname],
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: AppLocalizations.of(context)!.nickname,
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
controller: _emailController,
|
||||
autofillHints: const [AutofillHints.email],
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: AppLocalizations.of(context)!.email,
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
obscureText: true,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
autofillHints: const [AutofillHints.password],
|
||||
controller: _passwordController,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: AppLocalizations.of(context)!.password,
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onSubmitted: (_) => performSignIn(context),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
child: Text(AppLocalizations.of(context)!.signUp),
|
||||
onPressed: () => performSignIn(context),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -92,14 +92,16 @@ class _ChatCallState extends State<ChatCall> {
|
||||
itemCount: math.max(0, _call.participantTracks.length),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final track = _call.participantTracks[index];
|
||||
if (track.participant.sid == _call.focusTrack?.participant.sid) {
|
||||
if (track.participant.sid ==
|
||||
_call.focusTrack?.participant.sid) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8, left: 8),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(8)),
|
||||
child: InteractiveParticipantWidget(
|
||||
isFixed: true,
|
||||
width: 120,
|
||||
@ -107,7 +109,8 @@ class _ChatCallState extends State<ChatCall> {
|
||||
color: Theme.of(context).cardColor,
|
||||
participant: track,
|
||||
onTap: () {
|
||||
if (track.participant.sid != _call.focusTrack?.participant.sid) {
|
||||
if (track.participant.sid !=
|
||||
_call.focusTrack?.participant.sid) {
|
||||
_call.changeFocusTrack(track);
|
||||
}
|
||||
},
|
||||
|
@ -113,10 +113,13 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
|
||||
constraints: const BoxConstraints(maxWidth: 640),
|
||||
child: Column(
|
||||
children: [
|
||||
_isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(),
|
||||
_isSubmitting
|
||||
? const LinearProgressIndicator().animate().scaleX()
|
||||
: Container(),
|
||||
ListTile(
|
||||
title: Text(AppLocalizations.of(context)!.chatChannelUsage),
|
||||
subtitle: Text(AppLocalizations.of(context)!.chatChannelUsageCaption),
|
||||
subtitle:
|
||||
Text(AppLocalizations.of(context)!.chatChannelUsageCaption),
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.teal,
|
||||
child: Icon(Icons.tag, color: Colors.white),
|
||||
@ -124,7 +127,8 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
|
||||
),
|
||||
const Divider(thickness: 0.3),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@ -132,15 +136,18 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
|
||||
autofocus: true,
|
||||
controller: _aliasController,
|
||||
decoration: InputDecoration.collapsed(
|
||||
hintText: AppLocalizations.of(context)!.chatChannelAliasLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.chatChannelAliasLabel,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
shape: const CircleBorder(),
|
||||
visualDensity: const VisualDensity(horizontal: -2, vertical: -2),
|
||||
visualDensity:
|
||||
const VisualDensity(horizontal: -2, vertical: -2),
|
||||
),
|
||||
onPressed: () => randomizeAlias(),
|
||||
child: const Icon(Icons.refresh),
|
||||
@ -150,20 +157,24 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
|
||||
),
|
||||
const Divider(thickness: 0.3),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: TextField(
|
||||
autocorrect: true,
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration.collapsed(
|
||||
hintText: AppLocalizations.of(context)!.chatChannelNameLabel,
|
||||
hintText:
|
||||
AppLocalizations.of(context)!.chatChannelNameLabel,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
),
|
||||
const Divider(thickness: 0.3),
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: TextField(
|
||||
minLines: 5,
|
||||
maxLines: null,
|
||||
@ -171,9 +182,11 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
|
||||
keyboardType: TextInputType.multiline,
|
||||
controller: _descriptionController,
|
||||
decoration: InputDecoration.collapsed(
|
||||
hintText: AppLocalizations.of(context)!.chatChannelDescriptionLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.chatChannelDescriptionLabel,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -36,7 +36,8 @@ class _ChatMemberScreenState extends State<ChatMemberScreen> {
|
||||
|
||||
_selfId = prof['id'];
|
||||
|
||||
var uri = getRequestUri('messaging', '/api/channels/${widget.channel.alias}/members');
|
||||
var uri = getRequestUri(
|
||||
'messaging', '/api/channels/${widget.channel.alias}/members');
|
||||
|
||||
var res = await auth.client!.get(uri);
|
||||
if (res.statusCode == 200) {
|
||||
@ -59,7 +60,8 @@ class _ChatMemberScreenState extends State<ChatMemberScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
var uri = getRequestUri('messaging', '/api/channels/${widget.channel.alias}/kick');
|
||||
var uri = getRequestUri(
|
||||
'messaging', '/api/channels/${widget.channel.alias}/kick');
|
||||
|
||||
var res = await auth.client!.post(
|
||||
uri,
|
||||
@ -89,7 +91,8 @@ class _ChatMemberScreenState extends State<ChatMemberScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
var uri = getRequestUri('messaging', '/api/channels/${widget.channel.alias}/invite');
|
||||
var uri = getRequestUri(
|
||||
'messaging', '/api/channels/${widget.channel.alias}/invite');
|
||||
|
||||
var res = await auth.client!.post(
|
||||
uri,
|
||||
@ -153,7 +156,9 @@ class _ChatMemberScreenState extends State<ChatMemberScreen> {
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(),
|
||||
child: _isSubmitting
|
||||
? const LinearProgressIndicator().animate().scaleX()
|
||||
: Container(),
|
||||
),
|
||||
SliverList.builder(
|
||||
itemCount: _members.length,
|
||||
@ -164,7 +169,9 @@ class _ChatMemberScreenState extends State<ChatMemberScreen> {
|
||||
|
||||
return Dismissible(
|
||||
key: Key(randomId.toString()),
|
||||
direction: getKickable(element) ? DismissDirection.startToEnd : DismissDirection.none,
|
||||
direction: getKickable(element)
|
||||
? DismissDirection.startToEnd
|
||||
: DismissDirection.none,
|
||||
background: Container(
|
||||
color: Colors.red,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
@ -172,7 +179,8 @@ class _ChatMemberScreenState extends State<ChatMemberScreen> {
|
||||
child: const Icon(Icons.remove, color: Colors.white),
|
||||
),
|
||||
child: ListTile(
|
||||
leading: AccountAvatar(source: element.account.avatar, direct: true),
|
||||
leading: AccountAvatar(
|
||||
source: element.account.avatar, direct: true),
|
||||
title: Text(element.account.nick),
|
||||
subtitle: Text(element.account.name),
|
||||
),
|
||||
|
@ -34,7 +34,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
Call? _ongoingCall;
|
||||
Channel? _channelMeta;
|
||||
|
||||
final PagingController<int, Message> _pagingController = PagingController(firstPageKey: 0);
|
||||
final PagingController<int, Message> _pagingController =
|
||||
PagingController(firstPageKey: 0);
|
||||
|
||||
final http.Client _client = http.Client();
|
||||
|
||||
@ -53,7 +54,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
}
|
||||
|
||||
Future<Call?> fetchCall() async {
|
||||
var uri = getRequestUri('messaging', '/api/channels/${widget.alias}/calls/ongoing');
|
||||
var uri = getRequestUri(
|
||||
'messaging', '/api/channels/${widget.alias}/calls/ongoing');
|
||||
var res = await _client.get(uri);
|
||||
if (res.statusCode == 200) {
|
||||
final result = jsonDecode(utf8.decode(res.bodyBytes));
|
||||
@ -82,8 +84,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
|
||||
var res = await auth.client!.get(uri);
|
||||
if (res.statusCode == 200) {
|
||||
final result = PaginationResult.fromJson(jsonDecode(utf8.decode(res.bodyBytes)));
|
||||
final items = result.data?.map((x) => Message.fromJson(x)).toList() ?? List.empty();
|
||||
final result =
|
||||
PaginationResult.fromJson(jsonDecode(utf8.decode(res.bodyBytes)));
|
||||
final items =
|
||||
result.data?.map((x) => Message.fromJson(x)).toList() ?? List.empty();
|
||||
final isLastPage = (result.count - pageKey) < take;
|
||||
if (isLastPage || result.data == null) {
|
||||
_pagingController.appendLastPage(items);
|
||||
@ -97,7 +101,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
}
|
||||
|
||||
bool getMessageMergeable(Message? a, Message? b) {
|
||||
if (a?.replyTo != null || b?.replyTo != null) return false;
|
||||
if (a?.replyTo != null) return false;
|
||||
if (a == null || b == null) return false;
|
||||
if (a.senderId != b.senderId) return false;
|
||||
return a.createdAt.difference(b.createdAt).inMinutes <= 5;
|
||||
@ -111,13 +115,16 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
|
||||
void updateMessage(Message item) {
|
||||
setState(() {
|
||||
_pagingController.itemList = _pagingController.itemList?.map((x) => x.id == item.id ? item : x).toList();
|
||||
_pagingController.itemList = _pagingController.itemList
|
||||
?.map((x) => x.id == item.id ? item : x)
|
||||
.toList();
|
||||
});
|
||||
}
|
||||
|
||||
void deleteMessage(Message item) {
|
||||
setState(() {
|
||||
_pagingController.itemList = _pagingController.itemList?.where((x) => x.id != item.id).toList();
|
||||
_pagingController.itemList =
|
||||
_pagingController.itemList?.where((x) => x.id != item.id).toList();
|
||||
});
|
||||
}
|
||||
|
||||
@ -147,7 +154,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
fetchCall();
|
||||
});
|
||||
|
||||
_pagingController.addPageRequestListener((pageKey) => fetchMessages(pageKey, context));
|
||||
_pagingController
|
||||
.addPageRequestListener((pageKey) => fetchMessages(pageKey, context));
|
||||
|
||||
super.initState();
|
||||
}
|
||||
@ -157,10 +165,12 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
Widget chatHistoryBuilder(context, item, index) {
|
||||
bool isMerged = false, hasMerged = false;
|
||||
if (index > 0) {
|
||||
hasMerged = getMessageMergeable(_pagingController.itemList?[index - 1], item);
|
||||
hasMerged =
|
||||
getMessageMergeable(_pagingController.itemList?[index - 1], item);
|
||||
}
|
||||
if (index + 1 < (_pagingController.itemList?.length ?? 0)) {
|
||||
isMerged = getMessageMergeable(item, _pagingController.itemList?[index + 1]);
|
||||
isMerged =
|
||||
getMessageMergeable(item, _pagingController.itemList?[index + 1]);
|
||||
}
|
||||
return InkWell(
|
||||
child: Container(
|
||||
@ -183,7 +193,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
final callBanner = MaterialBanner(
|
||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20),
|
||||
leading: const Icon(Icons.call_received),
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
|
||||
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
||||
content: Text(AppLocalizations.of(context)!.chatCallOngoing),
|
||||
actions: [
|
||||
@ -205,8 +216,12 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
title: _channelMeta?.name ?? "Loading...",
|
||||
appBarActions: _channelMeta != null
|
||||
? [
|
||||
ChannelCallAction(call: _ongoingCall, channel: _channelMeta!, onUpdate: () => fetchMetadata()),
|
||||
ChannelManageAction(channel: _channelMeta!, onUpdate: () => fetchMetadata()),
|
||||
ChannelCallAction(
|
||||
call: _ongoingCall,
|
||||
channel: _channelMeta!,
|
||||
onUpdate: () => fetchMetadata()),
|
||||
ChannelManageAction(
|
||||
channel: _channelMeta!, onUpdate: () => fetchMetadata()),
|
||||
]
|
||||
: [],
|
||||
child: FutureBuilder(
|
||||
@ -243,7 +258,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
_ongoingCall != null ? callBanner.animate().slideY() : Container(),
|
||||
_ongoingCall != null
|
||||
? callBanner.animate().slideY()
|
||||
: Container(),
|
||||
],
|
||||
),
|
||||
onInsertMessage: (message) => addMessage(message),
|
||||
|
@ -105,7 +105,7 @@ class _ChatIndexScreenState extends State<ChatIndexScreen> {
|
||||
'channel': element.alias,
|
||||
},
|
||||
);
|
||||
switch(result) {
|
||||
switch (result) {
|
||||
case 'refresh':
|
||||
fetchChannels();
|
||||
}
|
||||
|
@ -53,7 +53,9 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
||||
leading: const Icon(Icons.settings),
|
||||
title: Text(AppLocalizations.of(context)!.settings),
|
||||
onTap: () async {
|
||||
router.pushNamed('chat.channel.editor', extra: widget.channel).then((did) {
|
||||
router
|
||||
.pushNamed('chat.channel.editor', extra: widget.channel)
|
||||
.then((did) {
|
||||
if (did == true) {
|
||||
if (router.canPop()) router.pop('refresh');
|
||||
}
|
||||
@ -79,10 +81,14 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(widget.channel.name, style: Theme.of(context).textTheme.bodyLarge),
|
||||
Text(widget.channel.description, style: Theme.of(context).textTheme.bodySmall),
|
||||
]),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(widget.channel.name,
|
||||
style: Theme.of(context).textTheme.bodyLarge),
|
||||
Text(widget.channel.description,
|
||||
style: Theme.of(context).textTheme.bodySmall),
|
||||
]),
|
||||
)
|
||||
],
|
||||
),
|
||||
@ -110,8 +116,12 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
||||
...(_isOwned ? authorizedItems : List.empty()),
|
||||
const Divider(thickness: 0.3),
|
||||
ListTile(
|
||||
leading: _isOwned ? const Icon(Icons.delete) : const Icon(Icons.exit_to_app),
|
||||
title: Text(_isOwned ? AppLocalizations.of(context)!.delete : AppLocalizations.of(context)!.exit),
|
||||
leading: _isOwned
|
||||
? const Icon(Icons.delete)
|
||||
: const Icon(Icons.exit_to_app),
|
||||
title: Text(_isOwned
|
||||
? AppLocalizations.of(context)!.delete
|
||||
: AppLocalizations.of(context)!.exit),
|
||||
onTap: () => promptLeaveChannel(),
|
||||
),
|
||||
],
|
||||
|
@ -22,7 +22,8 @@ class ExploreScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ExploreScreenState extends State<ExploreScreen> {
|
||||
final PagingController<int, Post> _pagingController = PagingController(firstPageKey: 0);
|
||||
final PagingController<int, Post> _pagingController =
|
||||
PagingController(firstPageKey: 0);
|
||||
|
||||
final http.Client _client = http.Client();
|
||||
|
||||
@ -30,12 +31,15 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
final offset = pageKey;
|
||||
const take = 5;
|
||||
|
||||
var uri = getRequestUri('interactive', '/api/feed?take=$take&offset=$offset');
|
||||
var uri =
|
||||
getRequestUri('interactive', '/api/feed?take=$take&offset=$offset');
|
||||
|
||||
var res = await _client.get(uri);
|
||||
if (res.statusCode == 200) {
|
||||
final result = PaginationResult.fromJson(jsonDecode(utf8.decode(res.bodyBytes)));
|
||||
final items = result.data?.map((x) => Post.fromJson(x)).toList() ?? List.empty();
|
||||
final result =
|
||||
PaginationResult.fromJson(jsonDecode(utf8.decode(res.bodyBytes)));
|
||||
final items =
|
||||
result.data?.map((x) => Post.fromJson(x)).toList() ?? List.empty();
|
||||
final isLastPage = (result.count - pageKey) < take;
|
||||
if (isLastPage || result.data == null) {
|
||||
_pagingController.appendLastPage(items);
|
||||
|
@ -41,7 +41,8 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.check),
|
||||
title: Text(AppLocalizations.of(context)!.notifyDone),
|
||||
subtitle: Text(AppLocalizations.of(context)!.notifyDoneCaption),
|
||||
subtitle: Text(
|
||||
AppLocalizations.of(context)!.notifyDoneCaption),
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -78,7 +79,8 @@ class NotificationItem extends StatelessWidget {
|
||||
final model.Notification item;
|
||||
final void Function()? onDismiss;
|
||||
|
||||
const NotificationItem({super.key, required this.index, required this.item, this.onDismiss});
|
||||
const NotificationItem(
|
||||
{super.key, required this.index, required this.item, this.onDismiss});
|
||||
|
||||
bool hasLinks() => item.links != null && item.links!.isNotEmpty;
|
||||
|
||||
@ -92,7 +94,8 @@ class NotificationItem extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, top: 34, bottom: 12),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16, right: 16, top: 34, bottom: 12),
|
||||
child: Text(
|
||||
"Links",
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
@ -121,7 +124,8 @@ class NotificationItem extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> markAsRead(model.Notification element, BuildContext context) async {
|
||||
Future<void> markAsRead(
|
||||
model.Notification element, BuildContext context) async {
|
||||
if (element.isRealtime) return;
|
||||
|
||||
final auth = context.read<AuthProvider>();
|
||||
|
@ -129,69 +129,68 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
|
||||
child: Text(AppLocalizations.of(context)!.postVerb.toUpperCase()),
|
||||
),
|
||||
],
|
||||
child: Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 640),
|
||||
child: Column(
|
||||
children: [
|
||||
_isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(),
|
||||
FutureBuilder(
|
||||
future: auth.getProfiles(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var userinfo = snapshot.data;
|
||||
return ListTile(
|
||||
title: Text(userinfo["nick"]),
|
||||
subtitle: Text(
|
||||
AppLocalizations.of(context)!.postIdentityNotify,
|
||||
),
|
||||
leading: AccountAvatar(
|
||||
source: userinfo["picture"],
|
||||
direct: true,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
),
|
||||
const Divider(thickness: 0.3),
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: TextField(
|
||||
maxLines: null,
|
||||
autofocus: true,
|
||||
autocorrect: true,
|
||||
keyboardType: TextInputType.multiline,
|
||||
controller: _textController,
|
||||
decoration: InputDecoration.collapsed(
|
||||
hintText: AppLocalizations.of(context)!.postContentPlaceholder,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
child: Column(
|
||||
children: [
|
||||
_isSubmitting
|
||||
? const LinearProgressIndicator().animate().scaleX()
|
||||
: Container(),
|
||||
FutureBuilder(
|
||||
future: auth.getProfiles(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var userinfo = snapshot.data;
|
||||
return ListTile(
|
||||
title: Text(userinfo["nick"]),
|
||||
subtitle: Text(
|
||||
AppLocalizations.of(context)!.postIdentityNotify,
|
||||
),
|
||||
),
|
||||
),
|
||||
widget.editing != null ? editingBanner : Container(),
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(width: 0.3, color: Color(0xffdedede)),
|
||||
leading: AccountAvatar(
|
||||
source: userinfo["picture"],
|
||||
direct: true,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(shape: const CircleBorder()),
|
||||
child: const Icon(Icons.camera_alt),
|
||||
onPressed: () => viewAttachments(context),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const Divider(thickness: 0.3),
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: TextField(
|
||||
maxLines: null,
|
||||
autofocus: true,
|
||||
autocorrect: true,
|
||||
keyboardType: TextInputType.multiline,
|
||||
controller: _textController,
|
||||
decoration: InputDecoration.collapsed(
|
||||
hintText:
|
||||
AppLocalizations.of(context)!.postContentPlaceholder,
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
),
|
||||
),
|
||||
widget.editing != null ? editingBanner : Container(),
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(width: 0.3, color: Color(0xffdedede)),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(shape: const CircleBorder()),
|
||||
child: const Icon(Icons.camera_alt),
|
||||
onPressed: () => viewAttachments(context),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -119,69 +119,68 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
|
||||
child: Text(AppLocalizations.of(context)!.postVerb.toUpperCase()),
|
||||
),
|
||||
],
|
||||
child: Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 640),
|
||||
child: Column(
|
||||
children: [
|
||||
_isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(),
|
||||
FutureBuilder(
|
||||
future: auth.getProfiles(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var userinfo = snapshot.data;
|
||||
return ListTile(
|
||||
title: Text(userinfo["nick"]),
|
||||
subtitle: Text(
|
||||
AppLocalizations.of(context)!.postIdentityNotify,
|
||||
),
|
||||
leading: AccountAvatar(
|
||||
source: userinfo["picture"],
|
||||
direct: true,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
),
|
||||
const Divider(thickness: 0.3),
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: TextField(
|
||||
maxLines: null,
|
||||
autofocus: true,
|
||||
autocorrect: true,
|
||||
keyboardType: TextInputType.multiline,
|
||||
controller: _textController,
|
||||
decoration: InputDecoration.collapsed(
|
||||
hintText: AppLocalizations.of(context)!.postContentPlaceholder,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
child: Column(
|
||||
children: [
|
||||
_isSubmitting
|
||||
? const LinearProgressIndicator().animate().scaleX()
|
||||
: Container(),
|
||||
FutureBuilder(
|
||||
future: auth.getProfiles(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var userinfo = snapshot.data;
|
||||
return ListTile(
|
||||
title: Text(userinfo["nick"]),
|
||||
subtitle: Text(
|
||||
AppLocalizations.of(context)!.postIdentityNotify,
|
||||
),
|
||||
),
|
||||
),
|
||||
widget.editing != null ? editingBanner : Container(),
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(width: 0.3, color: Color(0xffdedede)),
|
||||
leading: AccountAvatar(
|
||||
source: userinfo["picture"],
|
||||
direct: true,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(shape: const CircleBorder()),
|
||||
child: const Icon(Icons.camera_alt),
|
||||
onPressed: () => viewAttachments(context),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const Divider(thickness: 0.3),
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: TextField(
|
||||
maxLines: null,
|
||||
autofocus: true,
|
||||
autocorrect: true,
|
||||
keyboardType: TextInputType.multiline,
|
||||
controller: _textController,
|
||||
decoration: InputDecoration.collapsed(
|
||||
hintText:
|
||||
AppLocalizations.of(context)!.postContentPlaceholder,
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
),
|
||||
),
|
||||
widget.editing != null ? editingBanner : Container(),
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(width: 0.3, color: Color(0xffdedede)),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(shape: const CircleBorder()),
|
||||
child: const Icon(Icons.camera_alt),
|
||||
onPressed: () => viewAttachments(context),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -24,10 +24,12 @@ class PostScreen extends StatefulWidget {
|
||||
class _PostScreenState extends State<PostScreen> {
|
||||
final _client = http.Client();
|
||||
|
||||
final PagingController<int, Post> _commentPagingController = PagingController(firstPageKey: 0);
|
||||
final PagingController<int, Post> _commentPagingController =
|
||||
PagingController(firstPageKey: 0);
|
||||
|
||||
Future<Post?> fetchPost(BuildContext context) async {
|
||||
final uri = getRequestUri('interactive', '/api/p/${widget.dataset}/${widget.alias}');
|
||||
final uri = getRequestUri(
|
||||
'interactive', '/api/p/${widget.dataset}/${widget.alias}');
|
||||
final res = await _client.get(uri);
|
||||
if (res.statusCode != 200) {
|
||||
final err = utf8.decode(res.bodyBytes);
|
||||
|
@ -1,108 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/utils/service_url.dart';
|
||||
import 'package:solian/widgets/exts.dart';
|
||||
import 'package:solian/widgets/indent_wrapper.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class SignInScreen extends StatelessWidget {
|
||||
final _usernameController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
|
||||
SignInScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final auth = context.read<AuthProvider>();
|
||||
|
||||
return IndentWrapper(
|
||||
title: AppLocalizations.of(context)!.signIn,
|
||||
hideDrawer: true,
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.6,
|
||||
constraints: const BoxConstraints(maxWidth: 360),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
controller: _usernameController,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
border: const UnderlineInputBorder(),
|
||||
labelText: AppLocalizations.of(context)!.username,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
obscureText: true,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
controller: _passwordController,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
border: const UnderlineInputBorder(),
|
||||
labelText: AppLocalizations.of(context)!.password,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextButton(
|
||||
child: Text(AppLocalizations.of(context)!.next),
|
||||
onPressed: () {
|
||||
final username = _usernameController.value.text;
|
||||
final password = _passwordController.value.text;
|
||||
if (username.isEmpty || password.isEmpty) return;
|
||||
auth.signin(context, username, password).then((_) {
|
||||
router.pop(true);
|
||||
}).catchError((e) {
|
||||
List<String> messages = e.toString().split('\n');
|
||||
if (messages.last.contains("risk")) {
|
||||
final ticketId = RegExp(r"ticketId=(\d+)").firstMatch(messages.last);
|
||||
if (ticketId == null) {
|
||||
context
|
||||
.showErrorDialog("requested to multi-factor authenticate, but the ticket id was not found");
|
||||
}
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text(AppLocalizations.of(context)!.riskDetection),
|
||||
content: Text(AppLocalizations.of(context)!.signInRiskDetected),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(AppLocalizations.of(context)!.next),
|
||||
onPressed: () {
|
||||
launchUrlString(
|
||||
getRequestUri('passport', '/mfa?ticket=${ticketId!.group(1)}').toString(),
|
||||
);
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(messages.last),
|
||||
));
|
||||
}
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user