🎨 Formatted all the code

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

View File

@ -37,7 +37,8 @@ class _FriendPickerState extends State<FriendPicker> {
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,

View File

@ -41,7 +41,8 @@ class _ControlsWidgetState extends State<ControlsWidget> {
void initState() {
super.initState();
participant.addListener(onChange);
_subscription = Hardware.instance.onDeviceChange.stream.listen((List<MediaDevice> devices) {
_subscription = Hardware.instance.onDeviceChange.stream
.listen((List<MediaDevice> devices) {
revertDevices(devices);
});
Hardware.instance.enumerateDevices().then(revertDevices);
@ -161,18 +162,23 @@ class _ControlsWidgetState extends State<ControlsWidget> {
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<void>.delayed(const Duration(seconds: 1), () => requestBackgroundPermission(true));
return await Future<void>.delayed(const Duration(seconds: 1),
() => requestBackgroundPermission(true));
}
}
}
@ -223,7 +229,8 @@ class _ControlsWidgetState extends State<ControlsWidget> {
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<ControlsWidget> {
return PopupMenuItem<MediaDevice>(
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<ControlsWidget> {
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<ControlsWidget> {
return PopupMenuItem<MediaDevice>(
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<ControlsWidget> {
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<ControlsWidget> {
return PopupMenuItem<MediaDevice>(
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<ControlsWidget> {
),
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())

View File

@ -20,7 +20,8 @@ class NoContentWidget extends StatefulWidget {
State<NoContentWidget> createState() => _NoContentWidgetState();
}
class _NoContentWidgetState extends State<NoContentWidget> with SingleTickerProviderStateMixin {
class _NoContentWidgetState extends State<NoContentWidget>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
@override
@ -35,7 +36,9 @@ class _NoContentWidgetState extends State<NoContentWidget> 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<NoContentWidget> 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,
),

View File

@ -95,7 +95,8 @@ class RemoteParticipantWidget extends ParticipantWidget {
State<StatefulWidget> createState() => _RemoteParticipantWidgetState();
}
abstract class _ParticipantWidgetState<T extends ParticipantWidget> extends State<T> {
abstract class _ParticipantWidgetState<T extends ParticipantWidget>
extends State<T> {
VideoTrack? get _activeVideoTrack;
TrackPublication? get _firstAudioPublication;
@ -126,7 +127,8 @@ abstract class _ParticipantWidgetState<T extends ParticipantWidget> 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<T extends ParticipantWidget> 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<T extends ParticipantWidget> extends Stat
}
}
class _LocalParticipantWidgetState extends _ParticipantWidgetState<LocalParticipantWidget> {
class _LocalParticipantWidgetState
extends _ParticipantWidgetState<LocalParticipantWidget> {
@override
LocalTrackPublication<LocalAudioTrack>? get _firstAudioPublication =>
widget.participant.audioTrackPublications.firstOrNull;
@ -180,7 +186,8 @@ class _LocalParticipantWidgetState extends _ParticipantWidgetState<LocalParticip
VideoTrack? get _activeVideoTrack => widget.videoTrack;
}
class _RemoteParticipantWidgetState extends _ParticipantWidgetState<RemoteParticipantWidget> {
class _RemoteParticipantWidgetState
extends _ParticipantWidgetState<RemoteParticipantWidget> {
@override
RemoteTrackPublication<RemoteAudioTrack>? get _firstAudioPublication =>
widget.participant.audioTrackPublications.firstOrNull;

View File

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

View File

@ -22,7 +22,9 @@ class ParticipantMenu extends StatefulWidget {
class _ParticipantMenuState extends State<ParticipantMenu> {
RemoteTrackPublication<RemoteVideoTrack>? 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<RemoteAudioTrack>? get _firstAudioPublication =>
widget.participant.audioTrackPublications.firstOrNull;
@ -39,7 +41,8 @@ class _ParticipantMenuState extends State<ParticipantMenu> {
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<ParticipantMenu> {
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<ParticipantMenu> {
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<ParticipantMenu> {
...[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<ParticipantMenu> {
].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: () {

View File

@ -28,11 +28,14 @@ class _ParticipantStatsWidgetState extends State<ParticipantStatsWidget> {
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<ParticipantStatsWidget> {
listener.on<VideoReceiverStatsEvent>((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<ParticipantStatsWidget> {
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<ParticipantStatsWidget> {
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<ParticipantStatsWidget> {
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(),
),
);
}

View File

@ -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<ChannelCallAction> createState() => _ChannelCallActionState();
@ -32,7 +33,8 @@ class _ChannelCallActionState extends State<ChannelCallAction> {
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<ChannelCallAction> {
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<ChannelCallAction> {
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) {

View File

@ -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<ChannelDeletion> createState() => _ChannelDeletionState();

View File

@ -55,19 +55,23 @@ class _ChatMaintainerState extends State<ChatMaintainer> {
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!);

View File

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

View File

@ -19,7 +19,8 @@ class ChatMessageDeletionDialog extends StatefulWidget {
});
@override
State<ChatMessageDeletionDialog> createState() => _ChatMessageDeletionDialogState();
State<ChatMessageDeletionDialog> createState() =>
_ChatMessageDeletionDialogState();
}
class _ChatMessageDeletionDialogState extends State<ChatMessageDeletionDialog> {
@ -29,7 +30,8 @@ class _ChatMessageDeletionDialogState extends State<ChatMessageDeletionDialog> {
final auth = context.read<AuthProvider>();
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);

View File

@ -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<ChatMessageEditor> createState() => _ChatMessageEditorState();
@ -51,7 +56,8 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
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<ChatMessageEditor> {
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<ChatMessageEditor> {
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<ChatMessageEditor> {
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),
)

View File

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

View File

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

View File

@ -49,7 +49,8 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
);
}
Future<void> pickImageToUpload(BuildContext context, ImageSource source) async {
Future<void> pickImageToUpload(
BuildContext context, ImageSource source) async {
final auth = context.read<AuthProvider>();
if (!await auth.isAuthorized()) return;
@ -74,7 +75,8 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
}
}
Future<void> pickVideoToUpload(BuildContext context, ImageSource source) async {
Future<void> pickVideoToUpload(
BuildContext context, ImageSource source) async {
final auth = context.read<AuthProvider>();
if (!await auth.isAuthorized()) return;
@ -102,7 +104,8 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
Future<void> uploadAttachment(File file, String hashcode) async {
final auth = context.read<AuthProvider>();
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<AttachmentEditor> {
}
}
Future<void> disposeAttachment(BuildContext context, Attachment item, int index) async {
Future<void> disposeAttachment(
BuildContext context, Attachment item, int index) async {
final auth = context.read<AuthProvider>();
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<AttachmentEditor> {
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<AttachmentEditor> {
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<AttachmentEditor> {
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<AttachmentEditor> {
],
),
),
_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<AttachmentEditor> {
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),
],

View File

@ -36,6 +36,17 @@ class _AttachmentItemState extends State<AttachmentItem> {
);
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<AttachmentItem> {
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<AttachmentItem> {
right: 12,
bottom: 8,
child: Material(
color: Colors.transparent,
child: Chip(label: Text(widget.badge!)),
),
)
@ -83,11 +96,6 @@ class _AttachmentItemState extends State<AttachmentItem> {
},
);
} 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<Attachment> 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) {

View File

@ -47,7 +47,8 @@ class _PostItemState extends State<PostItem> {
}
void viewComments() {
final PagingController<int, Post> commentPaging = PagingController(firstPageKey: 0);
final PagingController<int, Post> commentPaging =
PagingController(firstPageKey: 0);
showModalBottomSheet(
context: context,
@ -87,10 +88,12 @@ class _PostItemState extends State<PostItem> {
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<PostItem> {
);
}
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<PostItem> {
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(),

View File

@ -106,7 +106,8 @@ class _ReactionActionPopupState extends State<ReactionActionPopup> {
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<ReactionActionPopup> {
),
),
),
_isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(),
_isSubmitting
? const LinearProgressIndicator().animate().scaleX()
: Container(),
Expanded(
child: ListView.builder(
itemCount: reactions.length,

View File

@ -36,5 +36,4 @@ class SignInRequiredScreen extends StatelessWidget {
},
);
}
}
}