✨ Network status, poll submit feedback
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
@@ -18,6 +17,7 @@ sealed class WebSocketState with _$WebSocketState {
|
|||||||
const factory WebSocketState.connected() = _Connected;
|
const factory WebSocketState.connected() = _Connected;
|
||||||
const factory WebSocketState.connecting() = _Connecting;
|
const factory WebSocketState.connecting() = _Connecting;
|
||||||
const factory WebSocketState.disconnected() = _Disconnected;
|
const factory WebSocketState.disconnected() = _Disconnected;
|
||||||
|
const factory WebSocketState.duplicateDevice() = _DuplicateDevice;
|
||||||
const factory WebSocketState.error(String message) = _Error;
|
const factory WebSocketState.error(String message) = _Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ class WebSocketService {
|
|||||||
Timer? _heartbeatTimer;
|
Timer? _heartbeatTimer;
|
||||||
|
|
||||||
DateTime? _heartbeatAt;
|
DateTime? _heartbeatAt;
|
||||||
Duration? _heartbeatDelay;
|
Duration? heartbeatDelay;
|
||||||
|
|
||||||
Stream<WebSocketPacket> get dataStream => _streamController.stream;
|
Stream<WebSocketPacket> get dataStream => _streamController.stream;
|
||||||
Stream<WebSocketState> get statusStream => _statusStreamController.stream;
|
Stream<WebSocketState> get statusStream => _statusStreamController.stream;
|
||||||
@@ -87,16 +87,23 @@ class WebSocketService {
|
|||||||
);
|
);
|
||||||
if (packet.type == 'pong' && _heartbeatAt != null) {
|
if (packet.type == 'pong' && _heartbeatAt != null) {
|
||||||
var now = DateTime.now();
|
var now = DateTime.now();
|
||||||
_heartbeatDelay = now.difference(_heartbeatAt!);
|
heartbeatDelay = now.difference(_heartbeatAt!);
|
||||||
log(
|
log(
|
||||||
"[WebSocket] Server respond last heartbeat for ${_heartbeatDelay!.inMilliseconds} ms",
|
"[WebSocket] Server respond last heartbeat for ${heartbeatDelay!.inMilliseconds} ms",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDone: () {
|
onDone: () {
|
||||||
log('[WebSocket] Connection closed, attempting to reconnect...');
|
if (_channel?.closeCode == 1006) {
|
||||||
_scheduleReconnect();
|
log(
|
||||||
_statusStreamController.sink.add(WebSocketState.disconnected());
|
'[WebSocket] Connection closed due to duplicate device. Not going to reconnect...',
|
||||||
|
);
|
||||||
|
_statusStreamController.sink.add(WebSocketState.duplicateDevice());
|
||||||
|
} else {
|
||||||
|
log('[WebSocket] Connection closed, attempting to reconnect...');
|
||||||
|
_scheduleReconnect();
|
||||||
|
_statusStreamController.sink.add(WebSocketState.disconnected());
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onError: (error) {
|
onError: (error) {
|
||||||
log('[WebSocket] Error occurred: $error, attempting to reconnect...');
|
log('[WebSocket] Error occurred: $error, attempting to reconnect...');
|
||||||
|
@@ -61,13 +61,14 @@ extension WebSocketStatePatterns on WebSocketState {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( _Connected value)? connected,TResult Function( _Connecting value)? connecting,TResult Function( _Disconnected value)? disconnected,TResult Function( _Error value)? error,required TResult orElse(),}){
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( _Connected value)? connected,TResult Function( _Connecting value)? connecting,TResult Function( _Disconnected value)? disconnected,TResult Function( _DuplicateDevice value)? duplicateDevice,TResult Function( _Error value)? error,required TResult orElse(),}){
|
||||||
final _that = this;
|
final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Connected() when connected != null:
|
case _Connected() when connected != null:
|
||||||
return connected(_that);case _Connecting() when connecting != null:
|
return connected(_that);case _Connecting() when connecting != null:
|
||||||
return connecting(_that);case _Disconnected() when disconnected != null:
|
return connecting(_that);case _Disconnected() when disconnected != null:
|
||||||
return disconnected(_that);case _Error() when error != null:
|
return disconnected(_that);case _DuplicateDevice() when duplicateDevice != null:
|
||||||
|
return duplicateDevice(_that);case _Error() when error != null:
|
||||||
return error(_that);case _:
|
return error(_that);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
@@ -86,13 +87,14 @@ return error(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( _Connected value) connected,required TResult Function( _Connecting value) connecting,required TResult Function( _Disconnected value) disconnected,required TResult Function( _Error value) error,}){
|
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( _Connected value) connected,required TResult Function( _Connecting value) connecting,required TResult Function( _Disconnected value) disconnected,required TResult Function( _DuplicateDevice value) duplicateDevice,required TResult Function( _Error value) error,}){
|
||||||
final _that = this;
|
final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Connected():
|
case _Connected():
|
||||||
return connected(_that);case _Connecting():
|
return connected(_that);case _Connecting():
|
||||||
return connecting(_that);case _Disconnected():
|
return connecting(_that);case _Disconnected():
|
||||||
return disconnected(_that);case _Error():
|
return disconnected(_that);case _DuplicateDevice():
|
||||||
|
return duplicateDevice(_that);case _Error():
|
||||||
return error(_that);}
|
return error(_that);}
|
||||||
}
|
}
|
||||||
/// A variant of `map` that fallback to returning `null`.
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
@@ -107,13 +109,14 @@ return error(_that);}
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( _Connected value)? connected,TResult? Function( _Connecting value)? connecting,TResult? Function( _Disconnected value)? disconnected,TResult? Function( _Error value)? error,}){
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( _Connected value)? connected,TResult? Function( _Connecting value)? connecting,TResult? Function( _Disconnected value)? disconnected,TResult? Function( _DuplicateDevice value)? duplicateDevice,TResult? Function( _Error value)? error,}){
|
||||||
final _that = this;
|
final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Connected() when connected != null:
|
case _Connected() when connected != null:
|
||||||
return connected(_that);case _Connecting() when connecting != null:
|
return connected(_that);case _Connecting() when connecting != null:
|
||||||
return connecting(_that);case _Disconnected() when disconnected != null:
|
return connecting(_that);case _Disconnected() when disconnected != null:
|
||||||
return disconnected(_that);case _Error() when error != null:
|
return disconnected(_that);case _DuplicateDevice() when duplicateDevice != null:
|
||||||
|
return duplicateDevice(_that);case _Error() when error != null:
|
||||||
return error(_that);case _:
|
return error(_that);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -131,12 +134,13 @@ return error(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function()? connected,TResult Function()? connecting,TResult Function()? disconnected,TResult Function( String message)? error,required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function()? connected,TResult Function()? connecting,TResult Function()? disconnected,TResult Function()? duplicateDevice,TResult Function( String message)? error,required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Connected() when connected != null:
|
case _Connected() when connected != null:
|
||||||
return connected();case _Connecting() when connecting != null:
|
return connected();case _Connecting() when connecting != null:
|
||||||
return connecting();case _Disconnected() when disconnected != null:
|
return connecting();case _Disconnected() when disconnected != null:
|
||||||
return disconnected();case _Error() when error != null:
|
return disconnected();case _DuplicateDevice() when duplicateDevice != null:
|
||||||
|
return duplicateDevice();case _Error() when error != null:
|
||||||
return error(_that.message);case _:
|
return error(_that.message);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
@@ -155,12 +159,13 @@ return error(_that.message);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function() connected,required TResult Function() connecting,required TResult Function() disconnected,required TResult Function( String message) error,}) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function() connected,required TResult Function() connecting,required TResult Function() disconnected,required TResult Function() duplicateDevice,required TResult Function( String message) error,}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Connected():
|
case _Connected():
|
||||||
return connected();case _Connecting():
|
return connected();case _Connecting():
|
||||||
return connecting();case _Disconnected():
|
return connecting();case _Disconnected():
|
||||||
return disconnected();case _Error():
|
return disconnected();case _DuplicateDevice():
|
||||||
|
return duplicateDevice();case _Error():
|
||||||
return error(_that.message);}
|
return error(_that.message);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
@@ -175,12 +180,13 @@ return error(_that.message);}
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function()? connected,TResult? Function()? connecting,TResult? Function()? disconnected,TResult? Function( String message)? error,}) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function()? connected,TResult? Function()? connecting,TResult? Function()? disconnected,TResult? Function()? duplicateDevice,TResult? Function( String message)? error,}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Connected() when connected != null:
|
case _Connected() when connected != null:
|
||||||
return connected();case _Connecting() when connecting != null:
|
return connected();case _Connecting() when connecting != null:
|
||||||
return connecting();case _Disconnected() when disconnected != null:
|
return connecting();case _Disconnected() when disconnected != null:
|
||||||
return disconnected();case _Error() when error != null:
|
return disconnected();case _DuplicateDevice() when duplicateDevice != null:
|
||||||
|
return duplicateDevice();case _Error() when error != null:
|
||||||
return error(_that.message);case _:
|
return error(_that.message);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -303,6 +309,44 @@ String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _DuplicateDevice with DiagnosticableTreeMixin implements WebSocketState {
|
||||||
|
const _DuplicateDevice();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
properties
|
||||||
|
..add(DiagnosticsProperty('type', 'WebSocketState.duplicateDevice'))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DuplicateDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => runtimeType.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
||||||
|
return 'WebSocketState.duplicateDevice()';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
@@ -331,7 +331,7 @@ class _WebSocketIndicator extends HookConsumerWidget {
|
|||||||
final user = ref.watch(userInfoProvider);
|
final user = ref.watch(userInfoProvider);
|
||||||
final websocketState = ref.watch(websocketStateProvider);
|
final websocketState = ref.watch(websocketStateProvider);
|
||||||
final indicatorHeight =
|
final indicatorHeight =
|
||||||
MediaQuery.of(context).padding.top + (isDesktop ? 27.5 : 60);
|
MediaQuery.of(context).padding.top + (isDesktop ? 27.5 : 20);
|
||||||
|
|
||||||
Color indicatorColor;
|
Color indicatorColor;
|
||||||
String indicatorText;
|
String indicatorText;
|
||||||
@@ -343,7 +343,7 @@ class _WebSocketIndicator extends HookConsumerWidget {
|
|||||||
indicatorColor = Colors.teal;
|
indicatorColor = Colors.teal;
|
||||||
indicatorText = 'connectionReconnecting';
|
indicatorText = 'connectionReconnecting';
|
||||||
} else {
|
} else {
|
||||||
indicatorColor = Colors.orange;
|
indicatorColor = Colors.red;
|
||||||
indicatorText = 'connectionDisconnected';
|
indicatorText = 'connectionDisconnected';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,8 +2,10 @@ import 'dart:async';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/pods/websocket.dart';
|
||||||
import 'package:island/services/notify.dart';
|
import 'package:island/services/notify.dart';
|
||||||
import 'package:island/services/sharing_intent.dart';
|
import 'package:island/services/sharing_intent.dart';
|
||||||
|
import 'package:island/widgets/content/network_status_sheet.dart';
|
||||||
import 'package:island/widgets/tour/tour.dart';
|
import 'package:island/widgets/tour/tour.dart';
|
||||||
|
|
||||||
class AppWrapper extends HookConsumerWidget {
|
class AppWrapper extends HookConsumerWidget {
|
||||||
@@ -25,6 +27,27 @@ class AppWrapper extends HookConsumerWidget {
|
|||||||
};
|
};
|
||||||
}, const []);
|
}, const []);
|
||||||
|
|
||||||
|
final wsNotifier = ref.watch(websocketStateProvider.notifier);
|
||||||
|
final websocketState = ref.watch(websocketStateProvider);
|
||||||
|
|
||||||
|
final networkStateShowing = useState(false);
|
||||||
|
|
||||||
|
if (websocketState == WebSocketState.duplicateDevice()) {
|
||||||
|
if (!networkStateShowing.value) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
networkStateShowing.value = true;
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
isDismissible: false,
|
||||||
|
builder:
|
||||||
|
(context) =>
|
||||||
|
NetworkStatusSheet(onReconnect: () => wsNotifier.connect()),
|
||||||
|
).then((_) => networkStateShowing.value = false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return TourTriggerWidget(child: child);
|
return TourTriggerWidget(child: child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
76
lib/widgets/content/network_status_sheet.dart
Normal file
76
lib/widgets/content/network_status_sheet.dart
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/pods/websocket.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
|
||||||
|
class NetworkStatusSheet extends HookConsumerWidget {
|
||||||
|
final VoidCallback onReconnect;
|
||||||
|
|
||||||
|
const NetworkStatusSheet({super.key, required this.onReconnect});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final ws = ref.watch(websocketProvider);
|
||||||
|
final wsState = ref.watch(websocketStateProvider);
|
||||||
|
|
||||||
|
return SheetScaffold(
|
||||||
|
titleText:
|
||||||
|
wsState == WebSocketState.connected()
|
||||||
|
? 'Connection Status'
|
||||||
|
: 'Connection Issue',
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
wsState.when(
|
||||||
|
connected:
|
||||||
|
() => Text(
|
||||||
|
'Connected to server',
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
connecting:
|
||||||
|
() => Text(
|
||||||
|
'Connecting to server...',
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
disconnected:
|
||||||
|
() => Text(
|
||||||
|
'Disconnected from server',
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
duplicateDevice:
|
||||||
|
() => Text(
|
||||||
|
'Another device has connected with the same account.',
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
error:
|
||||||
|
(message) => Text(
|
||||||
|
'Connection error: $message',
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
if (ws.heartbeatDelay != null)
|
||||||
|
Text(
|
||||||
|
'Last heartbeat: ${ws.heartbeatDelay!.inMilliseconds}ms',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Center(
|
||||||
|
child: FilledButton.icon(
|
||||||
|
icon: const Icon(Symbols.wifi),
|
||||||
|
label: const Text('Reconnect'),
|
||||||
|
onPressed: () {
|
||||||
|
onReconnect();
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:island/models/poll.dart';
|
import 'package:island/models/poll.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/widgets/alert.dart';
|
||||||
|
|
||||||
class PollSubmit extends ConsumerStatefulWidget {
|
class PollSubmit extends ConsumerStatefulWidget {
|
||||||
const PollSubmit({
|
const PollSubmit({
|
||||||
@@ -193,12 +195,11 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
|
|||||||
|
|
||||||
// Only call onSubmit after server accepts
|
// Only call onSubmit after server accepts
|
||||||
widget.onSubmit(Map<String, dynamic>.unmodifiable(_answers));
|
widget.onSubmit(Map<String, dynamic>.unmodifiable(_answers));
|
||||||
|
|
||||||
|
showSnackBar('Poll answer has been submitted.');
|
||||||
|
HapticFeedback.heavyImpact();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
showErrorAlert(e);
|
||||||
ScaffoldMessenger.of(
|
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text('Failed to submit poll: $e')));
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@@ -16,6 +16,7 @@ import 'package:island/pods/userinfo.dart';
|
|||||||
import 'package:island/screens/posts/compose.dart';
|
import 'package:island/screens/posts/compose.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/services/time.dart';
|
import 'package:island/services/time.dart';
|
||||||
|
import 'package:island/utils/mapping.dart';
|
||||||
import 'package:island/widgets/account/account_name.dart';
|
import 'package:island/widgets/account/account_name.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/content/cloud_file_collection.dart';
|
import 'package:island/widgets/content/cloud_file_collection.dart';
|
||||||
@@ -573,38 +574,42 @@ class PostItem extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (item.meta?['embeds'] != null)
|
if (item.meta?['embeds'] != null)
|
||||||
...((item.meta!['embeds'] as List<dynamic>).map(
|
...((item.meta!['embeds'] as List<dynamic>)
|
||||||
(embedData) => switch (embedData['type']) {
|
.map((embedData) => convertMapKeysToSnakeCase(embedData))
|
||||||
'link' => EmbedLinkWidget(
|
.map(
|
||||||
link: SnScrappedLink.fromJson(
|
(embedData) => switch (embedData['type']) {
|
||||||
embedData as Map<String, dynamic>,
|
'link' => EmbedLinkWidget(
|
||||||
),
|
link: SnScrappedLink.fromJson(embedData),
|
||||||
maxWidth: math.min(
|
maxWidth: math.min(
|
||||||
MediaQuery.of(context).size.width,
|
MediaQuery.of(context).size.width,
|
||||||
kWideScreenWidth,
|
kWideScreenWidth,
|
||||||
),
|
),
|
||||||
margin: EdgeInsets.only(
|
margin: EdgeInsets.only(
|
||||||
top: 4,
|
top: 4,
|
||||||
bottom: 4,
|
bottom: 4,
|
||||||
left: renderingPadding.horizontal,
|
left: renderingPadding.horizontal,
|
||||||
right: renderingPadding.horizontal,
|
right: renderingPadding.horizontal,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'poll' => Card(
|
'poll' => Card(
|
||||||
margin: EdgeInsets.symmetric(
|
margin: EdgeInsets.symmetric(
|
||||||
horizontal: renderingPadding.horizontal,
|
horizontal: renderingPadding.horizontal,
|
||||||
vertical: 8,
|
vertical: 8,
|
||||||
),
|
),
|
||||||
child: PollSubmit(
|
child:
|
||||||
initialAnswers: embedData['poll']?['user_answer']?['answer'],
|
embedData['poll'] == null
|
||||||
stats: embedData['poll']?['stats'],
|
? Text('Poll was not loaded...')
|
||||||
poll: SnPollWithStats.fromJson(embedData['poll']),
|
: PollSubmit(
|
||||||
onSubmit: (_) {},
|
initialAnswers:
|
||||||
).padding(horizontal: 16, vertical: 12),
|
embedData['poll']?['user_answer']?['answer'],
|
||||||
),
|
stats: embedData['poll']?['stats'],
|
||||||
_ => Text('Unable show embed: ${embedData['type']}'),
|
poll: SnPollWithStats.fromJson(embedData['poll']),
|
||||||
},
|
onSubmit: (_) {},
|
||||||
)),
|
).padding(horizontal: 16, vertical: 12),
|
||||||
|
),
|
||||||
|
_ => Text('Unable show embed: ${embedData['type']}'),
|
||||||
|
},
|
||||||
|
)),
|
||||||
if (isShowReference)
|
if (isShowReference)
|
||||||
_buildReferencePost(context, item, renderingPadding),
|
_buildReferencePost(context, item, renderingPadding),
|
||||||
if (item.repliesCount > 0 && isEmbedReply)
|
if (item.repliesCount > 0 && isEmbedReply)
|
||||||
|
Reference in New Issue
Block a user