✨ Joindable chat, detailed realms, discovery mixed into explore
🐛 bunch of bugs fixes
This commit is contained in:
@ -434,17 +434,31 @@ class ChatListScreen extends HookConsumerWidget {
|
||||
@riverpod
|
||||
Future<SnChatRoom?> chatroom(Ref ref, String? identifier) async {
|
||||
if (identifier == null) return null;
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/chat/$identifier');
|
||||
return SnChatRoom.fromJson(resp.data);
|
||||
try {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/chat/$identifier');
|
||||
return SnChatRoom.fromJson(resp.data);
|
||||
} catch (err) {
|
||||
if (err is DioException && err.response?.statusCode == 404) {
|
||||
return null; // Chat room not found
|
||||
}
|
||||
rethrow; // Rethrow other errors
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<SnChatMember?> chatroomIdentity(Ref ref, String? identifier) async {
|
||||
if (identifier == null) return null;
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/chat/$identifier/members/me');
|
||||
return SnChatMember.fromJson(resp.data);
|
||||
try {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/chat/$identifier/members/me');
|
||||
return SnChatMember.fromJson(resp.data);
|
||||
} catch (err) {
|
||||
if (err is DioException && err.response?.statusCode == 404) {
|
||||
return null; // Chat member not found
|
||||
}
|
||||
rethrow; // Rethrow other errors
|
||||
}
|
||||
}
|
||||
|
||||
class NewChatScreen extends StatelessWidget {
|
||||
|
@ -25,7 +25,7 @@ final chatroomsJoinedProvider =
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef ChatroomsJoinedRef = AutoDisposeFutureProviderRef<List<SnChatRoom>>;
|
||||
String _$chatroomHash() => r'dce3c0fc407f178bb7c306a08b9fa545795a9205';
|
||||
String _$chatroomHash() => r'8dac7aaac50932e6dd213039102d43c1cf5f1d4e';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
@ -164,7 +164,7 @@ class _ChatroomProviderElement
|
||||
String? get identifier => (origin as ChatroomProvider).identifier;
|
||||
}
|
||||
|
||||
String _$chatroomIdentityHash() => r'4c349ea4265df7b0498cf26c82dbaabe3d868727';
|
||||
String _$chatroomIdentityHash() => r'ad6ad09b6fc4cf7c4abe146ea97f8e364a3d4fd0';
|
||||
|
||||
/// See also [chatroomIdentity].
|
||||
@ProviderFor(chatroomIdentity)
|
||||
|
@ -305,7 +305,55 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
// Identity was not found, user was not joined
|
||||
return AppScaffold(
|
||||
appBar: AppBar(leading: const PageBackButton()),
|
||||
body: Center(child: Text('You are not a member of this chat room')),
|
||||
body: Center(
|
||||
child:
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 280),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
chatRoom.value?.isCommunity == true
|
||||
? Symbols.person_add
|
||||
: Symbols.person_remove,
|
||||
size: 36,
|
||||
fill: 1,
|
||||
).padding(bottom: 4),
|
||||
Text('chatNotJoined').tr(),
|
||||
if (chatRoom.value?.isCommunity != true)
|
||||
Text(
|
||||
'chatUnableJoin',
|
||||
textAlign: TextAlign.center,
|
||||
).tr().bold()
|
||||
else
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: () async {
|
||||
try {
|
||||
showLoadingModal(context);
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
if (chatRoom.value == null) {
|
||||
hideLoadingModal(context);
|
||||
return;
|
||||
}
|
||||
|
||||
await apiClient.post(
|
||||
'/chat/${chatRoom.value!.id}/members/me',
|
||||
);
|
||||
ref.invalidate(chatroomIdentityProvider(id));
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
}
|
||||
},
|
||||
label: Text('chatJoin').tr(),
|
||||
icon: const Icon(Icons.add),
|
||||
).padding(top: 8),
|
||||
],
|
||||
),
|
||||
).center(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -443,6 +491,28 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
return () => subscription.cancel();
|
||||
}, [ws, chatRoom]);
|
||||
|
||||
useEffect(() {
|
||||
final wsState = ref.read(websocketStateProvider.notifier);
|
||||
wsState.sendMessage(
|
||||
jsonEncode(
|
||||
WebSocketPacket(
|
||||
type: 'messages.subscribe',
|
||||
data: {'chat_room_id': id},
|
||||
),
|
||||
),
|
||||
);
|
||||
return () {
|
||||
wsState.sendMessage(
|
||||
jsonEncode(
|
||||
WebSocketPacket(
|
||||
type: 'messages.unsubscribe',
|
||||
data: {'chat_room_id': id},
|
||||
),
|
||||
),
|
||||
);
|
||||
};
|
||||
}, [id]);
|
||||
|
||||
Future<void> pickPhotoMedia() async {
|
||||
final result = await ref
|
||||
.watch(imagePickerProvider)
|
||||
@ -617,7 +687,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
IconButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onPressed: () {
|
||||
context.push('/chat/id/detail');
|
||||
context.push('/chat/$id/detail');
|
||||
},
|
||||
),
|
||||
const Gap(8),
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
@ -12,13 +13,13 @@ import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/widgets/check_in.dart';
|
||||
import 'package:island/widgets/post/post_item.dart';
|
||||
import 'package:island/widgets/tour/tour.dart';
|
||||
import 'package:island/screens/tabs.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/widgets/realm/realm_card.dart';
|
||||
import 'package:island/widgets/publisher/publisher_card.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
part 'explore.g.dart';
|
||||
@ -85,65 +86,64 @@ class ExploreScreen extends HookConsumerWidget {
|
||||
activityListNotifierProvider(currentFilter.value).notifier,
|
||||
);
|
||||
|
||||
return TourTriggerWidget(
|
||||
child: AppScaffold(
|
||||
extendBody: false, // Prevent conflicts with tabs navigation
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 0,
|
||||
bottom: TabBar(
|
||||
controller: tabController,
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Text(
|
||||
'explore'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Text(
|
||||
'exploreFilterSubscriptions'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Text(
|
||||
'exploreFilterFriends'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
heroTag: Key("explore-page-fab"),
|
||||
onPressed: () {
|
||||
context.push('/posts/compose').then((value) {
|
||||
if (value != null) {
|
||||
activitiesNotifier.forceRefresh();
|
||||
}
|
||||
});
|
||||
},
|
||||
child: const Icon(Symbols.edit),
|
||||
),
|
||||
floatingActionButtonLocation: TabbedFabLocation(context),
|
||||
body: TabBarView(
|
||||
return AppScaffold(
|
||||
extendBody: false, // Prevent conflicts with tabs navigation
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 0,
|
||||
bottom: TabBar(
|
||||
controller: tabController,
|
||||
children: [
|
||||
_buildActivityList(ref, null),
|
||||
_buildActivityList(ref, 'subscriptions'),
|
||||
_buildActivityList(ref, 'friends'),
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Text(
|
||||
'explore'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Text(
|
||||
'exploreFilterSubscriptions'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Text(
|
||||
'exploreFilterFriends'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
heroTag: Key("explore-page-fab"),
|
||||
onPressed: () {
|
||||
context.push('/posts/compose').then((value) {
|
||||
if (value != null) {
|
||||
activitiesNotifier.forceRefresh();
|
||||
}
|
||||
});
|
||||
},
|
||||
child: const Icon(Symbols.edit),
|
||||
),
|
||||
floatingActionButtonLocation: TabbedFabLocation(context),
|
||||
body: TabBarView(
|
||||
controller: tabController,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
_buildActivityList(ref, null),
|
||||
_buildActivityList(ref, 'subscriptions'),
|
||||
_buildActivityList(ref, 'friends'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -180,10 +180,8 @@ class _DiscoveryActivityItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final items =
|
||||
(data['items'] as List)
|
||||
.map((e) => SnRealm.fromJson(e['data'] as Map<String, dynamic>))
|
||||
.toList();
|
||||
final items = data['items'] as List;
|
||||
final type = items.firstOrNull?['type'] ?? 'unknown';
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -194,7 +192,11 @@ class _DiscoveryActivityItem extends StatelessWidget {
|
||||
const Icon(Symbols.explore, size: 19),
|
||||
const Gap(8),
|
||||
Text(
|
||||
'discoverCommunities'.tr(),
|
||||
(switch (type) {
|
||||
'realm' => 'discoverRealms',
|
||||
'publisher' => 'discoverPublishers',
|
||||
_ => 'unknown',
|
||||
}).tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
).padding(top: 1),
|
||||
],
|
||||
@ -204,13 +206,26 @@ class _DiscoveryActivityItem extends StatelessWidget {
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: items.length,
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
itemBuilder: (context, index) {
|
||||
final realm = items[index];
|
||||
return RealmCard(realm: realm);
|
||||
final item = items[index];
|
||||
switch (type) {
|
||||
case 'realm':
|
||||
return RealmCard(
|
||||
realm: SnRealm.fromJson(item['data']),
|
||||
maxWidth: 280,
|
||||
);
|
||||
case 'publisher':
|
||||
return PublisherCard(
|
||||
publisher: SnPublisher.fromJson(item['data']),
|
||||
maxWidth: 280,
|
||||
);
|
||||
default:
|
||||
return Placeholder();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
).padding(bottom: 4),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -326,6 +341,7 @@ class ActivityListNotifier extends _$ActivityListNotifier
|
||||
if (cursor != null) 'cursor': cursor,
|
||||
'take': take,
|
||||
if (filter != null) 'filter': filter,
|
||||
if (kDebugMode) 'debugInclude': 'realms,publishers',
|
||||
};
|
||||
|
||||
final response = await client.get(
|
||||
|
@ -7,7 +7,7 @@ part of 'explore.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$activityListNotifierHash() =>
|
||||
r'14ec2f211c86e1e64a9a34b142d0e8f78ff6361a';
|
||||
r'57e9dcec944a9f88f8508b69fc91342592f5b349';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
@ -155,7 +155,7 @@ class RealmDetailScreen extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (identity == null && realm.isPublic)
|
||||
if (identity == null && realm.isCommunity)
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: () async {
|
||||
try {
|
||||
@ -169,14 +169,14 @@ class RealmDetailScreen extends HookConsumerWidget {
|
||||
realmIdentityProvider(slug),
|
||||
);
|
||||
ref.invalidate(realmsJoinedProvider);
|
||||
showSnackBar('joinRealmSuccess'.tr());
|
||||
showSnackBar('realmJoinSuccess'.tr());
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Symbols.add),
|
||||
label: const Text('joinRealm').tr(),
|
||||
).padding(horizontal: 16, vertical: 8)
|
||||
label: const Text('realmJoin').tr(),
|
||||
).padding(horizontal: 16, vertical: 16)
|
||||
else
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
|
Reference in New Issue
Block a user