diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 61711d3..7e395f1 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -222,6 +222,7 @@ "friendRequestDeclined": "Declined friend request from {}", "requestExpiredIn": "Expired in {}", "friendSentRequest": "Sent Friend Requests", + "friendSentRequestEmpty": "No sent friend requests", "friendSentRequestHint": { "one": "{} friend request sent", "other": "{} friend requests sent" diff --git a/lib/pods/websocket.dart b/lib/pods/websocket.dart index 1ca30f2..ff1af27 100644 --- a/lib/pods/websocket.dart +++ b/lib/pods/websocket.dart @@ -14,7 +14,7 @@ part 'websocket.freezed.dart'; part 'websocket.g.dart'; @freezed -class WebSocketState with _$WebSocketState { +abstract class WebSocketState with _$WebSocketState { const factory WebSocketState.connected() = _Connected; const factory WebSocketState.connecting() = _Connecting; const factory WebSocketState.disconnected() = _Disconnected; @@ -72,9 +72,7 @@ class WebSocketService { log('[WebSocket] Trying connecting to $url'); try { if (kIsWeb) { - _channel = WebSocketChannel.connect( - Uri.parse(url)..queryParameters['tk'] = atk, - ); + _channel = WebSocketChannel.connect(Uri.parse('$url?tk=$atk')); } else { _channel = IOWebSocketChannel.connect( Uri.parse(url), diff --git a/lib/pods/websocket.freezed.dart b/lib/pods/websocket.freezed.dart index 139372a..f05ed90 100644 --- a/lib/pods/websocket.freezed.dart +++ b/lib/pods/websocket.freezed.dart @@ -13,11 +13,17 @@ part of 'websocket.dart'; // dart format off T _$identity(T value) => value; /// @nodoc -mixin _$WebSocketState { +mixin _$WebSocketState implements DiagnosticableTreeMixin { +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'WebSocketState')) + ; +} @override bool operator ==(Object other) { @@ -29,7 +35,7 @@ bool operator ==(Object other) { int get hashCode => runtimeType.hashCode; @override -String toString() { +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { return 'WebSocketState()'; } @@ -45,7 +51,7 @@ $WebSocketStateCopyWith(WebSocketState _, $Res Function(WebSocketState) __); /// @nodoc -class _Connected implements WebSocketState { +class _Connected with DiagnosticableTreeMixin implements WebSocketState { const _Connected(); @@ -53,6 +59,12 @@ class _Connected implements WebSocketState { +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'WebSocketState.connected')) + ; +} @override bool operator ==(Object other) { @@ -64,7 +76,7 @@ bool operator ==(Object other) { int get hashCode => runtimeType.hashCode; @override -String toString() { +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { return 'WebSocketState.connected()'; } @@ -77,7 +89,7 @@ String toString() { /// @nodoc -class _Connecting implements WebSocketState { +class _Connecting with DiagnosticableTreeMixin implements WebSocketState { const _Connecting(); @@ -85,6 +97,12 @@ class _Connecting implements WebSocketState { +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'WebSocketState.connecting')) + ; +} @override bool operator ==(Object other) { @@ -96,7 +114,7 @@ bool operator ==(Object other) { int get hashCode => runtimeType.hashCode; @override -String toString() { +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { return 'WebSocketState.connecting()'; } @@ -109,7 +127,7 @@ String toString() { /// @nodoc -class _Disconnected implements WebSocketState { +class _Disconnected with DiagnosticableTreeMixin implements WebSocketState { const _Disconnected(); @@ -117,6 +135,12 @@ class _Disconnected implements WebSocketState { +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'WebSocketState.disconnected')) + ; +} @override bool operator ==(Object other) { @@ -128,7 +152,7 @@ bool operator ==(Object other) { int get hashCode => runtimeType.hashCode; @override -String toString() { +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { return 'WebSocketState.disconnected()'; } @@ -141,7 +165,7 @@ String toString() { /// @nodoc -class _Error implements WebSocketState { +class _Error with DiagnosticableTreeMixin implements WebSocketState { const _Error(this.message); @@ -154,6 +178,12 @@ class _Error implements WebSocketState { _$ErrorCopyWith<_Error> get copyWith => __$ErrorCopyWithImpl<_Error>(this, _$identity); +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'WebSocketState.error')) + ..add(DiagnosticsProperty('message', message)); +} @override bool operator ==(Object other) { @@ -165,7 +195,7 @@ bool operator ==(Object other) { int get hashCode => Object.hash(runtimeType,message); @override -String toString() { +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { return 'WebSocketState.error(message: $message)'; } @@ -206,7 +236,7 @@ as String, /// @nodoc -mixin _$WebSocketPacket { +mixin _$WebSocketPacket implements DiagnosticableTreeMixin { String get type; Map? get data; String? get errorMessage; /// Create a copy of WebSocketPacket @@ -218,6 +248,12 @@ $WebSocketPacketCopyWith get copyWith => _$WebSocketPacketCopyW /// Serializes this WebSocketPacket to a JSON map. Map toJson(); +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'WebSocketPacket')) + ..add(DiagnosticsProperty('type', type))..add(DiagnosticsProperty('data', data))..add(DiagnosticsProperty('errorMessage', errorMessage)); +} @override bool operator ==(Object other) { @@ -229,7 +265,7 @@ bool operator ==(Object other) { int get hashCode => Object.hash(runtimeType,type,const DeepCollectionEquality().hash(data),errorMessage); @override -String toString() { +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { return 'WebSocketPacket(type: $type, data: $data, errorMessage: $errorMessage)'; } @@ -273,7 +309,7 @@ as String?, /// @nodoc @JsonSerializable() -class _WebSocketPacket implements WebSocketPacket { +class _WebSocketPacket with DiagnosticableTreeMixin implements WebSocketPacket { const _WebSocketPacket({required this.type, required final Map? data, required this.errorMessage}): _data = data; factory _WebSocketPacket.fromJson(Map json) => _$WebSocketPacketFromJson(json); @@ -299,6 +335,12 @@ _$WebSocketPacketCopyWith<_WebSocketPacket> get copyWith => __$WebSocketPacketCo Map toJson() { return _$WebSocketPacketToJson(this, ); } +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'WebSocketPacket')) + ..add(DiagnosticsProperty('type', type))..add(DiagnosticsProperty('data', data))..add(DiagnosticsProperty('errorMessage', errorMessage)); +} @override bool operator ==(Object other) { @@ -310,7 +352,7 @@ bool operator ==(Object other) { int get hashCode => Object.hash(runtimeType,type,const DeepCollectionEquality().hash(_data),errorMessage); @override -String toString() { +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { return 'WebSocketPacket(type: $type, data: $data, errorMessage: $errorMessage)'; } diff --git a/lib/screens/account/relationship.dart b/lib/screens/account/relationship.dart index aea5cc5..ab20dea 100644 --- a/lib/screens/account/relationship.dart +++ b/lib/screens/account/relationship.dart @@ -19,9 +19,9 @@ import 'package:island/pods/network.dart'; part 'relationship.g.dart'; @riverpod -Future> sendFriendRequest(Ref ref) async { +Future> sentFriendRequest(Ref ref) async { final client = ref.read(apiClientProvider); - final resp = await client.post('/relationships/requests'); + final resp = await client.get('/relationships/requests'); return resp.data .map((e) => SnRelationship.fromJson(e)) .cast() @@ -99,7 +99,7 @@ class RelationshipScreen extends HookConsumerWidget { final client = ref.read(apiClientProvider); await client.post('/relationships/${result.id}/friends'); - relationshipNotifier.forceRefresh(); + ref.invalidate(sentFriendRequestProvider); } final submitting = useState(false); @@ -136,7 +136,7 @@ class RelationshipScreen extends HookConsumerWidget { } final user = ref.watch(userInfoProvider); - final requests = ref.watch(sendFriendRequestProvider); + final requests = ref.watch(sentFriendRequestProvider); return AppScaffold( appBar: AppBar(title: Text('relationships').tr()), @@ -151,12 +151,19 @@ class RelationshipScreen extends HookConsumerWidget { ), if (requests.hasValue && requests.value!.isNotEmpty) ListTile( - leading: const Icon(Symbols.add), + leading: const Icon(Symbols.send), title: Text('friendSentRequest').tr(), subtitle: Text( 'friendSentRequestHint'.plural(requests.value!.length), ), contentPadding: const EdgeInsets.symmetric(horizontal: 24), + onTap: () { + showModalBottomSheet( + isScrollControlled: true, + context: context, + builder: (context) => const _SentFriendRequestsSheet(), + ); + }, ), const Divider(height: 1), Expanded( @@ -268,3 +275,125 @@ class RelationshipScreen extends HookConsumerWidget { ); } } + +class _SentFriendRequestsSheet extends HookConsumerWidget { + const _SentFriendRequestsSheet(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final requests = ref.watch(sentFriendRequestProvider); + + Future cancelRequest(SnRelationship request) async { + try { + final client = ref.read(apiClientProvider); + await client.delete('/relationships/${request.accountId}/friends'); + ref.invalidate(sentFriendRequestProvider); + } catch (err) { + showErrorAlert(err); + } + } + + return Container( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.8, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only( + top: 16, + left: 20, + right: 16, + bottom: 12, + ), + child: Row( + children: [ + Text( + 'friendSentRequest'.tr(), + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w600, + letterSpacing: -0.5, + ), + ), + const Spacer(), + IconButton( + icon: const Icon(Symbols.refresh), + style: IconButton.styleFrom(minimumSize: const Size(36, 36)), + onPressed: () { + ref.invalidate(sentFriendRequestProvider); + }, + ), + IconButton( + icon: const Icon(Symbols.close), + onPressed: () => Navigator.pop(context), + style: IconButton.styleFrom(minimumSize: const Size(36, 36)), + ), + ], + ), + ), + const Divider(height: 1), + Expanded( + child: requests.when( + data: + (items) => + items.isEmpty + ? Center( + child: Text( + 'friendSentRequestEmpty'.tr(), + textAlign: TextAlign.center, + ), + ) + : ListView.builder( + shrinkWrap: true, + itemCount: items.length, + itemBuilder: (context, index) { + final request = items[index]; + final account = request.related; + return ListTile( + leading: ProfilePictureWidget( + fileId: account.profile.pictureId, + ), + title: Text(account.nick), + subtitle: Text('@${account.name}'), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (request.expiredAt != null) + Badge( + label: Text( + 'requestExpiredIn'.tr( + args: [ + RelativeTime( + context, + ).format(request.expiredAt!), + ], + ), + ), + backgroundColor: + Theme.of( + context, + ).colorScheme.tertiary, + textColor: + Theme.of( + context, + ).colorScheme.onTertiary, + ), + IconButton( + icon: const Icon(Symbols.close), + onPressed: () => cancelRequest(request), + ), + ], + ), + ); + }, + ), + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => Center(child: Text('Error: $error')), + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/account/relationship.g.dart b/lib/screens/account/relationship.g.dart index c4b0dd9..e474d57 100644 --- a/lib/screens/account/relationship.g.dart +++ b/lib/screens/account/relationship.g.dart @@ -6,25 +6,25 @@ part of 'relationship.dart'; // RiverpodGenerator // ************************************************************************** -String _$sendFriendRequestHash() => r'0fc0a3866b64df8b547f831fdb7db47929e2c9ff'; +String _$sentFriendRequestHash() => r'cb134439280d361af585c3108fdd12543ac84130'; -/// See also [sendFriendRequest]. -@ProviderFor(sendFriendRequest) -final sendFriendRequestProvider = +/// See also [sentFriendRequest]. +@ProviderFor(sentFriendRequest) +final sentFriendRequestProvider = AutoDisposeFutureProvider>.internal( - sendFriendRequest, - name: r'sendFriendRequestProvider', + sentFriendRequest, + name: r'sentFriendRequestProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null - : _$sendFriendRequestHash, + : _$sentFriendRequestHash, dependencies: null, allTransitiveDependencies: null, ); @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element -typedef SendFriendRequestRef = +typedef SentFriendRequestRef = AutoDisposeFutureProviderRef>; String _$relationshipListNotifierHash() => r'ad352e8b10641820d5acac27b26ad1bb0b59b67f';