🎨 Formatted all the code

This commit is contained in:
2024-05-01 17:37:34 +08:00
parent 28c0094837
commit cd5cfedb2f
51 changed files with 938 additions and 540 deletions

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -105,7 +105,7 @@ class _ChatIndexScreenState extends State<ChatIndexScreen> {
'channel': element.alias,
},
);
switch(result) {
switch (result) {
case 'refresh':
fetchChannels();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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