diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d29021..45241af 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -21,8 +21,9 @@ linter: # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + lines_longer_than_80_chars: false + avoid_print: false # Uncomment to disable the `avoid_print` rule + prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/lib/i18n/app_en.arb b/lib/i18n/app_en.arb index 82290b3..e567df7 100644 --- a/lib/i18n/app_en.arb +++ b/lib/i18n/app_en.arb @@ -16,6 +16,8 @@ "confirmation": "Confirmation", "confirmCancel": "Not sure", "confirmOkay": "OK", + "email": "Email Address", + "nickname": "Nickname", "username": "Username", "password": "Password", "next": "Next", diff --git a/lib/i18n/app_zh.arb b/lib/i18n/app_zh.arb index 68a60f7..baa7b66 100644 --- a/lib/i18n/app_zh.arb +++ b/lib/i18n/app_zh.arb @@ -16,6 +16,8 @@ "confirmation": "确认", "confirmCancel": "不太确定", "confirmOkay": "确定", + "email": "邮箱地址", + "nickname": "显示名", "username": "用户名", "password": "密码", "next": "下一步", diff --git a/lib/models/call.dart b/lib/models/call.dart index 92169e3..74f85d8 100644 --- a/lib/models/call.dart +++ b/lib/models/call.dart @@ -25,28 +25,29 @@ class Call { }); factory Call.fromJson(Map json) => Call( - id: json["id"], - createdAt: DateTime.parse(json["created_at"]), - updatedAt: DateTime.parse(json["updated_at"]), - deletedAt: json["deleted_at"], - endedAt: json["ended_at"] != null ? DateTime.parse(json["ended_at"]) : null, - externalId: json["external_id"], - founderId: json["founder_id"], - channelId: json["channel_id"], - channel: Channel.fromJson(json["channel"]), - ); + id: json["id"], + createdAt: DateTime.parse(json["created_at"]), + updatedAt: DateTime.parse(json["updated_at"]), + deletedAt: json["deleted_at"], + endedAt: + json["ended_at"] != null ? DateTime.parse(json["ended_at"]) : null, + externalId: json["external_id"], + founderId: json["founder_id"], + channelId: json["channel_id"], + channel: Channel.fromJson(json["channel"]), + ); Map toJson() => { - "id": id, - "created_at": createdAt.toIso8601String(), - "updated_at": updatedAt.toIso8601String(), - "deleted_at": deletedAt, - "ended_at": endedAt?.toIso8601String(), - "external_id": externalId, - "founder_id": founderId, - "channel_id": channelId, - "channel": channel.toJson(), - }; + "id": id, + "created_at": createdAt.toIso8601String(), + "updated_at": updatedAt.toIso8601String(), + "deleted_at": deletedAt, + "ended_at": endedAt?.toIso8601String(), + "external_id": externalId, + "founder_id": founderId, + "channel_id": channelId, + "channel": channel.toJson(), + }; } enum ParticipantStatsType { @@ -60,9 +61,9 @@ enum ParticipantStatsType { class ParticipantTrack { ParticipantTrack( {required this.participant, - required this.videoTrack, - required this.isScreenShare}); + required this.videoTrack, + required this.isScreenShare}); VideoTrack? videoTrack; Participant participant; bool isScreenShare; -} \ No newline at end of file +} diff --git a/lib/models/channel.dart b/lib/models/channel.dart index 72621f6..4b295d4 100644 --- a/lib/models/channel.dart +++ b/lib/models/channel.dart @@ -32,36 +32,36 @@ class Channel { }); factory Channel.fromJson(Map json) => Channel( - id: json["id"], - createdAt: DateTime.parse(json["created_at"]), - updatedAt: DateTime.parse(json["updated_at"]), - deletedAt: json["deleted_at"], - alias: json["alias"], - name: json["name"], - description: json["description"], - members: json["members"], - calls: json["calls"], - type: json["type"], - account: Account.fromJson(json["account"]), - accountId: json["account_id"], - realmId: json["realm_id"], - ); + id: json["id"], + createdAt: DateTime.parse(json["created_at"]), + updatedAt: DateTime.parse(json["updated_at"]), + deletedAt: json["deleted_at"], + alias: json["alias"], + name: json["name"], + description: json["description"], + members: json["members"], + calls: json["calls"], + type: json["type"], + account: Account.fromJson(json["account"]), + accountId: json["account_id"], + realmId: json["realm_id"], + ); Map toJson() => { - "id": id, - "created_at": createdAt.toIso8601String(), - "updated_at": updatedAt.toIso8601String(), - "deleted_at": deletedAt, - "alias": alias, - "name": name, - "description": description, - "members": members, - "calls": calls, - "type": type, - "account": account, - "account_id": accountId, - "realm_id": realmId, - }; + "id": id, + "created_at": createdAt.toIso8601String(), + "updated_at": updatedAt.toIso8601String(), + "deleted_at": deletedAt, + "alias": alias, + "name": name, + "description": description, + "members": members, + "calls": calls, + "type": type, + "account": account, + "account_id": accountId, + "realm_id": realmId, + }; } class ChannelMember { @@ -86,24 +86,24 @@ class ChannelMember { }); factory ChannelMember.fromJson(Map json) => ChannelMember( - id: json["id"], - createdAt: DateTime.parse(json["created_at"]), - updatedAt: DateTime.parse(json["updated_at"]), - deletedAt: json["deleted_at"], - channelId: json["channel_id"], - accountId: json["account_id"], - account: Account.fromJson(json["account"]), - notify: json["notify"], - ); + id: json["id"], + createdAt: DateTime.parse(json["created_at"]), + updatedAt: DateTime.parse(json["updated_at"]), + deletedAt: json["deleted_at"], + channelId: json["channel_id"], + accountId: json["account_id"], + account: Account.fromJson(json["account"]), + notify: json["notify"], + ); Map toJson() => { - "id": id, - "created_at": createdAt.toIso8601String(), - "updated_at": updatedAt.toIso8601String(), - "deleted_at": deletedAt, - "channel_id": channelId, - "account_id": accountId, - "account": account.toJson(), - "notify": notify, - }; -} \ No newline at end of file + "id": id, + "created_at": createdAt.toIso8601String(), + "updated_at": updatedAt.toIso8601String(), + "deleted_at": deletedAt, + "channel_id": channelId, + "account_id": accountId, + "account": account.toJson(), + "notify": notify, + }; +} diff --git a/lib/models/friendship.dart b/lib/models/friendship.dart index c42d418..442e638 100644 --- a/lib/models/friendship.dart +++ b/lib/models/friendship.dart @@ -26,30 +26,30 @@ class Friendship { }); factory Friendship.fromJson(Map json) => Friendship( - id: json["id"], - createdAt: DateTime.parse(json["created_at"]), - updatedAt: DateTime.parse(json["updated_at"]), - deletedAt: json["deleted_at"], - accountId: json["account_id"], - relatedId: json["related_id"], - blockedBy: json["blocked_by"], - account: Account.fromJson(json["account"]), - related: Account.fromJson(json["related"]), - status: json["status"], - ); + id: json["id"], + createdAt: DateTime.parse(json["created_at"]), + updatedAt: DateTime.parse(json["updated_at"]), + deletedAt: json["deleted_at"], + accountId: json["account_id"], + relatedId: json["related_id"], + blockedBy: json["blocked_by"], + account: Account.fromJson(json["account"]), + related: Account.fromJson(json["related"]), + status: json["status"], + ); Map toJson() => { - "id": id, - "created_at": createdAt.toIso8601String(), - "updated_at": updatedAt.toIso8601String(), - "deleted_at": deletedAt, - "account_id": accountId, - "related_id": relatedId, - "blocked_by": blockedBy, - "account": account.toJson(), - "related": related.toJson(), - "status": status, - }; + "id": id, + "created_at": createdAt.toIso8601String(), + "updated_at": updatedAt.toIso8601String(), + "deleted_at": deletedAt, + "account_id": accountId, + "related_id": relatedId, + "blocked_by": blockedBy, + "account": account.toJson(), + "related": related.toJson(), + "status": status, + }; Account getOtherside(int selfId) { if (accountId != selfId) { @@ -58,4 +58,4 @@ class Friendship { return related; } } -} \ No newline at end of file +} diff --git a/lib/models/message.dart b/lib/models/message.dart index a4614fe..af99f7c 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -43,11 +43,15 @@ class Message { content: json["content"], metadata: json["metadata"], type: json["type"], - attachments: List.from(json["attachments"]?.map((x) => Attachment.fromJson(x)) ?? List.empty()), + attachments: List.from( + json["attachments"]?.map((x) => Attachment.fromJson(x)) ?? + List.empty()), channel: Channel.fromJson(json["channel"]), sender: Sender.fromJson(json["sender"]), replyId: json["reply_id"], - replyTo: json["reply_to"] != null ? Message.fromJson(json["reply_to"]) : null, + replyTo: json["reply_to"] != null + ? Message.fromJson(json["reply_to"]) + : null, channelId: json["channel_id"], senderId: json["sender_id"], ); @@ -60,7 +64,8 @@ class Message { "content": content, "metadata": metadata, "type": type, - "attachments": List.from(attachments?.map((x) => x.toJson()) ?? List.empty()), + "attachments": List.from( + attachments?.map((x) => x.toJson()) ?? List.empty()), "channel": channel?.toJson(), "sender": sender.toJson(), "reply_id": replyId, diff --git a/lib/models/notification.dart b/lib/models/notification.dart index dd80529..0f61674 100755 --- a/lib/models/notification.dart +++ b/lib/models/notification.dart @@ -34,7 +34,9 @@ class Notification { deletedAt: json["deleted_at"], subject: json["subject"], content: json["content"], - links: json["links"] != null ? List.from(json["links"].map((x) => Link.fromJson(x))) : List.empty(), + links: json["links"] != null + ? List.from(json["links"].map((x) => Link.fromJson(x))) + : List.empty(), isImportant: json["is_important"], isRealtime: json["is_realtime"], readAt: json["read_at"], @@ -49,7 +51,9 @@ class Notification { "deleted_at": deletedAt, "subject": subject, "content": content, - "links": links != null ? List.from(links!.map((x) => x.toJson())) : List.empty(), + "links": links != null + ? List.from(links!.map((x) => x.toJson())) + : List.empty(), "is_important": isImportant, "is_realtime": isRealtime, "read_at": readAt, diff --git a/lib/models/packet.dart b/lib/models/packet.dart index d0511cb..3e701b6 100644 --- a/lib/models/packet.dart +++ b/lib/models/packet.dart @@ -10,14 +10,14 @@ class NetworkPackage { }); factory NetworkPackage.fromJson(Map json) => NetworkPackage( - method: json["w"], - message: json["m"], - payload: json["p"], - ); + method: json["w"], + message: json["m"], + payload: json["p"], + ); Map toJson() => { - "w": method, - "m": message, - "p": payload, - }; -} \ No newline at end of file + "w": method, + "m": message, + "p": payload, + }; +} diff --git a/lib/providers/auth.dart b/lib/providers/auth.dart index 40b8ab3..0ed08a7 100755 --- a/lib/providers/auth.dart +++ b/lib/providers/auth.dart @@ -8,7 +8,8 @@ import 'package:solian/utils/service_url.dart'; class AuthProvider extends ChangeNotifier { AuthProvider(); - final deviceEndpoint = getRequestUri('passport', '/api/notifications/subscribe'); + final deviceEndpoint = + getRequestUri('passport', '/api/notifications/subscribe'); final tokenEndpoint = getRequestUri('passport', '/api/auth/token'); final userinfoEndpoint = getRequestUri('passport', '/api/users/me'); final redirectUrl = Uri.parse('solian://auth'); @@ -29,8 +30,10 @@ class AuthProvider extends ChangeNotifier { Future loadClient() async { if (await storage.containsKey(key: storageKey)) { try { - final credentials = oauth2.Credentials.fromJson((await storage.read(key: storageKey))!); - client = oauth2.Client(credentials, identifier: clientId, secret: clientSecret); + final credentials = + oauth2.Credentials.fromJson((await storage.read(key: storageKey))!); + client = oauth2.Client(credentials, + identifier: clientId, secret: clientSecret); await fetchProfiles(); return true; } catch (e) { @@ -42,7 +45,8 @@ class AuthProvider extends ChangeNotifier { } } - Future createClient(BuildContext context, String username, String password) async { + Future createClient( + BuildContext context, String username, String password) async { if (await loadClient()) { return client!; } @@ -68,15 +72,17 @@ class AuthProvider extends ChangeNotifier { Future refreshToken() async { if (client != null) { - final credentials = - await client!.credentials.refresh(identifier: clientId, secret: clientSecret, basicAuth: false); - client = oauth2.Client(credentials, identifier: clientId, secret: clientSecret); + final credentials = await client!.credentials.refresh( + identifier: clientId, secret: clientSecret, basicAuth: false); + client = oauth2.Client(credentials, + identifier: clientId, secret: clientSecret); storage.write(key: storageKey, value: credentials.toJson()); } notifyListeners(); } - Future signin(BuildContext context, String username, String password) async { + Future signin( + BuildContext context, String username, String password) async { client = await createClient(context, username, password); storage.write(key: storageKey, value: client!.credentials.toJson()); @@ -94,7 +100,10 @@ class AuthProvider extends ChangeNotifier { if (client == null) { await loadClient(); } - if (lastRefreshedAt == null || DateTime.now().subtract(const Duration(minutes: 3)).isAfter(lastRefreshedAt!)) { + if (lastRefreshedAt == null || + DateTime.now() + .subtract(const Duration(minutes: 3)) + .isAfter(lastRefreshedAt!)) { await refreshToken(); lastRefreshedAt = DateTime.now(); } diff --git a/lib/providers/chat.dart b/lib/providers/chat.dart index 917fe83..12e999f 100644 --- a/lib/providers/chat.dart +++ b/lib/providers/chat.dart @@ -32,7 +32,9 @@ class ChatProvider extends ChangeNotifier { scheme: ori.scheme.replaceFirst('http', 'ws'), host: ori.host, path: ori.path, - queryParameters: {'tk': Uri.encodeComponent(auth.client!.credentials.accessToken)}, + queryParameters: { + 'tk': Uri.encodeComponent(auth.client!.credentials.accessToken) + }, ); final channel = WebSocketChannel.connect(uri); @@ -41,7 +43,8 @@ class ChatProvider extends ChangeNotifier { return channel; } - bool handleCall(Call call, Channel channel, {Function? onUpdate, Function? onDispose}) { + bool handleCall(Call call, Channel channel, + {Function? onUpdate, Function? onDispose}) { if (this.call != null) return false; this.call = ChatCallInstance( @@ -106,7 +109,8 @@ class ChatCallInstance { }); void init() { - subscription = Hardware.instance.onDeviceChange.stream.listen(revertDevices); + subscription = + Hardware.instance.onDeviceChange.stream.listen(revertDevices); room = Room(); listener = room.createListener(); Hardware.instance.enumerateDevices().then(revertDevices); @@ -114,7 +118,8 @@ class ChatCallInstance { } Future checkPermissions() async { - if (lkPlatformIs(PlatformType.macOS) || lkPlatformIs(PlatformType.linux)) return; + if (lkPlatformIs(PlatformType.macOS) || lkPlatformIs(PlatformType.linux)) + return; await Permission.camera.request(); await Permission.microphone.request(); @@ -131,7 +136,8 @@ class ChatCallInstance { throw Exception("unauthorized"); } - var uri = getRequestUri('messaging', '/api/channels/${channel.alias}/calls/ongoing/token'); + var uri = getRequestUri( + 'messaging', '/api/channels/${channel.alias}/calls/ongoing/token'); var res = await auth.client!.post(uri); if (res.statusCode == 200) { @@ -184,10 +190,12 @@ class ChatCallInstance { useiOSBroadcastExtension: true, params: VideoParameters( dimensions: VideoDimensionsPresets.h1080_169, - encoding: VideoEncoding(maxBitrate: 3 * 1000 * 1000, maxFramerate: 30), + encoding: + VideoEncoding(maxBitrate: 3 * 1000 * 1000, maxFramerate: 30), ), ), - defaultCameraCaptureOptions: CameraCaptureOptions(maxFrameRate: 30, params: videoParameters), + defaultCameraCaptureOptions: + CameraCaptureOptions(maxFrameRate: 30, params: videoParameters), ), fastConnectOptions: FastConnectOptions( microphone: TrackOption(track: audioTrack), @@ -220,7 +228,8 @@ class ChatCallInstance { room.addListener(onRoomDidUpdate); setupRoomListeners(context); sortParticipants(); - WidgetsBindingCompatible.instance?.addPostFrameCallback((_) => autoPublish(context)); + WidgetsBindingCompatible.instance + ?.addPostFrameCallback((_) => autoPublish(context)); if (lkPlatformIsMobile()) { Hardware.instance.setSpeakerphoneOn(true); @@ -295,7 +304,8 @@ class ChatCallInstance { } // First joined people first - return a.participant.joinedAt.millisecondsSinceEpoch - b.participant.joinedAt.millisecondsSinceEpoch; + return a.participant.joinedAt.millisecondsSinceEpoch - + b.participant.joinedAt.millisecondsSinceEpoch; }); ParticipantTrack localTrack = ParticipantTrack( @@ -304,7 +314,8 @@ class ChatCallInstance { isScreenShare: false, ); if (room.localParticipant != null) { - final localParticipantTracks = room.localParticipant?.videoTrackPublications; + final localParticipantTracks = + room.localParticipant?.videoTrackPublications; if (localParticipantTracks != null) { for (var t in localParticipantTracks) { localTrack.videoTrack = t.track; @@ -317,7 +328,8 @@ class ChatCallInstance { if (focusTrack == null) { focusTrack = participantTracks.first; } else { - final idx = participantTracks.indexWhere((x) => focusTrack!.participant.sid == x.participant.sid); + final idx = participantTracks + .indexWhere((x) => focusTrack!.participant.sid == x.participant.sid); focusTrack = participantTracks[idx]; } diff --git a/lib/providers/friend.dart b/lib/providers/friend.dart index cdfa59f..4cc0479 100644 --- a/lib/providers/friend.dart +++ b/lib/providers/friend.dart @@ -16,11 +16,11 @@ class FriendProvider extends ChangeNotifier { var res = await auth.client!.get(uri); if (res.statusCode == 200) { final result = jsonDecode(utf8.decode(res.bodyBytes)) as List; - friends = result.map((x) => Friendship.fromJson(x)).toList(); - notifyListeners(); + friends = result.map((x) => Friendship.fromJson(x)).toList(); + notifyListeners(); } else { var message = utf8.decode(res.bodyBytes); throw Exception(message); } } -} \ No newline at end of file +} diff --git a/lib/providers/notify.dart b/lib/providers/notify.dart index b9e549e..382ee0f 100644 --- a/lib/providers/notify.dart +++ b/lib/providers/notify.dart @@ -17,7 +17,8 @@ class NotifyProvider extends ChangeNotifier { List notifications = List.empty(growable: true); - final FlutterLocalNotificationsPlugin localNotify = FlutterLocalNotificationsPlugin(); + final FlutterLocalNotificationsPlugin localNotify = + FlutterLocalNotificationsPlugin(); NotifyProvider() { initNotify(); @@ -31,8 +32,10 @@ class NotifyProvider extends ChangeNotifier { DarwinNotificationCategory("general"), ], ); - const linuxSettings = LinuxInitializationSettings(defaultActionName: 'Open notification'); - const InitializationSettings initializationSettings = InitializationSettings( + const linuxSettings = + LinuxInitializationSettings(defaultActionName: 'Open notification'); + const InitializationSettings initializationSettings = + InitializationSettings( android: androidSettings, iOS: darwinSettings, macOS: darwinSettings, @@ -43,7 +46,8 @@ class NotifyProvider extends ChangeNotifier { } Future requestPermissions() async { - if (lkPlatformIs(PlatformType.macOS) || lkPlatformIs(PlatformType.linux)) return; + if (lkPlatformIs(PlatformType.macOS) || lkPlatformIs(PlatformType.linux)) + return; await Permission.notification.request(); } @@ -53,8 +57,11 @@ class NotifyProvider extends ChangeNotifier { var uri = getRequestUri('passport', '/api/notifications?skip=0&take=25'); var res = await auth.client!.get(uri); if (res.statusCode == 200) { - final result = PaginationResult.fromJson(jsonDecode(utf8.decode(res.bodyBytes))); - notifications = result.data?.map((x) => model.Notification.fromJson(x)).toList() ?? List.empty(growable: true); + final result = + PaginationResult.fromJson(jsonDecode(utf8.decode(res.bodyBytes))); + notifications = + result.data?.map((x) => model.Notification.fromJson(x)).toList() ?? + List.empty(growable: true); } notifyListeners(); @@ -71,7 +78,9 @@ class NotifyProvider extends ChangeNotifier { scheme: ori.scheme.replaceFirst('http', 'ws'), host: ori.host, path: ori.path, - queryParameters: {'tk': Uri.encodeComponent(auth.client!.credentials.accessToken)}, + queryParameters: { + 'tk': Uri.encodeComponent(auth.client!.credentials.accessToken) + }, ); final channel = WebSocketChannel.connect(uri); diff --git a/lib/router.dart b/lib/router.dart index 39d1f90..05c0ab4 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -4,6 +4,7 @@ import 'package:solian/models/channel.dart'; import 'package:solian/models/post.dart'; import 'package:solian/screens/account.dart'; import 'package:solian/screens/account/friend.dart'; +import 'package:solian/screens/auth/signup.dart'; import 'package:solian/screens/chat/call.dart'; import 'package:solian/screens/chat/chat.dart'; import 'package:solian/screens/chat/index.dart'; @@ -15,7 +16,7 @@ import 'package:solian/screens/notification.dart'; import 'package:solian/screens/posts/comment_editor.dart'; import 'package:solian/screens/posts/moment_editor.dart'; import 'package:solian/screens/posts/screen.dart'; -import 'package:solian/screens/signin.dart'; +import 'package:solian/screens/auth/signin.dart'; final router = GoRouter( routes: [ @@ -37,12 +38,14 @@ final router = GoRouter( GoRoute( path: '/chat/create', name: 'chat.channel.editor', - builder: (context, state) => ChannelEditorScreen(editing: state.extra as Channel?), + builder: (context, state) => + ChannelEditorScreen(editing: state.extra as Channel?), ), GoRoute( path: '/chat/c/:channel', name: 'chat.channel', - builder: (context, state) => ChatScreen(alias: state.pathParameters['channel'] as String), + builder: (context, state) => + ChatScreen(alias: state.pathParameters['channel'] as String), ), GoRoute( path: '/chat/c/:channel/call', @@ -52,12 +55,14 @@ final router = GoRouter( GoRoute( path: '/chat/c/:channel/manage', name: 'chat.channel.manage', - builder: (context, state) => ChatManageScreen(channel: state.extra as Channel), + builder: (context, state) => + ChatManageScreen(channel: state.extra as Channel), ), GoRoute( path: '/chat/c/:channel/member', name: 'chat.channel.member', - builder: (context, state) => ChatMemberScreen(channel: state.extra as Channel), + builder: (context, state) => + ChatMemberScreen(channel: state.extra as Channel), ), GoRoute( path: '/account', @@ -67,14 +72,16 @@ final router = GoRouter( GoRoute( path: '/posts/publish/moments', name: 'posts.moments.editor', - builder: (context, state) => MomentEditorScreen(editing: state.extra as Post?), + builder: (context, state) => + MomentEditorScreen(editing: state.extra as Post?), ), GoRoute( path: '/posts/publish/comments', name: 'posts.comments.editor', builder: (context, state) { final args = state.extra as CommentPostArguments; - return CommentEditorScreen(editing: args.editing, related: args.related); + return CommentEditorScreen( + editing: args.editing, related: args.related); }, ), GoRoute( @@ -90,6 +97,11 @@ final router = GoRouter( name: 'auth.sign-in', builder: (context, state) => SignInScreen(), ), + GoRoute( + path: '/auth/sign-up', + name: 'auth.sign-up', + builder: (context, state) => SignUpScreen(), + ), GoRoute( path: '/account/friend', name: 'account.friend', diff --git a/lib/screens/account.dart b/lib/screens/account.dart index 39bca4b..d4d52ca 100644 --- a/lib/screens/account.dart +++ b/lib/screens/account.dart @@ -83,7 +83,7 @@ class _AccountScreenState extends State { 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 snapshot) { + builder: + (BuildContext context, AsyncSnapshot 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 snapshot) { + builder: + (BuildContext context, AsyncSnapshot 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) { diff --git a/lib/screens/account/friend.dart b/lib/screens/account/friend.dart index 23b1f7d..21cbf70 100644 --- a/lib/screens/account/friend.dart +++ b/lib/screens/account/friend.dart @@ -120,14 +120,16 @@ class _FriendScreenState extends State { border: const OutlineInputBorder(), labelText: AppLocalizations.of(context)!.username, ), - onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), ), ], ), actions: [ 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 { 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 { 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 { ), 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 { ), 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 { 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, )), ), diff --git a/lib/screens/auth/signin.dart b/lib/screens/auth/signin.dart new file mode 100644 index 0000000..256d0a3 --- /dev/null +++ b/lib/screens/auth/signin.dart @@ -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(); + + 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 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), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/auth/signup.dart b/lib/screens/auth/signup.dart new file mode 100644 index 0000000..b8197e8 --- /dev/null +++ b/lib/screens/auth/signup.dart @@ -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(); + + 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), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/chat/call.dart b/lib/screens/chat/call.dart index c6e9092..bb167a9 100644 --- a/lib/screens/chat/call.dart +++ b/lib/screens/chat/call.dart @@ -92,14 +92,16 @@ class _ChatCallState extends State { 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 { 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); } }, diff --git a/lib/screens/chat/channel/editor.dart b/lib/screens/chat/channel/editor.dart index 307b114..353a574 100644 --- a/lib/screens/chat/channel/editor.dart +++ b/lib/screens/chat/channel/editor.dart @@ -113,10 +113,13 @@ class _ChannelEditorScreenState extends State { 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 { ), 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 { 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 { ), 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 { 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(), ), ), ), diff --git a/lib/screens/chat/channel/member.dart b/lib/screens/chat/channel/member.dart index f3d5d2d..86f2b5e 100644 --- a/lib/screens/chat/channel/member.dart +++ b/lib/screens/chat/channel/member.dart @@ -36,7 +36,8 @@ class _ChatMemberScreenState extends State { _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 { 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 { 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 { 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 { 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 { 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), ), diff --git a/lib/screens/chat/chat.dart b/lib/screens/chat/chat.dart index 6747d07..1665cf9 100644 --- a/lib/screens/chat/chat.dart +++ b/lib/screens/chat/chat.dart @@ -34,7 +34,8 @@ class _ChatScreenState extends State { Call? _ongoingCall; Channel? _channelMeta; - final PagingController _pagingController = PagingController(firstPageKey: 0); + final PagingController _pagingController = + PagingController(firstPageKey: 0); final http.Client _client = http.Client(); @@ -53,7 +54,8 @@ class _ChatScreenState extends State { } Future 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 { 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 { } 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 { 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 { fetchCall(); }); - _pagingController.addPageRequestListener((pageKey) => fetchMessages(pageKey, context)); + _pagingController + .addPageRequestListener((pageKey) => fetchMessages(pageKey, context)); super.initState(); } @@ -157,10 +165,12 @@ class _ChatScreenState extends State { 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 { 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 { 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 { ), ], ), - _ongoingCall != null ? callBanner.animate().slideY() : Container(), + _ongoingCall != null + ? callBanner.animate().slideY() + : Container(), ], ), onInsertMessage: (message) => addMessage(message), diff --git a/lib/screens/chat/index.dart b/lib/screens/chat/index.dart index 8c56a37..c6b507f 100644 --- a/lib/screens/chat/index.dart +++ b/lib/screens/chat/index.dart @@ -105,7 +105,7 @@ class _ChatIndexScreenState extends State { 'channel': element.alias, }, ); - switch(result) { + switch (result) { case 'refresh': fetchChannels(); } diff --git a/lib/screens/chat/manage.dart b/lib/screens/chat/manage.dart index 3abff55..6c5e4b3 100644 --- a/lib/screens/chat/manage.dart +++ b/lib/screens/chat/manage.dart @@ -53,7 +53,9 @@ class _ChatManageScreenState extends State { 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 { ), 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 { ...(_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(), ), ], diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index be6256f..daaefe5 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -22,7 +22,8 @@ class ExploreScreen extends StatefulWidget { } class _ExploreScreenState extends State { - final PagingController _pagingController = PagingController(firstPageKey: 0); + final PagingController _pagingController = + PagingController(firstPageKey: 0); final http.Client _client = http.Client(); @@ -30,12 +31,15 @@ class _ExploreScreenState extends State { 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); diff --git a/lib/screens/notification.dart b/lib/screens/notification.dart index 6e6269c..415f866 100644 --- a/lib/screens/notification.dart +++ b/lib/screens/notification.dart @@ -41,7 +41,8 @@ class _NotificationScreenState extends State { 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 markAsRead(model.Notification element, BuildContext context) async { + Future markAsRead( + model.Notification element, BuildContext context) async { if (element.isRealtime) return; final auth = context.read(); diff --git a/lib/screens/posts/comment_editor.dart b/lib/screens/posts/comment_editor.dart index 00468b0..6a292ca 100644 --- a/lib/screens/posts/comment_editor.dart +++ b/lib/screens/posts/comment_editor.dart @@ -129,69 +129,68 @@ class _CommentEditorScreenState extends State { 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), + ) + ], + ), + ), + ], ), ); } diff --git a/lib/screens/posts/moment_editor.dart b/lib/screens/posts/moment_editor.dart index 0c603fd..52cdc48 100644 --- a/lib/screens/posts/moment_editor.dart +++ b/lib/screens/posts/moment_editor.dart @@ -119,69 +119,68 @@ class _MomentEditorScreenState extends State { 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), + ) + ], + ), + ), + ], ), ); } diff --git a/lib/screens/posts/screen.dart b/lib/screens/posts/screen.dart index 58b256f..0bd1b75 100644 --- a/lib/screens/posts/screen.dart +++ b/lib/screens/posts/screen.dart @@ -24,10 +24,12 @@ class PostScreen extends StatefulWidget { class _PostScreenState extends State { final _client = http.Client(); - final PagingController _commentPagingController = PagingController(firstPageKey: 0); + final PagingController _commentPagingController = + PagingController(firstPageKey: 0); Future 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); diff --git a/lib/screens/signin.dart b/lib/screens/signin.dart deleted file mode 100644 index 033fdc6..0000000 --- a/lib/screens/signin.dart +++ /dev/null @@ -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(); - - 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 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), - )); - } - }); - }, - ) - ], - ), - ), - ), - ); - } -} diff --git a/lib/utils/video_player.dart b/lib/utils/video_player.dart index f2134cd..bf3ea34 100644 --- a/lib/utils/video_player.dart +++ b/lib/utils/video_player.dart @@ -4,4 +4,4 @@ import 'package:media_kit/media_kit.dart'; void initVideo() { WidgetsFlutterBinding.ensureInitialized(); MediaKit.ensureInitialized(); -} \ No newline at end of file +} diff --git a/lib/widgets/account/friend_picker.dart b/lib/widgets/account/friend_picker.dart index 5deda28..e959c92 100644 --- a/lib/widgets/account/friend_picker.dart +++ b/lib/widgets/account/friend_picker.dart @@ -37,7 +37,8 @@ class _FriendPickerState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: const EdgeInsets.only(left: 16, right: 16, top: 32, bottom: 12), + padding: + const EdgeInsets.only(left: 16, right: 16, top: 32, bottom: 12), child: Text( AppLocalizations.of(context)!.friend, style: Theme.of(context).textTheme.headlineSmall, diff --git a/lib/widgets/chat/call/controls.dart b/lib/widgets/chat/call/controls.dart index 2a37654..017c91b 100644 --- a/lib/widgets/chat/call/controls.dart +++ b/lib/widgets/chat/call/controls.dart @@ -41,7 +41,8 @@ class _ControlsWidgetState extends State { void initState() { super.initState(); participant.addListener(onChange); - _subscription = Hardware.instance.onDeviceChange.stream.listen((List devices) { + _subscription = Hardware.instance.onDeviceChange.stream + .listen((List devices) { revertDevices(devices); }); Hardware.instance.enumerateDevices().then(revertDevices); @@ -161,18 +162,23 @@ class _ControlsWidgetState extends State { if (!isRetry) { const androidConfig = FlutterBackgroundAndroidConfig( notificationTitle: 'Screen Sharing', - notificationText: 'A Solar Messager\'s Call is sharing your screen', + notificationText: + 'A Solar Messager\'s Call is sharing your screen', notificationImportance: AndroidNotificationImportance.Default, - notificationIcon: AndroidResource(name: 'launcher_icon', defType: 'mipmap'), + notificationIcon: + AndroidResource(name: 'launcher_icon', defType: 'mipmap'), ); - hasPermissions = await FlutterBackground.initialize(androidConfig: androidConfig); + hasPermissions = await FlutterBackground.initialize( + androidConfig: androidConfig); } - if (hasPermissions && !FlutterBackground.isBackgroundExecutionEnabled) { + if (hasPermissions && + !FlutterBackground.isBackgroundExecutionEnabled) { await FlutterBackground.enableBackgroundExecution(); } } catch (e) { if (!isRetry) { - return await Future.delayed(const Duration(seconds: 1), () => requestBackgroundPermission(true)); + return await Future.delayed(const Duration(seconds: 1), + () => requestBackgroundPermission(true)); } } } @@ -223,7 +229,8 @@ class _ControlsWidgetState extends State { runSpacing: 5, children: [ IconButton( - icon: Transform.flip(flipX: true, child: const Icon(Icons.exit_to_app)), + icon: Transform.flip( + flipX: true, child: const Icon(Icons.exit_to_app)), color: Theme.of(context).colorScheme.onSurface, onPressed: disconnect, ), @@ -253,7 +260,8 @@ class _ControlsWidgetState extends State { return PopupMenuItem( value: device, child: ListTile( - leading: (device.deviceId == widget.room.selectedAudioInputDeviceId) + leading: (device.deviceId == + widget.room.selectedAudioInputDeviceId) ? const Icon(Icons.check_box_outlined) : const Icon(Icons.check_box_outline_blank), title: Text(device.label), @@ -281,7 +289,8 @@ class _ControlsWidgetState extends State { onTap: disableVideo, child: ListTile( leading: const Icon(Icons.videocam_off), - title: Text(AppLocalizations.of(context)!.chatCallVideoOff), + title: + Text(AppLocalizations.of(context)!.chatCallVideoOff), ), ), if (_videoInputs != null) @@ -289,7 +298,8 @@ class _ControlsWidgetState extends State { return PopupMenuItem( value: device, child: ListTile( - leading: (device.deviceId == widget.room.selectedVideoInputDeviceId) + leading: (device.deviceId == + widget.room.selectedVideoInputDeviceId) ? const Icon(Icons.check_box_outlined) : const Icon(Icons.check_box_outline_blank), title: Text(device.label), @@ -308,7 +318,9 @@ class _ControlsWidgetState extends State { tooltip: AppLocalizations.of(context)!.chatCallVideoOn, ), IconButton( - icon: Icon(position == CameraPosition.back ? Icons.video_camera_back : Icons.video_camera_front), + icon: Icon(position == CameraPosition.back + ? Icons.video_camera_back + : Icons.video_camera_front), color: Theme.of(context).colorScheme.onSurface, onPressed: () => toggleCamera(), tooltip: AppLocalizations.of(context)!.chatCallVideoFlip, @@ -330,7 +342,8 @@ class _ControlsWidgetState extends State { return PopupMenuItem( value: device, child: ListTile( - leading: (device.deviceId == widget.room.selectedAudioOutputDeviceId) + leading: (device.deviceId == + widget.room.selectedAudioOutputDeviceId) ? const Icon(Icons.check_box_outlined) : const Icon(Icons.check_box_outline_blank), title: Text(device.label), @@ -343,9 +356,12 @@ class _ControlsWidgetState extends State { ), if (!kIsWeb && lkPlatformIs(PlatformType.iOS)) IconButton( - onPressed: Hardware.instance.canSwitchSpeakerphone ? setSpeakerphoneOn : null, + onPressed: Hardware.instance.canSwitchSpeakerphone + ? setSpeakerphoneOn + : null, color: Theme.of(context).colorScheme.onSurface, - icon: Icon(_speakerphoneOn ? Icons.speaker_phone : Icons.phone_android), + icon: Icon( + _speakerphoneOn ? Icons.speaker_phone : Icons.phone_android), tooltip: AppLocalizations.of(context)!.chatCallChangeSpeaker, ), if (participant.isScreenShareEnabled()) diff --git a/lib/widgets/chat/call/no_content.dart b/lib/widgets/chat/call/no_content.dart index 040fe59..80caf82 100644 --- a/lib/widgets/chat/call/no_content.dart +++ b/lib/widgets/chat/call/no_content.dart @@ -20,7 +20,8 @@ class NoContentWidget extends StatefulWidget { State createState() => _NoContentWidgetState(); } -class _NoContentWidgetState extends State with SingleTickerProviderStateMixin { +class _NoContentWidgetState extends State + with SingleTickerProviderStateMixin { late final AnimationController _animationController; @override @@ -35,7 +36,9 @@ class _NoContentWidgetState extends State with SingleTickerProv if (widget.isSpeaking) { _animationController.repeat(reverse: true); } else { - _animationController.animateTo(0, duration: 300.ms).then((_) => _animationController.reset()); + _animationController + .animateTo(0, duration: 300.ms) + .then((_) => _animationController.reset()); } } @@ -63,7 +66,9 @@ class _NoContentWidgetState extends State with SingleTickerProv builder: (context, value, child) => Container( decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(radius + 8)), - border: value > 0 ? Border.all(color: Colors.green, width: value) : null, + border: value > 0 + ? Border.all(color: Colors.green, width: value) + : null, ), child: child, ), diff --git a/lib/widgets/chat/call/participant.dart b/lib/widgets/chat/call/participant.dart index bc4e63a..f2155a1 100644 --- a/lib/widgets/chat/call/participant.dart +++ b/lib/widgets/chat/call/participant.dart @@ -95,7 +95,8 @@ class RemoteParticipantWidget extends ParticipantWidget { State createState() => _RemoteParticipantWidgetState(); } -abstract class _ParticipantWidgetState extends State { +abstract class _ParticipantWidgetState + extends State { VideoTrack? get _activeVideoTrack; TrackPublication? get _firstAudioPublication; @@ -126,7 +127,8 @@ abstract class _ParticipantWidgetState extends Stat void onParticipantChanged() { setState(() { if (widget.participant.metadata != null) { - _userinfoMetadata = Account.fromJson(jsonDecode(widget.participant.metadata!)); + _userinfoMetadata = + Account.fromJson(jsonDecode(widget.participant.metadata!)); } }); } @@ -158,8 +160,11 @@ abstract class _ParticipantWidgetState extends Stat mainAxisSize: MainAxisSize.min, children: [ ParticipantInfoWidget( - title: widget.participant.name.isNotEmpty ? widget.participant.name : widget.participant.identity, - audioAvailable: _firstAudioPublication?.muted == false && _firstAudioPublication?.subscribed == true, + title: widget.participant.name.isNotEmpty + ? widget.participant.name + : widget.participant.identity, + audioAvailable: _firstAudioPublication?.muted == false && + _firstAudioPublication?.subscribed == true, connectionQuality: widget.participant.connectionQuality, isScreenShare: widget.isScreenShare, ), @@ -171,7 +176,8 @@ abstract class _ParticipantWidgetState extends Stat } } -class _LocalParticipantWidgetState extends _ParticipantWidgetState { +class _LocalParticipantWidgetState + extends _ParticipantWidgetState { @override LocalTrackPublication? get _firstAudioPublication => widget.participant.audioTrackPublications.firstOrNull; @@ -180,7 +186,8 @@ class _LocalParticipantWidgetState extends _ParticipantWidgetState widget.videoTrack; } -class _RemoteParticipantWidgetState extends _ParticipantWidgetState { +class _RemoteParticipantWidgetState + extends _ParticipantWidgetState { @override RemoteTrackPublication? get _firstAudioPublication => widget.participant.audioTrackPublications.firstOrNull; diff --git a/lib/widgets/chat/call/participant_info.dart b/lib/widgets/chat/call/participant_info.dart index 8279691..2765293 100644 --- a/lib/widgets/chat/call/participant_info.dart +++ b/lib/widgets/chat/call/participant_info.dart @@ -55,7 +55,9 @@ class ParticipantInfoWidget extends StatelessWidget { Padding( padding: const EdgeInsets.only(left: 5), child: Icon( - connectionQuality == ConnectionQuality.poor ? Icons.wifi_off_outlined : Icons.wifi, + connectionQuality == ConnectionQuality.poor + ? Icons.wifi_off_outlined + : Icons.wifi, color: { ConnectionQuality.excellent: Colors.green, ConnectionQuality.good: Colors.orange, diff --git a/lib/widgets/chat/call/participant_menu.dart b/lib/widgets/chat/call/participant_menu.dart index 107d401..b31027a 100644 --- a/lib/widgets/chat/call/participant_menu.dart +++ b/lib/widgets/chat/call/participant_menu.dart @@ -22,7 +22,9 @@ class ParticipantMenu extends StatefulWidget { class _ParticipantMenuState extends State { RemoteTrackPublication? get _videoPublication => - widget.participant.videoTrackPublications.where((element) => element.sid == widget.videoTrack?.sid).firstOrNull; + widget.participant.videoTrackPublications + .where((element) => element.sid == widget.videoTrack?.sid) + .firstOrNull; RemoteTrackPublication? get _firstAudioPublication => widget.participant.audioTrackPublications.firstOrNull; @@ -39,7 +41,8 @@ class _ParticipantMenuState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: const EdgeInsets.only(left: 8, right: 8, top: 20, bottom: 12), + padding: + const EdgeInsets.only(left: 8, right: 8, top: 20, bottom: 12), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 8, @@ -59,9 +62,14 @@ class _ParticipantMenuState extends State { leading: Icon( Icons.volume_up, color: { - TrackSubscriptionState.notAllowed: Theme.of(context).colorScheme.error, - TrackSubscriptionState.unsubscribed: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), - TrackSubscriptionState.subscribed: Theme.of(context).colorScheme.primary, + TrackSubscriptionState.notAllowed: + Theme.of(context).colorScheme.error, + TrackSubscriptionState.unsubscribed: Theme.of(context) + .colorScheme + .onSurface + .withOpacity(0.6), + TrackSubscriptionState.subscribed: + Theme.of(context).colorScheme.primary, }[_firstAudioPublication!.subscriptionState], ), title: Text( @@ -83,9 +91,14 @@ class _ParticipantMenuState extends State { leading: Icon( widget.isScreenShare ? Icons.monitor : Icons.videocam, color: { - TrackSubscriptionState.notAllowed: Theme.of(context).colorScheme.error, - TrackSubscriptionState.unsubscribed: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), - TrackSubscriptionState.subscribed: Theme.of(context).colorScheme.primary, + TrackSubscriptionState.notAllowed: + Theme.of(context).colorScheme.error, + TrackSubscriptionState.unsubscribed: Theme.of(context) + .colorScheme + .onSurface + .withOpacity(0.6), + TrackSubscriptionState.subscribed: + Theme.of(context).colorScheme.primary, }[_videoPublication!.subscriptionState], ), title: Text( @@ -107,7 +120,9 @@ class _ParticipantMenuState extends State { ...[30, 15, 8].map( (x) => ListTile( leading: Icon( - _videoPublication?.fps == x ? Icons.check_box_outlined : Icons.check_box_outline_blank, + _videoPublication?.fps == x + ? Icons.check_box_outlined + : Icons.check_box_outline_blank, ), title: Text('Set preferred frame-per-second to $x'), onTap: () { @@ -125,7 +140,9 @@ class _ParticipantMenuState extends State { ].map( (x) => ListTile( leading: Icon( - _videoPublication?.videoQuality == x.$2 ? Icons.check_box_outlined : Icons.check_box_outline_blank, + _videoPublication?.videoQuality == x.$2 + ? Icons.check_box_outlined + : Icons.check_box_outline_blank, ), title: Text('Set preferred quality to ${x.$1}'), onTap: () { diff --git a/lib/widgets/chat/call/participant_stats.dart b/lib/widgets/chat/call/participant_stats.dart index e1205e0..41bac65 100644 --- a/lib/widgets/chat/call/participant_stats.dart +++ b/lib/widgets/chat/call/participant_stats.dart @@ -28,11 +28,14 @@ class _ParticipantStatsWidgetState extends State { stats['layer-$key'] = '${value.frameWidth ?? 0}x${value.frameHeight ?? 0} ${value.framesPerSecond?.toDouble() ?? 0} fps, ${event.bitrateForLayers[key] ?? 0} kbps'; }); - var firstStats = event.stats['f'] ?? event.stats['h'] ?? event.stats['q']; + var firstStats = + event.stats['f'] ?? event.stats['h'] ?? event.stats['q']; if (firstStats != null) { stats['encoder'] = firstStats.encoderImplementation ?? ''; - stats['video codec'] = '${firstStats.mimeType}, ${firstStats.clockRate}hz, pt: ${firstStats.payloadType}'; - stats['qualityLimitationReason'] = firstStats.qualityLimitationReason ?? ''; + stats['video codec'] = + '${firstStats.mimeType}, ${firstStats.clockRate}hz, pt: ${firstStats.payloadType}'; + stats['qualityLimitationReason'] = + firstStats.qualityLimitationReason ?? ''; } }); }); @@ -41,7 +44,8 @@ class _ParticipantStatsWidgetState extends State { listener.on((event) { setState(() { stats['video rx'] = '${event.currentBitrate.toInt()} kpbs'; - stats['video codec'] = '${event.stats.mimeType}, ${event.stats.clockRate}hz, pt: ${event.stats.payloadType}'; + stats['video codec'] = + '${event.stats.mimeType}, ${event.stats.clockRate}hz, pt: ${event.stats.payloadType}'; stats['video size'] = '${event.stats.frameWidth}x${event.stats.frameHeight} ${event.stats.framesPerSecond?.toDouble()}fps'; stats['video jitter'] = '${event.stats.jitter} s'; @@ -70,7 +74,8 @@ class _ParticipantStatsWidgetState extends State { stats['audio codec'] = '${event.stats.mimeType}, ${event.stats.clockRate}hz, ${event.stats.channels}ch, pt: ${event.stats.payloadType}'; stats['audio jitter'] = '${event.stats.jitter} s'; - stats['audio concealed samples'] = '${event.stats.concealedSamples} / ${event.stats.concealmentEvents}'; + stats['audio concealed samples'] = + '${event.stats.concealedSamples} / ${event.stats.concealmentEvents}'; stats['audio packets lost'] = '${event.stats.packetsLost}'; stats['audio packets received'] = '${event.stats.packetsReceived}'; }); @@ -83,7 +88,10 @@ class _ParticipantStatsWidgetState extends State { element.dispose(); } listeners.clear(); - for (var track in [...widget.participant.videoTrackPublications, ...widget.participant.audioTrackPublications]) { + for (var track in [ + ...widget.participant.videoTrackPublications, + ...widget.participant.audioTrackPublications + ]) { if (track.track != null) { _setUpListener(track.track!); } @@ -117,7 +125,8 @@ class _ParticipantStatsWidgetState extends State { horizontal: 8, ), child: Column( - children: stats.entries.map((e) => Text('${e.key}: ${e.value}')).toList(), + children: + stats.entries.map((e) => Text('${e.key}: ${e.value}')).toList(), ), ); } diff --git a/lib/widgets/chat/channel_action.dart b/lib/widgets/chat/channel_action.dart index 1f1bc54..460e752 100644 --- a/lib/widgets/chat/channel_action.dart +++ b/lib/widgets/chat/channel_action.dart @@ -14,7 +14,8 @@ class ChannelCallAction extends StatefulWidget { final Channel channel; final Function onUpdate; - const ChannelCallAction({super.key, this.call, required this.channel, required this.onUpdate}); + const ChannelCallAction( + {super.key, this.call, required this.channel, required this.onUpdate}); @override State createState() => _ChannelCallActionState(); @@ -32,7 +33,8 @@ class _ChannelCallActionState extends State { return; } - var uri = getRequestUri('messaging', '/api/channels/${widget.channel.alias}/calls'); + var uri = getRequestUri( + 'messaging', '/api/channels/${widget.channel.alias}/calls'); var res = await auth.client!.post(uri); if (res.statusCode != 200) { @@ -52,7 +54,8 @@ class _ChannelCallActionState extends State { return; } - var uri = getRequestUri('messaging', '/api/channels/${widget.channel.alias}/calls/ongoing'); + var uri = getRequestUri( + 'messaging', '/api/channels/${widget.channel.alias}/calls/ongoing'); var res = await auth.client!.delete(uri); if (res.statusCode != 200) { @@ -75,7 +78,9 @@ class _ChannelCallActionState extends State { endsCall(); } }, - icon: widget.call == null ? const Icon(Icons.call) : const Icon(Icons.call_end), + icon: widget.call == null + ? const Icon(Icons.call) + : const Icon(Icons.call_end), ); } } @@ -84,7 +89,8 @@ class ChannelManageAction extends StatelessWidget { final Channel channel; final Function onUpdate; - const ChannelManageAction({super.key, required this.channel, required this.onUpdate}); + const ChannelManageAction( + {super.key, required this.channel, required this.onUpdate}); @override Widget build(BuildContext context) { diff --git a/lib/widgets/chat/channel_deletion.dart b/lib/widgets/chat/channel_deletion.dart index 05b441b..6d5c206 100644 --- a/lib/widgets/chat/channel_deletion.dart +++ b/lib/widgets/chat/channel_deletion.dart @@ -12,7 +12,8 @@ class ChannelDeletion extends StatefulWidget { final Channel channel; final bool isOwned; - const ChannelDeletion({super.key, required this.channel, required this.isOwned}); + const ChannelDeletion( + {super.key, required this.channel, required this.isOwned}); @override State createState() => _ChannelDeletionState(); diff --git a/lib/widgets/chat/maintainer.dart b/lib/widgets/chat/maintainer.dart index 23f0e95..d2c59bd 100644 --- a/lib/widgets/chat/maintainer.dart +++ b/lib/widgets/chat/maintainer.dart @@ -55,19 +55,23 @@ class _ChatMaintainerState extends State { switch (result.method) { case 'messages.new': final payload = Message.fromJson(result.payload!); - if (payload.channelId == widget.channel.id) widget.onInsertMessage(payload); + if (payload.channelId == widget.channel.id) + widget.onInsertMessage(payload); break; case 'messages.update': final payload = Message.fromJson(result.payload!); - if (payload.channelId == widget.channel.id) widget.onUpdateMessage(payload); + if (payload.channelId == widget.channel.id) + widget.onUpdateMessage(payload); break; case 'messages.burnt': final payload = Message.fromJson(result.payload!); - if (payload.channelId == widget.channel.id) widget.onDeleteMessage(payload); + if (payload.channelId == widget.channel.id) + widget.onDeleteMessage(payload); break; case 'calls.new': final payload = Call.fromJson(result.payload!); - if (payload.channelId == widget.channel.id) widget.onCallStarted(payload); + if (payload.channelId == widget.channel.id) + widget.onCallStarted(payload); break; case 'calls.end': final payload = Call.fromJson(result.payload!); diff --git a/lib/widgets/chat/message_action.dart b/lib/widgets/chat/message_action.dart index 4baa609..378efb5 100644 --- a/lib/widgets/chat/message_action.dart +++ b/lib/widgets/chat/message_action.dart @@ -79,7 +79,9 @@ class ChatMessageAction extends StatelessWidget { return ListView( children: [ - ...(snapshot.data['id'] == item.sender.account.externalId ? authorizedItems : List.empty()), + ...(snapshot.data['id'] == item.sender.account.externalId + ? authorizedItems + : List.empty()), ListTile( leading: const Icon(Icons.reply), title: Text(AppLocalizations.of(context)!.reply), diff --git a/lib/widgets/chat/message_deletion.dart b/lib/widgets/chat/message_deletion.dart index 8df92ff..cfba7ec 100644 --- a/lib/widgets/chat/message_deletion.dart +++ b/lib/widgets/chat/message_deletion.dart @@ -19,7 +19,8 @@ class ChatMessageDeletionDialog extends StatefulWidget { }); @override - State createState() => _ChatMessageDeletionDialogState(); + State createState() => + _ChatMessageDeletionDialogState(); } class _ChatMessageDeletionDialogState extends State { @@ -29,7 +30,8 @@ class _ChatMessageDeletionDialogState extends State { final auth = context.read(); if (!await auth.isAuthorized()) return; - final uri = getRequestUri('messaging', '/api/channels/${widget.channel}/messages/${widget.item.id}'); + final uri = getRequestUri('messaging', + '/api/channels/${widget.channel}/messages/${widget.item.id}'); setState(() => _isSubmitting = true); final res = await auth.client!.delete(uri); diff --git a/lib/widgets/chat/message_editor.dart b/lib/widgets/chat/message_editor.dart index 2ed523a..881618e 100644 --- a/lib/widgets/chat/message_editor.dart +++ b/lib/widgets/chat/message_editor.dart @@ -18,7 +18,12 @@ class ChatMessageEditor extends StatefulWidget { final Message? replying; final Function? onReset; - const ChatMessageEditor({super.key, required this.channel, this.editing, this.replying, this.onReset}); + const ChatMessageEditor( + {super.key, + required this.channel, + this.editing, + this.replying, + this.onReset}); @override State createState() => _ChatMessageEditorState(); @@ -51,7 +56,8 @@ class _ChatMessageEditorState extends State { final uri = widget.editing == null ? getRequestUri('messaging', '/api/channels/${widget.channel}/messages') - : getRequestUri('messaging', '/api/channels/${widget.channel}/messages/${widget.editing!.id}'); + : getRequestUri('messaging', + '/api/channels/${widget.channel}/messages/${widget.editing!.id}'); final req = Request(widget.editing == null ? "POST" : "PUT", uri); req.headers['Content-Type'] = 'application/json'; @@ -84,7 +90,8 @@ class _ChatMessageEditorState extends State { setState(() { _prevEditingId = widget.editing!.id; _textController.text = widget.editing!.content; - _attachments = widget.editing!.attachments ?? List.empty(growable: true); + _attachments = + widget.editing!.attachments ?? List.empty(growable: true); }); } } @@ -147,11 +154,15 @@ class _ChatMessageEditorState extends State { children: [ badge.Badge( showBadge: _attachments.isNotEmpty, - badgeContent: Text(_attachments.length.toString(), style: const TextStyle(color: Colors.white)), + badgeContent: Text(_attachments.length.toString(), + style: const TextStyle(color: Colors.white)), position: badge.BadgePosition.custom(top: -2, end: 8), child: TextButton( - style: TextButton.styleFrom(shape: const CircleBorder(), padding: const EdgeInsets.all(4)), - onPressed: !_isSubmitting ? () => viewAttachments(context) : null, + style: TextButton.styleFrom( + shape: const CircleBorder(), + padding: const EdgeInsets.all(4)), + onPressed: + !_isSubmitting ? () => viewAttachments(context) : null, child: const Icon(Icons.attach_file), ), ), @@ -163,14 +174,18 @@ class _ChatMessageEditorState extends State { autocorrect: true, keyboardType: TextInputType.text, decoration: InputDecoration.collapsed( - hintText: AppLocalizations.of(context)!.chatMessagePlaceholder, + hintText: + AppLocalizations.of(context)!.chatMessagePlaceholder, ), onSubmitted: (_) => sendMessage(context), - onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), ), ), TextButton( - style: TextButton.styleFrom(shape: const CircleBorder(), padding: const EdgeInsets.all(4)), + style: TextButton.styleFrom( + shape: const CircleBorder(), + padding: const EdgeInsets.all(4)), onPressed: !_isSubmitting ? () => sendMessage(context) : null, child: const Icon(Icons.send), ) diff --git a/lib/widgets/exts.dart b/lib/widgets/exts.dart index 29200d4..984e236 100644 --- a/lib/widgets/exts.dart +++ b/lib/widgets/exts.dart @@ -8,7 +8,8 @@ extension SolianCommonExtensions on BuildContext { if (message.trim().isEmpty) return ''; return message .split(' ') - .map((element) => "${element[0].toUpperCase()}${element.substring(1).toLowerCase()}") + .map((element) => + "${element[0].toUpperCase()}${element.substring(1).toLowerCase()}") .join(" "); } diff --git a/lib/widgets/indent_wrapper.dart b/lib/widgets/indent_wrapper.dart index b9533bc..2939f58 100644 --- a/lib/widgets/indent_wrapper.dart +++ b/lib/widgets/indent_wrapper.dart @@ -22,10 +22,12 @@ class IndentWrapper extends LayoutWrapper { return Scaffold( appBar: AppBar( - leading: hideDrawer ? IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () => router.pop(), - ) : null, + leading: hideDrawer + ? IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => router.pop(), + ) + : null, title: Text(title), actions: appBarActions, ), diff --git a/lib/widgets/posts/attachment_editor.dart b/lib/widgets/posts/attachment_editor.dart index 32f4ec5..1446cc6 100755 --- a/lib/widgets/posts/attachment_editor.dart +++ b/lib/widgets/posts/attachment_editor.dart @@ -49,7 +49,8 @@ class _AttachmentEditorState extends State { ); } - Future pickImageToUpload(BuildContext context, ImageSource source) async { + Future pickImageToUpload( + BuildContext context, ImageSource source) async { final auth = context.read(); if (!await auth.isAuthorized()) return; @@ -74,7 +75,8 @@ class _AttachmentEditorState extends State { } } - Future pickVideoToUpload(BuildContext context, ImageSource source) async { + Future pickVideoToUpload( + BuildContext context, ImageSource source) async { final auth = context.read(); if (!await auth.isAuthorized()) return; @@ -102,7 +104,8 @@ class _AttachmentEditorState extends State { Future uploadAttachment(File file, String hashcode) async { final auth = context.read(); - final req = MultipartRequest('POST', getRequestUri(widget.provider, '/api/attachments')); + final req = MultipartRequest( + 'POST', getRequestUri(widget.provider, '/api/attachments')); req.files.add(await MultipartFile.fromPath('attachment', file.path)); req.fields['hashcode'] = hashcode; @@ -118,10 +121,12 @@ class _AttachmentEditorState extends State { } } - Future disposeAttachment(BuildContext context, Attachment item, int index) async { + Future disposeAttachment( + BuildContext context, Attachment item, int index) async { final auth = context.read(); - final req = MultipartRequest('DELETE', getRequestUri(widget.provider, '/api/attachments/${item.id}')); + final req = MultipartRequest('DELETE', + getRequestUri(widget.provider, '/api/attachments/${item.id}')); setState(() => _isSubmitting = true); var res = await auth.client!.send(req); @@ -162,7 +167,17 @@ class _AttachmentEditorState extends State { if (bytes == 0) return '0 Bytes'; const k = 1024; final dm = decimals < 0 ? 0 : decimals; - final sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; + final sizes = [ + 'Bytes', + 'KiB', + 'MiB', + 'GiB', + 'TiB', + 'PiB', + 'EiB', + 'ZiB', + 'YiB' + ]; final i = (math.log(bytes) / math.log(k)).floor().toInt(); return '${(bytes / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}'; } @@ -180,7 +195,8 @@ class _AttachmentEditorState extends State { return Column( children: [ Container( - padding: const EdgeInsets.only(left: 8, right: 8, top: 20, bottom: 12), + padding: + const EdgeInsets.only(left: 8, right: 8, top: 20, bottom: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -199,7 +215,9 @@ class _AttachmentEditorState extends State { builder: (context, snapshot) { if (snapshot.hasData && snapshot.data == true) { return TextButton( - onPressed: _isSubmitting ? null : () => viewAttachMethods(context), + onPressed: _isSubmitting + ? null + : () => viewAttachMethods(context), style: TextButton.styleFrom(shape: const CircleBorder()), child: const Icon(Icons.add_circle), ); @@ -211,7 +229,9 @@ class _AttachmentEditorState extends State { ], ), ), - _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(), + _isSubmitting + ? const LinearProgressIndicator().animate().scaleX() + : Container(), Expanded( child: ListView.separated( itemCount: _attachments.length, @@ -243,7 +263,8 @@ class _AttachmentEditorState extends State { foregroundColor: Colors.red, ), child: const Icon(Icons.delete), - onPressed: () => disposeAttachment(context, element, index), + onPressed: () => + disposeAttachment(context, element, index), ), ], ), @@ -303,7 +324,8 @@ class AttachmentEditorMethodPopup extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.add_photo_alternate, color: Colors.indigo), + const Icon(Icons.add_photo_alternate, + color: Colors.indigo), const SizedBox(height: 8), Text(AppLocalizations.of(context)!.pickPhoto), ], diff --git a/lib/widgets/posts/content/attachment.dart b/lib/widgets/posts/content/attachment.dart index 08accd5..28343ae 100644 --- a/lib/widgets/posts/content/attachment.dart +++ b/lib/widgets/posts/content/attachment.dart @@ -36,6 +36,17 @@ class _AttachmentItemState extends State { ); late final _videoController = VideoController(_videoPlayer); + @override + void initState() { + super.initState(); + if (widget.type != 1) { + _videoPlayer.open( + Media(widget.url), + play: false, + ); + } + } + @override Widget build(BuildContext context) { const borderRadius = Radius.circular(8); @@ -53,6 +64,7 @@ class _AttachmentItemState extends State { children: [ Image.network( widget.url, + key: Key(getTag()), width: double.infinity, height: double.infinity, fit: BoxFit.cover, @@ -63,6 +75,7 @@ class _AttachmentItemState extends State { right: 12, bottom: 8, child: Material( + color: Colors.transparent, child: Chip(label: Text(widget.badge!)), ), ) @@ -83,11 +96,6 @@ class _AttachmentItemState extends State { }, ); } else { - _videoPlayer.open( - Media(widget.url), - play: false, - ); - content = ClipRRect( borderRadius: const BorderRadius.all(borderRadius), child: Video( @@ -121,9 +129,11 @@ class AttachmentList extends StatelessWidget { final List items; final String provider; - const AttachmentList({super.key, required this.items, required this.provider}); + const AttachmentList( + {super.key, required this.items, required this.provider}); - Uri getFileUri(String fileId) => getRequestUri(provider, '/api/attachments/o/$fileId'); + Uri getFileUri(String fileId) => + getRequestUri(provider, '/api/attachments/o/$fileId'); @override Widget build(BuildContext context) { diff --git a/lib/widgets/posts/item.dart b/lib/widgets/posts/item.dart index 6a7cefe..fb4219d 100644 --- a/lib/widgets/posts/item.dart +++ b/lib/widgets/posts/item.dart @@ -47,7 +47,8 @@ class _PostItemState extends State { } void viewComments() { - final PagingController commentPaging = PagingController(firstPageKey: 0); + final PagingController commentPaging = + PagingController(firstPageKey: 0); showModalBottomSheet( context: context, @@ -87,10 +88,12 @@ class _PostItemState extends State { Widget renderAttachments() { if (widget.item.modelType == 'article') return Container(); - if (widget.item.attachments != null && widget.item.attachments!.isNotEmpty) { + if (widget.item.attachments != null && + widget.item.attachments!.isNotEmpty) { return Padding( padding: const EdgeInsets.only(top: 8), - child: AttachmentList(items: widget.item.attachments!, provider: 'interactive'), + child: AttachmentList( + items: widget.item.attachments!, provider: 'interactive'), ); } else { return Container(); @@ -130,8 +133,9 @@ class _PostItemState extends State { ); } - String getAuthorDescribe() => - widget.item.author.description.isNotEmpty ? widget.item.author.description : 'No description yet.'; + String getAuthorDescribe() => widget.item.author.description.isNotEmpty + ? widget.item.author.description + : 'No description yet.'; @override void initState() { @@ -177,7 +181,8 @@ class _PostItemState extends State { children: [ ...headingParts, Padding( - padding: const EdgeInsets.only(left: 12, right: 12, top: 4), + padding: + const EdgeInsets.only(left: 12, right: 12, top: 4), child: renderContent(), ), renderAttachments(), diff --git a/lib/widgets/posts/reaction_action.dart b/lib/widgets/posts/reaction_action.dart index 65015ae..99ea870 100644 --- a/lib/widgets/posts/reaction_action.dart +++ b/lib/widgets/posts/reaction_action.dart @@ -106,7 +106,8 @@ class _ReactionActionPopupState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: const EdgeInsets.only(left: 8, right: 8, top: 20, bottom: 12), + padding: + const EdgeInsets.only(left: 8, right: 8, top: 20, bottom: 12), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 8, @@ -118,7 +119,9 @@ class _ReactionActionPopupState extends State { ), ), ), - _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(), + _isSubmitting + ? const LinearProgressIndicator().animate().scaleX() + : Container(), Expanded( child: ListView.builder( itemCount: reactions.length, diff --git a/lib/widgets/signin_required.dart b/lib/widgets/signin_required.dart index 6ab83e0..bda890e 100644 --- a/lib/widgets/signin_required.dart +++ b/lib/widgets/signin_required.dart @@ -36,5 +36,4 @@ class SignInRequiredScreen extends StatelessWidget { }, ); } - -} \ No newline at end of file +}