From 2e37582b45554a42b86e41e28146bd29d1bc283b Mon Sep 17 00:00:00 2001
From: LittleSheep <littlesheep.code@hotmail.com>
Date: Sun, 4 May 2025 23:07:36 +0800
Subject: [PATCH] :sparkles: Realm detail page

---
 assets/i18n/en-US.json            |   5 +-
 lib/models/realm.dart             |  19 ++
 lib/models/realm.freezed.dart     | 205 +++++++++++++++
 lib/models/realm.g.dart           |  38 +++
 lib/route.dart                    |   1 +
 lib/route.gr.dart                 | 220 ++++++++++-------
 lib/screens/chat/room_detail.dart |  53 ++--
 lib/screens/realm/detail.dart     | 397 ++++++++++++++++++++++++++++++
 lib/screens/realm/detail.g.dart   | 152 ++++++++++++
 lib/screens/realm/realms.dart     |  49 +---
 10 files changed, 975 insertions(+), 164 deletions(-)
 create mode 100644 lib/screens/realm/detail.dart
 create mode 100644 lib/screens/realm/detail.g.dart

diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json
index 368732b..3d02698 100644
--- a/assets/i18n/en-US.json
+++ b/assets/i18n/en-US.json
@@ -63,7 +63,7 @@
   "createRealm": "Create a Realm",
   "createRealmHint": "Meet friends with same interests, build communities, and more.",
   "editRealm": "Edit Realm",
-  "deleteRealm": "Delete Realm {}",
+  "deleteRealm": "Delete Realm",
   "deleteRealmHint": "Are you sure to delete this realm? This will also deleted all the channels, publishers, and posts under this realm.",
   "explore": "Explore",
   "account": "Account",
@@ -74,6 +74,7 @@
   "createChatRoom": "Create a Room",
   "editChatRoom": "Edit Room",
   "deleteChatRoom": "Delete Room",
+  "deleteChatRoomHint": "Are you sure to delete this room? This action cannot be undone.",
   "chat": "Chat",
   "chatMessageHint": "Message in {}",
   "chatDirectMessageHint": "Message to {}",
@@ -81,7 +82,7 @@
   "descriptionNone": "No description yet.",
   "invites": "Invites",
   "invitesEmpty": "No invites yet, such a lonely person...",
-  "chatMembers": {
+  "members": {
     "one": "{} member",
     "other": "{} members"
   },
diff --git a/lib/models/realm.dart b/lib/models/realm.dart
index 34a6b7e..53e58bc 100644
--- a/lib/models/realm.dart
+++ b/lib/models/realm.dart
@@ -1,5 +1,6 @@
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:island/models/file.dart';
+import 'package:island/models/user.dart';
 
 part 'realm.freezed.dart';
 part 'realm.g.dart';
@@ -28,3 +29,21 @@ abstract class SnRealm with _$SnRealm {
   factory SnRealm.fromJson(Map<String, dynamic> json) =>
       _$SnRealmFromJson(json);
 }
+
+@freezed
+abstract class SnRealmMember with _$SnRealmMember {
+  const factory SnRealmMember({
+    required int realmId,
+    required SnRealm? realm,
+    required int accountId,
+    required SnAccount? account,
+    required int role,
+    required DateTime? joinedAt,
+    required DateTime createdAt,
+    required DateTime updatedAt,
+    required DateTime? deletedAt,
+  }) = _SnRealmMember;
+
+  factory SnRealmMember.fromJson(Map<String, dynamic> json) =>
+      _$SnRealmMemberFromJson(json);
+}
diff --git a/lib/models/realm.freezed.dart b/lib/models/realm.freezed.dart
index ed12b2a..a38ba3d 100644
--- a/lib/models/realm.freezed.dart
+++ b/lib/models/realm.freezed.dart
@@ -238,4 +238,209 @@ $SnCloudFileCopyWith<$Res>? get background {
 }
 }
 
+
+/// @nodoc
+mixin _$SnRealmMember {
+
+ int get realmId; SnRealm? get realm; int get accountId; SnAccount? get account; int get role; DateTime? get joinedAt; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
+/// Create a copy of SnRealmMember
+/// with the given fields replaced by the non-null parameter values.
+@JsonKey(includeFromJson: false, includeToJson: false)
+@pragma('vm:prefer-inline')
+$SnRealmMemberCopyWith<SnRealmMember> get copyWith => _$SnRealmMemberCopyWithImpl<SnRealmMember>(this as SnRealmMember, _$identity);
+
+  /// Serializes this SnRealmMember to a JSON map.
+  Map<String, dynamic> toJson();
+
+
+@override
+bool operator ==(Object other) {
+  return identical(this, other) || (other.runtimeType == runtimeType&&other is SnRealmMember&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.role, role) || other.role == role)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
+}
+
+@JsonKey(includeFromJson: false, includeToJson: false)
+@override
+int get hashCode => Object.hash(runtimeType,realmId,realm,accountId,account,role,joinedAt,createdAt,updatedAt,deletedAt);
+
+@override
+String toString() {
+  return 'SnRealmMember(realmId: $realmId, realm: $realm, accountId: $accountId, account: $account, role: $role, joinedAt: $joinedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
+}
+
+
+}
+
+/// @nodoc
+abstract mixin class $SnRealmMemberCopyWith<$Res>  {
+  factory $SnRealmMemberCopyWith(SnRealmMember value, $Res Function(SnRealmMember) _then) = _$SnRealmMemberCopyWithImpl;
+@useResult
+$Res call({
+ int realmId, SnRealm? realm, int accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
+});
+
+
+$SnRealmCopyWith<$Res>? get realm;$SnAccountCopyWith<$Res>? get account;
+
+}
+/// @nodoc
+class _$SnRealmMemberCopyWithImpl<$Res>
+    implements $SnRealmMemberCopyWith<$Res> {
+  _$SnRealmMemberCopyWithImpl(this._self, this._then);
+
+  final SnRealmMember _self;
+  final $Res Function(SnRealmMember) _then;
+
+/// Create a copy of SnRealmMember
+/// with the given fields replaced by the non-null parameter values.
+@pragma('vm:prefer-inline') @override $Res call({Object? realmId = null,Object? realm = freezed,Object? accountId = null,Object? account = freezed,Object? role = null,Object? joinedAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
+  return _then(_self.copyWith(
+realmId: null == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
+as int,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
+as SnRealm?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
+as int,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
+as SnAccount?,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable
+as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
+as DateTime?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
+as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
+as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
+as DateTime?,
+  ));
+}
+/// Create a copy of SnRealmMember
+/// with the given fields replaced by the non-null parameter values.
+@override
+@pragma('vm:prefer-inline')
+$SnRealmCopyWith<$Res>? get realm {
+    if (_self.realm == null) {
+    return null;
+  }
+
+  return $SnRealmCopyWith<$Res>(_self.realm!, (value) {
+    return _then(_self.copyWith(realm: value));
+  });
+}/// Create a copy of SnRealmMember
+/// with the given fields replaced by the non-null parameter values.
+@override
+@pragma('vm:prefer-inline')
+$SnAccountCopyWith<$Res>? get account {
+    if (_self.account == null) {
+    return null;
+  }
+
+  return $SnAccountCopyWith<$Res>(_self.account!, (value) {
+    return _then(_self.copyWith(account: value));
+  });
+}
+}
+
+
+/// @nodoc
+@JsonSerializable()
+
+class _SnRealmMember implements SnRealmMember {
+  const _SnRealmMember({required this.realmId, required this.realm, required this.accountId, required this.account, required this.role, required this.joinedAt, required this.createdAt, required this.updatedAt, required this.deletedAt});
+  factory _SnRealmMember.fromJson(Map<String, dynamic> json) => _$SnRealmMemberFromJson(json);
+
+@override final  int realmId;
+@override final  SnRealm? realm;
+@override final  int accountId;
+@override final  SnAccount? account;
+@override final  int role;
+@override final  DateTime? joinedAt;
+@override final  DateTime createdAt;
+@override final  DateTime updatedAt;
+@override final  DateTime? deletedAt;
+
+/// Create a copy of SnRealmMember
+/// with the given fields replaced by the non-null parameter values.
+@override @JsonKey(includeFromJson: false, includeToJson: false)
+@pragma('vm:prefer-inline')
+_$SnRealmMemberCopyWith<_SnRealmMember> get copyWith => __$SnRealmMemberCopyWithImpl<_SnRealmMember>(this, _$identity);
+
+@override
+Map<String, dynamic> toJson() {
+  return _$SnRealmMemberToJson(this, );
+}
+
+@override
+bool operator ==(Object other) {
+  return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnRealmMember&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.role, role) || other.role == role)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
+}
+
+@JsonKey(includeFromJson: false, includeToJson: false)
+@override
+int get hashCode => Object.hash(runtimeType,realmId,realm,accountId,account,role,joinedAt,createdAt,updatedAt,deletedAt);
+
+@override
+String toString() {
+  return 'SnRealmMember(realmId: $realmId, realm: $realm, accountId: $accountId, account: $account, role: $role, joinedAt: $joinedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
+}
+
+
+}
+
+/// @nodoc
+abstract mixin class _$SnRealmMemberCopyWith<$Res> implements $SnRealmMemberCopyWith<$Res> {
+  factory _$SnRealmMemberCopyWith(_SnRealmMember value, $Res Function(_SnRealmMember) _then) = __$SnRealmMemberCopyWithImpl;
+@override @useResult
+$Res call({
+ int realmId, SnRealm? realm, int accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
+});
+
+
+@override $SnRealmCopyWith<$Res>? get realm;@override $SnAccountCopyWith<$Res>? get account;
+
+}
+/// @nodoc
+class __$SnRealmMemberCopyWithImpl<$Res>
+    implements _$SnRealmMemberCopyWith<$Res> {
+  __$SnRealmMemberCopyWithImpl(this._self, this._then);
+
+  final _SnRealmMember _self;
+  final $Res Function(_SnRealmMember) _then;
+
+/// Create a copy of SnRealmMember
+/// with the given fields replaced by the non-null parameter values.
+@override @pragma('vm:prefer-inline') $Res call({Object? realmId = null,Object? realm = freezed,Object? accountId = null,Object? account = freezed,Object? role = null,Object? joinedAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
+  return _then(_SnRealmMember(
+realmId: null == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
+as int,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
+as SnRealm?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
+as int,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
+as SnAccount?,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable
+as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
+as DateTime?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
+as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
+as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
+as DateTime?,
+  ));
+}
+
+/// Create a copy of SnRealmMember
+/// with the given fields replaced by the non-null parameter values.
+@override
+@pragma('vm:prefer-inline')
+$SnRealmCopyWith<$Res>? get realm {
+    if (_self.realm == null) {
+    return null;
+  }
+
+  return $SnRealmCopyWith<$Res>(_self.realm!, (value) {
+    return _then(_self.copyWith(realm: value));
+  });
+}/// Create a copy of SnRealmMember
+/// with the given fields replaced by the non-null parameter values.
+@override
+@pragma('vm:prefer-inline')
+$SnAccountCopyWith<$Res>? get account {
+    if (_self.account == null) {
+    return null;
+  }
+
+  return $SnAccountCopyWith<$Res>(_self.account!, (value) {
+    return _then(_self.copyWith(account: value));
+  });
+}
+}
+
 // dart format on
diff --git a/lib/models/realm.g.dart b/lib/models/realm.g.dart
index 9b3834f..2085358 100644
--- a/lib/models/realm.g.dart
+++ b/lib/models/realm.g.dart
@@ -55,3 +55,41 @@ Map<String, dynamic> _$SnRealmToJson(_SnRealm instance) => <String, dynamic>{
   'updated_at': instance.updatedAt.toIso8601String(),
   'deleted_at': instance.deletedAt?.toIso8601String(),
 };
+
+_SnRealmMember _$SnRealmMemberFromJson(Map<String, dynamic> json) =>
+    _SnRealmMember(
+      realmId: (json['realm_id'] as num).toInt(),
+      realm:
+          json['realm'] == null
+              ? null
+              : SnRealm.fromJson(json['realm'] as Map<String, dynamic>),
+      accountId: (json['account_id'] as num).toInt(),
+      account:
+          json['account'] == null
+              ? null
+              : SnAccount.fromJson(json['account'] as Map<String, dynamic>),
+      role: (json['role'] as num).toInt(),
+      joinedAt:
+          json['joined_at'] == null
+              ? null
+              : DateTime.parse(json['joined_at'] as String),
+      createdAt: DateTime.parse(json['created_at'] as String),
+      updatedAt: DateTime.parse(json['updated_at'] as String),
+      deletedAt:
+          json['deleted_at'] == null
+              ? null
+              : DateTime.parse(json['deleted_at'] as String),
+    );
+
+Map<String, dynamic> _$SnRealmMemberToJson(_SnRealmMember instance) =>
+    <String, dynamic>{
+      'realm_id': instance.realmId,
+      'realm': instance.realm?.toJson(),
+      'account_id': instance.accountId,
+      'account': instance.account?.toJson(),
+      'role': instance.role,
+      'joined_at': instance.joinedAt?.toIso8601String(),
+      'created_at': instance.createdAt.toIso8601String(),
+      'updated_at': instance.updatedAt.toIso8601String(),
+      'deleted_at': instance.deletedAt?.toIso8601String(),
+    };
diff --git a/lib/route.dart b/lib/route.dart
index 12ae600..dc56694 100644
--- a/lib/route.dart
+++ b/lib/route.dart
@@ -33,6 +33,7 @@ class AppRouter extends RootStackRouter {
     AutoRoute(page: PostDetailRoute.page, path: '/posts/:id'),
     AutoRoute(page: PostEditRoute.page, path: '/posts/:id/edit'),
     AutoRoute(page: NewRealmRoute.page, path: '/realms/new'),
+    AutoRoute(page: RealmDetailRoute.page, path: '/realms/:slug'),
     AutoRoute(page: EditRealmRoute.page, path: '/realms/:slug/edit'),
     AutoRoute(page: NewChatRoute.page, path: '/chat/new'),
     AutoRoute(page: EditChatRoute.page, path: '/chat/:id/edit'),
diff --git a/lib/route.gr.dart b/lib/route.gr.dart
index ec049b8..40ccf8c 100644
--- a/lib/route.gr.dart
+++ b/lib/route.gr.dart
@@ -9,33 +9,34 @@
 // coverage:ignore-file
 
 // ignore_for_file: no_leading_underscores_for_library_prefixes
-import 'package:auto_route/auto_route.dart' as _i15;
-import 'package:flutter/material.dart' as _i16;
-import 'package:island/models/post.dart' as _i17;
+import 'package:auto_route/auto_route.dart' as _i16;
+import 'package:flutter/material.dart' as _i17;
+import 'package:island/models/post.dart' as _i18;
 import 'package:island/screens/account.dart' as _i1;
 import 'package:island/screens/account/me.dart' as _i10;
 import 'package:island/screens/account/me/publishers.dart' as _i6;
-import 'package:island/screens/account/me/update.dart' as _i14;
+import 'package:island/screens/account/me/update.dart' as _i15;
 import 'package:island/screens/auth/create_account.dart' as _i5;
 import 'package:island/screens/auth/login.dart' as _i9;
-import 'package:island/screens/auth/tabs.dart' as _i13;
+import 'package:island/screens/auth/tabs.dart' as _i14;
 import 'package:island/screens/chat/chat.dart' as _i3;
 import 'package:island/screens/chat/room.dart' as _i4;
 import 'package:island/screens/chat/room_detail.dart' as _i2;
 import 'package:island/screens/explore.dart' as _i8;
 import 'package:island/screens/posts/compose.dart' as _i11;
 import 'package:island/screens/posts/detail.dart' as _i12;
+import 'package:island/screens/realm/detail.dart' as _i13;
 import 'package:island/screens/realm/realms.dart' as _i7;
 
 /// generated route for
 /// [_i1.AccountScreen]
-class AccountRoute extends _i15.PageRouteInfo<void> {
-  const AccountRoute({List<_i15.PageRouteInfo>? children})
+class AccountRoute extends _i16.PageRouteInfo<void> {
+  const AccountRoute({List<_i16.PageRouteInfo>? children})
     : super(AccountRoute.name, initialChildren: children);
 
   static const String name = 'AccountRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       return const _i1.AccountScreen();
@@ -45,11 +46,11 @@ class AccountRoute extends _i15.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i2.ChatDetailScreen]
-class ChatDetailRoute extends _i15.PageRouteInfo<ChatDetailRouteArgs> {
+class ChatDetailRoute extends _i16.PageRouteInfo<ChatDetailRouteArgs> {
   ChatDetailRoute({
-    _i16.Key? key,
+    _i17.Key? key,
     required int id,
-    List<_i15.PageRouteInfo>? children,
+    List<_i16.PageRouteInfo>? children,
   }) : super(
          ChatDetailRoute.name,
          args: ChatDetailRouteArgs(key: key, id: id),
@@ -59,7 +60,7 @@ class ChatDetailRoute extends _i15.PageRouteInfo<ChatDetailRouteArgs> {
 
   static const String name = 'ChatDetailRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       final pathParams = data.inheritedPathParams;
@@ -74,7 +75,7 @@ class ChatDetailRoute extends _i15.PageRouteInfo<ChatDetailRouteArgs> {
 class ChatDetailRouteArgs {
   const ChatDetailRouteArgs({this.key, required this.id});
 
-  final _i16.Key? key;
+  final _i17.Key? key;
 
   final int id;
 
@@ -86,13 +87,13 @@ class ChatDetailRouteArgs {
 
 /// generated route for
 /// [_i3.ChatListScreen]
-class ChatListRoute extends _i15.PageRouteInfo<void> {
-  const ChatListRoute({List<_i15.PageRouteInfo>? children})
+class ChatListRoute extends _i16.PageRouteInfo<void> {
+  const ChatListRoute({List<_i16.PageRouteInfo>? children})
     : super(ChatListRoute.name, initialChildren: children);
 
   static const String name = 'ChatListRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       return const _i3.ChatListScreen();
@@ -102,11 +103,11 @@ class ChatListRoute extends _i15.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i4.ChatRoomScreen]
-class ChatRoomRoute extends _i15.PageRouteInfo<ChatRoomRouteArgs> {
+class ChatRoomRoute extends _i16.PageRouteInfo<ChatRoomRouteArgs> {
   ChatRoomRoute({
-    _i16.Key? key,
+    _i17.Key? key,
     required int id,
-    List<_i15.PageRouteInfo>? children,
+    List<_i16.PageRouteInfo>? children,
   }) : super(
          ChatRoomRoute.name,
          args: ChatRoomRouteArgs(key: key, id: id),
@@ -116,7 +117,7 @@ class ChatRoomRoute extends _i15.PageRouteInfo<ChatRoomRouteArgs> {
 
   static const String name = 'ChatRoomRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       final pathParams = data.inheritedPathParams;
@@ -131,7 +132,7 @@ class ChatRoomRoute extends _i15.PageRouteInfo<ChatRoomRouteArgs> {
 class ChatRoomRouteArgs {
   const ChatRoomRouteArgs({this.key, required this.id});
 
-  final _i16.Key? key;
+  final _i17.Key? key;
 
   final int id;
 
@@ -143,13 +144,13 @@ class ChatRoomRouteArgs {
 
 /// generated route for
 /// [_i5.CreateAccountScreen]
-class CreateAccountRoute extends _i15.PageRouteInfo<void> {
-  const CreateAccountRoute({List<_i15.PageRouteInfo>? children})
+class CreateAccountRoute extends _i16.PageRouteInfo<void> {
+  const CreateAccountRoute({List<_i16.PageRouteInfo>? children})
     : super(CreateAccountRoute.name, initialChildren: children);
 
   static const String name = 'CreateAccountRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       return const _i5.CreateAccountScreen();
@@ -159,8 +160,8 @@ class CreateAccountRoute extends _i15.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i3.EditChatScreen]
-class EditChatRoute extends _i15.PageRouteInfo<EditChatRouteArgs> {
-  EditChatRoute({_i16.Key? key, int? id, List<_i15.PageRouteInfo>? children})
+class EditChatRoute extends _i16.PageRouteInfo<EditChatRouteArgs> {
+  EditChatRoute({_i17.Key? key, int? id, List<_i16.PageRouteInfo>? children})
     : super(
         EditChatRoute.name,
         args: EditChatRouteArgs(key: key, id: id),
@@ -170,7 +171,7 @@ class EditChatRoute extends _i15.PageRouteInfo<EditChatRouteArgs> {
 
   static const String name = 'EditChatRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       final pathParams = data.inheritedPathParams;
@@ -185,7 +186,7 @@ class EditChatRoute extends _i15.PageRouteInfo<EditChatRouteArgs> {
 class EditChatRouteArgs {
   const EditChatRouteArgs({this.key, this.id});
 
-  final _i16.Key? key;
+  final _i17.Key? key;
 
   final int? id;
 
@@ -197,11 +198,11 @@ class EditChatRouteArgs {
 
 /// generated route for
 /// [_i6.EditPublisherScreen]
-class EditPublisherRoute extends _i15.PageRouteInfo<EditPublisherRouteArgs> {
+class EditPublisherRoute extends _i16.PageRouteInfo<EditPublisherRouteArgs> {
   EditPublisherRoute({
-    _i16.Key? key,
+    _i17.Key? key,
     String? name,
-    List<_i15.PageRouteInfo>? children,
+    List<_i16.PageRouteInfo>? children,
   }) : super(
          EditPublisherRoute.name,
          args: EditPublisherRouteArgs(key: key, name: name),
@@ -211,7 +212,7 @@ class EditPublisherRoute extends _i15.PageRouteInfo<EditPublisherRouteArgs> {
 
   static const String name = 'EditPublisherRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       final pathParams = data.inheritedPathParams;
@@ -226,7 +227,7 @@ class EditPublisherRoute extends _i15.PageRouteInfo<EditPublisherRouteArgs> {
 class EditPublisherRouteArgs {
   const EditPublisherRouteArgs({this.key, this.name});
 
-  final _i16.Key? key;
+  final _i17.Key? key;
 
   final String? name;
 
@@ -238,11 +239,11 @@ class EditPublisherRouteArgs {
 
 /// generated route for
 /// [_i7.EditRealmScreen]
-class EditRealmRoute extends _i15.PageRouteInfo<EditRealmRouteArgs> {
+class EditRealmRoute extends _i16.PageRouteInfo<EditRealmRouteArgs> {
   EditRealmRoute({
-    _i16.Key? key,
+    _i17.Key? key,
     String? slug,
-    List<_i15.PageRouteInfo>? children,
+    List<_i16.PageRouteInfo>? children,
   }) : super(
          EditRealmRoute.name,
          args: EditRealmRouteArgs(key: key, slug: slug),
@@ -252,7 +253,7 @@ class EditRealmRoute extends _i15.PageRouteInfo<EditRealmRouteArgs> {
 
   static const String name = 'EditRealmRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       final pathParams = data.inheritedPathParams;
@@ -267,7 +268,7 @@ class EditRealmRoute extends _i15.PageRouteInfo<EditRealmRouteArgs> {
 class EditRealmRouteArgs {
   const EditRealmRouteArgs({this.key, this.slug});
 
-  final _i16.Key? key;
+  final _i17.Key? key;
 
   final String? slug;
 
@@ -279,13 +280,13 @@ class EditRealmRouteArgs {
 
 /// generated route for
 /// [_i8.ExploreScreen]
-class ExploreRoute extends _i15.PageRouteInfo<void> {
-  const ExploreRoute({List<_i15.PageRouteInfo>? children})
+class ExploreRoute extends _i16.PageRouteInfo<void> {
+  const ExploreRoute({List<_i16.PageRouteInfo>? children})
     : super(ExploreRoute.name, initialChildren: children);
 
   static const String name = 'ExploreRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       return const _i8.ExploreScreen();
@@ -295,13 +296,13 @@ class ExploreRoute extends _i15.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i9.LoginScreen]
-class LoginRoute extends _i15.PageRouteInfo<void> {
-  const LoginRoute({List<_i15.PageRouteInfo>? children})
+class LoginRoute extends _i16.PageRouteInfo<void> {
+  const LoginRoute({List<_i16.PageRouteInfo>? children})
     : super(LoginRoute.name, initialChildren: children);
 
   static const String name = 'LoginRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       return const _i9.LoginScreen();
@@ -311,13 +312,13 @@ class LoginRoute extends _i15.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i6.ManagedPublisherScreen]
-class ManagedPublisherRoute extends _i15.PageRouteInfo<void> {
-  const ManagedPublisherRoute({List<_i15.PageRouteInfo>? children})
+class ManagedPublisherRoute extends _i16.PageRouteInfo<void> {
+  const ManagedPublisherRoute({List<_i16.PageRouteInfo>? children})
     : super(ManagedPublisherRoute.name, initialChildren: children);
 
   static const String name = 'ManagedPublisherRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       return const _i6.ManagedPublisherScreen();
@@ -327,13 +328,13 @@ class ManagedPublisherRoute extends _i15.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i10.MyselfProfileScreen]
-class MyselfProfileRoute extends _i15.PageRouteInfo<void> {
-  const MyselfProfileRoute({List<_i15.PageRouteInfo>? children})
+class MyselfProfileRoute extends _i16.PageRouteInfo<void> {
+  const MyselfProfileRoute({List<_i16.PageRouteInfo>? children})
     : super(MyselfProfileRoute.name, initialChildren: children);
 
   static const String name = 'MyselfProfileRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       return const _i10.MyselfProfileScreen();
@@ -343,13 +344,13 @@ class MyselfProfileRoute extends _i15.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i3.NewChatScreen]
-class NewChatRoute extends _i15.PageRouteInfo<void> {
-  const NewChatRoute({List<_i15.PageRouteInfo>? children})
+class NewChatRoute extends _i16.PageRouteInfo<void> {
+  const NewChatRoute({List<_i16.PageRouteInfo>? children})
     : super(NewChatRoute.name, initialChildren: children);
 
   static const String name = 'NewChatRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       return const _i3.NewChatScreen();
@@ -359,13 +360,13 @@ class NewChatRoute extends _i15.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i6.NewPublisherScreen]
-class NewPublisherRoute extends _i15.PageRouteInfo<void> {
-  const NewPublisherRoute({List<_i15.PageRouteInfo>? children})
+class NewPublisherRoute extends _i16.PageRouteInfo<void> {
+  const NewPublisherRoute({List<_i16.PageRouteInfo>? children})
     : super(NewPublisherRoute.name, initialChildren: children);
 
   static const String name = 'NewPublisherRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       return const _i6.NewPublisherScreen();
@@ -375,13 +376,13 @@ class NewPublisherRoute extends _i15.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i7.NewRealmScreen]
-class NewRealmRoute extends _i15.PageRouteInfo<void> {
-  const NewRealmRoute({List<_i15.PageRouteInfo>? children})
+class NewRealmRoute extends _i16.PageRouteInfo<void> {
+  const NewRealmRoute({List<_i16.PageRouteInfo>? children})
     : super(NewRealmRoute.name, initialChildren: children);
 
   static const String name = 'NewRealmRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       return const _i7.NewRealmScreen();
@@ -391,11 +392,11 @@ class NewRealmRoute extends _i15.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i11.PostComposeScreen]
-class PostComposeRoute extends _i15.PageRouteInfo<PostComposeRouteArgs> {
+class PostComposeRoute extends _i16.PageRouteInfo<PostComposeRouteArgs> {
   PostComposeRoute({
-    _i16.Key? key,
-    _i17.SnPost? originalPost,
-    List<_i15.PageRouteInfo>? children,
+    _i17.Key? key,
+    _i18.SnPost? originalPost,
+    List<_i16.PageRouteInfo>? children,
   }) : super(
          PostComposeRoute.name,
          args: PostComposeRouteArgs(key: key, originalPost: originalPost),
@@ -404,7 +405,7 @@ class PostComposeRoute extends _i15.PageRouteInfo<PostComposeRouteArgs> {
 
   static const String name = 'PostComposeRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       final args = data.argsAs<PostComposeRouteArgs>(
@@ -421,9 +422,9 @@ class PostComposeRoute extends _i15.PageRouteInfo<PostComposeRouteArgs> {
 class PostComposeRouteArgs {
   const PostComposeRouteArgs({this.key, this.originalPost});
 
-  final _i16.Key? key;
+  final _i17.Key? key;
 
-  final _i17.SnPost? originalPost;
+  final _i18.SnPost? originalPost;
 
   @override
   String toString() {
@@ -433,11 +434,11 @@ class PostComposeRouteArgs {
 
 /// generated route for
 /// [_i12.PostDetailScreen]
-class PostDetailRoute extends _i15.PageRouteInfo<PostDetailRouteArgs> {
+class PostDetailRoute extends _i16.PageRouteInfo<PostDetailRouteArgs> {
   PostDetailRoute({
-    _i16.Key? key,
+    _i17.Key? key,
     required int id,
-    List<_i15.PageRouteInfo>? children,
+    List<_i16.PageRouteInfo>? children,
   }) : super(
          PostDetailRoute.name,
          args: PostDetailRouteArgs(key: key, id: id),
@@ -447,7 +448,7 @@ class PostDetailRoute extends _i15.PageRouteInfo<PostDetailRouteArgs> {
 
   static const String name = 'PostDetailRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       final pathParams = data.inheritedPathParams;
@@ -462,7 +463,7 @@ class PostDetailRoute extends _i15.PageRouteInfo<PostDetailRouteArgs> {
 class PostDetailRouteArgs {
   const PostDetailRouteArgs({this.key, required this.id});
 
-  final _i16.Key? key;
+  final _i17.Key? key;
 
   final int id;
 
@@ -474,11 +475,11 @@ class PostDetailRouteArgs {
 
 /// generated route for
 /// [_i11.PostEditScreen]
-class PostEditRoute extends _i15.PageRouteInfo<PostEditRouteArgs> {
+class PostEditRoute extends _i16.PageRouteInfo<PostEditRouteArgs> {
   PostEditRoute({
-    _i16.Key? key,
+    _i17.Key? key,
     required int id,
-    List<_i15.PageRouteInfo>? children,
+    List<_i16.PageRouteInfo>? children,
   }) : super(
          PostEditRoute.name,
          args: PostEditRouteArgs(key: key, id: id),
@@ -488,7 +489,7 @@ class PostEditRoute extends _i15.PageRouteInfo<PostEditRouteArgs> {
 
   static const String name = 'PostEditRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       final pathParams = data.inheritedPathParams;
@@ -503,7 +504,7 @@ class PostEditRoute extends _i15.PageRouteInfo<PostEditRouteArgs> {
 class PostEditRouteArgs {
   const PostEditRouteArgs({this.key, required this.id});
 
-  final _i16.Key? key;
+  final _i17.Key? key;
 
   final int id;
 
@@ -513,15 +514,56 @@ class PostEditRouteArgs {
   }
 }
 
+/// generated route for
+/// [_i13.RealmDetailScreen]
+class RealmDetailRoute extends _i16.PageRouteInfo<RealmDetailRouteArgs> {
+  RealmDetailRoute({
+    _i17.Key? key,
+    required String slug,
+    List<_i16.PageRouteInfo>? children,
+  }) : super(
+         RealmDetailRoute.name,
+         args: RealmDetailRouteArgs(key: key, slug: slug),
+         rawPathParams: {'slug': slug},
+         initialChildren: children,
+       );
+
+  static const String name = 'RealmDetailRoute';
+
+  static _i16.PageInfo page = _i16.PageInfo(
+    name,
+    builder: (data) {
+      final pathParams = data.inheritedPathParams;
+      final args = data.argsAs<RealmDetailRouteArgs>(
+        orElse: () => RealmDetailRouteArgs(slug: pathParams.getString('slug')),
+      );
+      return _i13.RealmDetailScreen(key: args.key, slug: args.slug);
+    },
+  );
+}
+
+class RealmDetailRouteArgs {
+  const RealmDetailRouteArgs({this.key, required this.slug});
+
+  final _i17.Key? key;
+
+  final String slug;
+
+  @override
+  String toString() {
+    return 'RealmDetailRouteArgs{key: $key, slug: $slug}';
+  }
+}
+
 /// generated route for
 /// [_i7.RealmListScreen]
-class RealmListRoute extends _i15.PageRouteInfo<void> {
-  const RealmListRoute({List<_i15.PageRouteInfo>? children})
+class RealmListRoute extends _i16.PageRouteInfo<void> {
+  const RealmListRoute({List<_i16.PageRouteInfo>? children})
     : super(RealmListRoute.name, initialChildren: children);
 
   static const String name = 'RealmListRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
       return const _i7.RealmListScreen();
@@ -530,33 +572,33 @@ class RealmListRoute extends _i15.PageRouteInfo<void> {
 }
 
 /// generated route for
-/// [_i13.TabsScreen]
-class TabsRoute extends _i15.PageRouteInfo<void> {
-  const TabsRoute({List<_i15.PageRouteInfo>? children})
+/// [_i14.TabsScreen]
+class TabsRoute extends _i16.PageRouteInfo<void> {
+  const TabsRoute({List<_i16.PageRouteInfo>? children})
     : super(TabsRoute.name, initialChildren: children);
 
   static const String name = 'TabsRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
-      return const _i13.TabsScreen();
+      return const _i14.TabsScreen();
     },
   );
 }
 
 /// generated route for
-/// [_i14.UpdateProfileScreen]
-class UpdateProfileRoute extends _i15.PageRouteInfo<void> {
-  const UpdateProfileRoute({List<_i15.PageRouteInfo>? children})
+/// [_i15.UpdateProfileScreen]
+class UpdateProfileRoute extends _i16.PageRouteInfo<void> {
+  const UpdateProfileRoute({List<_i16.PageRouteInfo>? children})
     : super(UpdateProfileRoute.name, initialChildren: children);
 
   static const String name = 'UpdateProfileRoute';
 
-  static _i15.PageInfo page = _i15.PageInfo(
+  static _i16.PageInfo page = _i16.PageInfo(
     name,
     builder: (data) {
-      return const _i14.UpdateProfileScreen();
+      return const _i15.UpdateProfileScreen();
     },
   );
 }
diff --git a/lib/screens/chat/room_detail.dart b/lib/screens/chat/room_detail.dart
index ca13643..8159c9e 100644
--- a/lib/screens/chat/room_detail.dart
+++ b/lib/screens/chat/room_detail.dart
@@ -85,7 +85,11 @@ class ChatDetailScreen extends HookConsumerWidget {
                       currentRoom.type == 1
                           ? currentRoom.members!.first.account.nick
                           : currentRoom.name,
-                    ).textColor(Theme.of(context).appBarTheme.foregroundColor),
+                      style: TextStyle(
+                        color: Theme.of(context).appBarTheme.foregroundColor,
+                        shadows: [iconShadow],
+                      ),
+                    ),
                   ),
                   actions: [
                     IconButton(
@@ -110,7 +114,7 @@ class ChatDetailScreen extends HookConsumerWidget {
                       crossAxisAlignment: CrossAxisAlignment.start,
                       children: [
                         Text(
-                          currentRoom?.description ?? 'descriptionNone'.tr(),
+                          currentRoom.description,
                           style: const TextStyle(fontSize: 16),
                         ),
                       ],
@@ -124,14 +128,14 @@ class ChatDetailScreen extends HookConsumerWidget {
   }
 }
 
-class _ChatRoomActionMenu extends StatelessWidget {
+class _ChatRoomActionMenu extends HookConsumerWidget {
   final int id;
   final Shadow iconShadow;
 
   const _ChatRoomActionMenu({required this.id, required this.iconShadow});
 
   @override
-  Widget build(BuildContext context) {
+  Widget build(BuildContext context, WidgetRef ref) {
     return PopupMenuButton(
       icon: Icon(Icons.more_vert, shadows: [iconShadow]),
       itemBuilder:
@@ -163,30 +167,21 @@ class _ChatRoomActionMenu extends StatelessWidget {
                 ],
               ),
               onTap: () {
-                Navigator.pop(context);
-                showDialog(
-                  context: context,
-                  builder:
-                      (context) => AlertDialog(
-                        title: const Text('Delete Room'),
-                        content: const Text(
-                          'Are you sure you want to delete this room? This action cannot be undone.',
-                        ),
-                        actions: [
-                          TextButton(
-                            child: const Text('Cancel'),
-                            onPressed: () => Navigator.pop(context),
-                          ),
-                          TextButton(
-                            child: const Text(
-                              'Delete',
-                              style: TextStyle(color: Colors.red),
-                            ),
-                            onPressed: () async {},
-                          ),
-                        ],
-                      ),
-                );
+                showConfirmAlert(
+                  'deleteChatRoomHint'.tr(),
+                  'deleteChatRoom'.tr(),
+                ).then((confirm) {
+                  if (confirm) {
+                    final client = ref.watch(apiClientProvider);
+                    client.delete('/chat/$id');
+                    ref.invalidate(chatroomsJoinedProvider);
+                    if (context.mounted) {
+                      context.router.popUntil(
+                        (route) => route is ChatRoomRoute,
+                      );
+                    }
+                  }
+                });
               },
             ),
           ],
@@ -304,7 +299,7 @@ class _ChatMemberListSheet extends HookConsumerWidget {
               child: Row(
                 children: [
                   Text(
-                    'chatMembers'.plural(memberState.total),
+                    'members'.plural(memberState.total),
                     style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                       fontWeight: FontWeight.w600,
                       letterSpacing: -0.5,
diff --git a/lib/screens/realm/detail.dart b/lib/screens/realm/detail.dart
new file mode 100644
index 0000000..56dcc00
--- /dev/null
+++ b/lib/screens/realm/detail.dart
@@ -0,0 +1,397 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:dio/dio.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:gap/gap.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:island/models/realm.dart';
+import 'package:island/pods/network.dart';
+import 'package:island/route.gr.dart';
+import 'package:island/screens/realm/realms.dart';
+import 'package:island/widgets/account/account_picker.dart';
+import 'package:island/widgets/alert.dart';
+import 'package:island/widgets/app_scaffold.dart';
+import 'package:island/widgets/content/cloud_files.dart';
+import 'package:material_symbols_icons/symbols.dart';
+import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
+import 'package:riverpod_annotation/riverpod_annotation.dart';
+import 'package:styled_widget/styled_widget.dart';
+
+part 'detail.g.dart';
+
+@riverpod
+Future<SnRealmMember?> realmIdentity(Ref ref, String realmSlug) async {
+  final apiClient = ref.watch(apiClientProvider);
+  final response = await apiClient.get('/realms/$realmSlug/members/me');
+  return SnRealmMember.fromJson(response.data);
+}
+
+@RoutePage()
+class RealmDetailScreen extends HookConsumerWidget {
+  final String slug;
+  const RealmDetailScreen({super.key, @PathParam("slug") required this.slug});
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final realmState = ref.watch(realmProvider(slug));
+    final realmIdentity = ref.watch(realmIdentityProvider(slug));
+
+    final isModerator = realmIdentity.when(
+      loading: () => false,
+      error: (error, _) => false,
+      data: (identity) => (identity?.role ?? 0) >= 50,
+    );
+
+    const iconShadow = Shadow(
+      color: Colors.black54,
+      blurRadius: 5.0,
+      offset: Offset(1.0, 1.0),
+    );
+
+    return AppScaffold(
+      body: realmState.when(
+        loading: () => const Center(child: CircularProgressIndicator()),
+        error: (error, _) => Center(child: Text('Error: $error')),
+        data:
+            (realm) => CustomScrollView(
+              slivers: [
+                SliverAppBar(
+                  expandedHeight: 180,
+                  pinned: true,
+                  leading: PageBackButton(shadows: [iconShadow]),
+                  flexibleSpace: FlexibleSpaceBar(
+                    background:
+                        realm!.backgroundId != null
+                            ? CloudImageWidget(fileId: realm.backgroundId!)
+                            : Container(
+                              color:
+                                  Theme.of(context).appBarTheme.backgroundColor,
+                            ),
+                    title: Text(
+                      realm.name,
+                      style: TextStyle(
+                        color: Theme.of(context).appBarTheme.foregroundColor,
+                        shadows: [iconShadow],
+                      ),
+                    ),
+                  ),
+                  actions: [
+                    IconButton(
+                      icon: const Icon(Icons.people, shadows: [iconShadow]),
+                      onPressed: () {
+                        showCupertinoModalBottomSheet(
+                          context: context,
+                          builder:
+                              (context) =>
+                                  _RealmMemberListSheet(realmSlug: slug),
+                        );
+                      },
+                    ),
+                    if (isModerator)
+                      _RealmActionMenu(realmSlug: slug, iconShadow: iconShadow),
+                    const Gap(8),
+                  ],
+                ),
+                SliverToBoxAdapter(
+                  child: Padding(
+                    padding: const EdgeInsets.all(16.0),
+                    child: Column(
+                      crossAxisAlignment: CrossAxisAlignment.start,
+                      children: [
+                        Text(
+                          realm.description,
+                          style: const TextStyle(fontSize: 16),
+                        ),
+                      ],
+                    ),
+                  ),
+                ),
+              ],
+            ),
+      ),
+    );
+  }
+}
+
+class _RealmActionMenu extends HookConsumerWidget {
+  final String realmSlug;
+  final Shadow iconShadow;
+
+  const _RealmActionMenu({required this.realmSlug, required this.iconShadow});
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    return PopupMenuButton(
+      icon: Icon(Icons.more_vert, shadows: [iconShadow]),
+      itemBuilder:
+          (context) => [
+            PopupMenuItem(
+              onTap: () {
+                context.router.replace(EditRealmRoute(slug: realmSlug));
+              },
+              child: Row(
+                children: [
+                  Icon(
+                    Icons.edit,
+                    color: Theme.of(context).colorScheme.onSecondaryContainer,
+                  ),
+                  const Gap(12),
+                  const Text('editRealm').tr(),
+                ],
+              ),
+            ),
+            PopupMenuItem(
+              child: Row(
+                children: [
+                  const Icon(Icons.delete, color: Colors.red),
+                  const Gap(12),
+                  const Text(
+                    'deleteRealm',
+                    style: TextStyle(color: Colors.red),
+                  ).tr(),
+                ],
+              ),
+              onTap: () {
+                showConfirmAlert(
+                  'deleteRealmHint'.tr(),
+                  'deleteRealm'.tr(),
+                ).then((confirm) {
+                  if (confirm) {
+                    final client = ref.watch(apiClientProvider);
+                    client.delete('/realms/$realmSlug');
+                    ref.invalidate(realmsJoinedProvider);
+                    if (context.mounted) context.router.maybePop(true);
+                  }
+                });
+              },
+            ),
+          ],
+    );
+  }
+}
+
+final realmMemberStateProvider =
+    StateNotifierProvider.family<RealmMemberNotifier, RealmMemberState, String>(
+      (ref, realmSlug) {
+        final apiClient = ref.watch(apiClientProvider);
+        return RealmMemberNotifier(apiClient, realmSlug);
+      },
+    );
+
+class RealmMemberNotifier extends StateNotifier<RealmMemberState> {
+  final String realmSlug;
+  final Dio _apiClient;
+
+  RealmMemberNotifier(this._apiClient, this.realmSlug)
+    : super(const RealmMemberState(members: [], isLoading: false, total: 0));
+
+  Future<void> loadMore({int offset = 0, int take = 20}) async {
+    if (state.isLoading) return;
+    if (state.total > 0 && state.members.length >= state.total) return;
+
+    state = state.copyWith(isLoading: true, error: null);
+
+    try {
+      final response = await _apiClient.get(
+        '/realms/$realmSlug/members',
+        queryParameters: {'offset': offset, 'take': take},
+      );
+
+      final total = int.parse(response.headers.value('X-Total') ?? '0');
+      final List<dynamic> data = response.data;
+      final members = data.map((e) => SnRealmMember.fromJson(e)).toList();
+
+      state = state.copyWith(
+        members: [...state.members, ...members],
+        total: total,
+        isLoading: false,
+      );
+    } catch (e) {
+      state = state.copyWith(error: e.toString(), isLoading: false);
+    }
+  }
+
+  void reset() {
+    state = const RealmMemberState(members: [], isLoading: false, total: 0);
+  }
+}
+
+class _RealmMemberListSheet extends HookConsumerWidget {
+  final String realmSlug;
+  const _RealmMemberListSheet({required this.realmSlug});
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final memberState = ref.watch(realmMemberStateProvider(realmSlug));
+    final memberNotifier = ref.read(
+      realmMemberStateProvider(realmSlug).notifier,
+    );
+
+    useEffect(() {
+      Future(() {
+        memberNotifier.loadMore();
+      });
+      return null;
+    }, []);
+
+    Future<void> invitePerson() async {
+      final result = await showCupertinoModalBottomSheet(
+        context: context,
+        builder: (context) => const AccountPickerSheet(),
+      );
+      if (result == null) return;
+      try {
+        final apiClient = ref.watch(apiClientProvider);
+        await apiClient.post(
+          '/realms/invites/$realmSlug',
+          data: {'related_user_id': result.id, 'role': 0},
+        );
+        memberNotifier.reset();
+        await memberNotifier.loadMore();
+      } catch (err) {
+        showErrorAlert(err);
+      }
+    }
+
+    return Container(
+      constraints: BoxConstraints(
+        maxHeight: MediaQuery.of(context).size.height * 0.8,
+      ),
+      child: Material(
+        color: Colors.transparent,
+        child: Column(
+          children: [
+            Padding(
+              padding: EdgeInsets.only(
+                top: 16,
+                left: 20,
+                right: 16,
+                bottom: 12,
+              ),
+              child: Row(
+                children: [
+                  Text(
+                    'members'.plural(memberState.total),
+                    style: Theme.of(context).textTheme.headlineSmall?.copyWith(
+                      fontWeight: FontWeight.w600,
+                      letterSpacing: -0.5,
+                    ),
+                  ),
+                  const Spacer(),
+                  IconButton(
+                    icon: const Icon(Symbols.person_add),
+                    onPressed: invitePerson,
+                    style: IconButton.styleFrom(
+                      minimumSize: const Size(36, 36),
+                    ),
+                  ),
+                  IconButton(
+                    icon: const Icon(Symbols.refresh),
+                    onPressed: () {
+                      memberNotifier.reset();
+                      memberNotifier.loadMore();
+                    },
+                  ),
+                  IconButton(
+                    icon: const Icon(Symbols.close),
+                    onPressed: () => Navigator.pop(context),
+                    style: IconButton.styleFrom(
+                      minimumSize: const Size(36, 36),
+                    ),
+                  ),
+                ],
+              ),
+            ),
+            const Divider(height: 1),
+            Expanded(
+              child:
+                  memberState.error != null
+                      ? Center(child: Text(memberState.error!))
+                      : ListView.builder(
+                        itemCount: memberState.members.length + 1,
+                        itemBuilder: (context, index) {
+                          if (index == memberState.members.length) {
+                            if (memberState.isLoading) {
+                              return const Center(
+                                child: Padding(
+                                  padding: EdgeInsets.all(16.0),
+                                  child: CircularProgressIndicator(),
+                                ),
+                              );
+                            }
+                            if (memberState.members.length <
+                                memberState.total) {
+                              memberNotifier.loadMore(
+                                offset: memberState.members.length,
+                              );
+                            }
+                            return const SizedBox.shrink();
+                          }
+
+                          final member = memberState.members[index];
+                          return ListTile(
+                            leading: ProfilePictureWidget(
+                              fileId: member.account!.profile.pictureId,
+                            ),
+                            title: Row(
+                              spacing: 6,
+                              children: [
+                                Flexible(child: Text(member.account!.nick)),
+                                if (member.joinedAt == null)
+                                  const Icon(Symbols.pending_actions, size: 20),
+                              ],
+                            ),
+                            subtitle: Row(
+                              children: [
+                                Text(
+                                  member.role >= 100
+                                      ? 'permissionOwner'
+                                      : member.role >= 50
+                                      ? 'permissionModerator'
+                                      : 'permissionMember',
+                                ).tr(),
+                                Text('ยท').bold().padding(horizontal: 6),
+                                Expanded(
+                                  child: Text("@${member.account!.name}"),
+                                ),
+                              ],
+                            ),
+                          );
+                        },
+                      ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}
+
+class RealmMemberState {
+  final List<SnRealmMember> members;
+  final bool isLoading;
+  final int total;
+  final String? error;
+
+  const RealmMemberState({
+    required this.members,
+    required this.isLoading,
+    required this.total,
+    this.error,
+  });
+
+  RealmMemberState copyWith({
+    List<SnRealmMember>? members,
+    bool? isLoading,
+    int? total,
+    String? error,
+  }) {
+    return RealmMemberState(
+      members: members ?? this.members,
+      isLoading: isLoading ?? this.isLoading,
+      total: total ?? this.total,
+      error: error ?? this.error,
+    );
+  }
+}
diff --git a/lib/screens/realm/detail.g.dart b/lib/screens/realm/detail.g.dart
new file mode 100644
index 0000000..ef73fba
--- /dev/null
+++ b/lib/screens/realm/detail.g.dart
@@ -0,0 +1,152 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'detail.dart';
+
+// **************************************************************************
+// RiverpodGenerator
+// **************************************************************************
+
+String _$realmIdentityHash() => r'eac6e829b5b46bcfadbf201ab6f918d78c894b9f';
+
+/// Copied from Dart SDK
+class _SystemHash {
+  _SystemHash._();
+
+  static int combine(int hash, int value) {
+    // ignore: parameter_assignments
+    hash = 0x1fffffff & (hash + value);
+    // ignore: parameter_assignments
+    hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
+    return hash ^ (hash >> 6);
+  }
+
+  static int finish(int hash) {
+    // ignore: parameter_assignments
+    hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
+    // ignore: parameter_assignments
+    hash = hash ^ (hash >> 11);
+    return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
+  }
+}
+
+/// See also [realmIdentity].
+@ProviderFor(realmIdentity)
+const realmIdentityProvider = RealmIdentityFamily();
+
+/// See also [realmIdentity].
+class RealmIdentityFamily extends Family<AsyncValue<SnRealmMember?>> {
+  /// See also [realmIdentity].
+  const RealmIdentityFamily();
+
+  /// See also [realmIdentity].
+  RealmIdentityProvider call(String realmSlug) {
+    return RealmIdentityProvider(realmSlug);
+  }
+
+  @override
+  RealmIdentityProvider getProviderOverride(
+    covariant RealmIdentityProvider provider,
+  ) {
+    return call(provider.realmSlug);
+  }
+
+  static const Iterable<ProviderOrFamily>? _dependencies = null;
+
+  @override
+  Iterable<ProviderOrFamily>? get dependencies => _dependencies;
+
+  static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
+
+  @override
+  Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
+      _allTransitiveDependencies;
+
+  @override
+  String? get name => r'realmIdentityProvider';
+}
+
+/// See also [realmIdentity].
+class RealmIdentityProvider extends AutoDisposeFutureProvider<SnRealmMember?> {
+  /// See also [realmIdentity].
+  RealmIdentityProvider(String realmSlug)
+    : this._internal(
+        (ref) => realmIdentity(ref as RealmIdentityRef, realmSlug),
+        from: realmIdentityProvider,
+        name: r'realmIdentityProvider',
+        debugGetCreateSourceHash:
+            const bool.fromEnvironment('dart.vm.product')
+                ? null
+                : _$realmIdentityHash,
+        dependencies: RealmIdentityFamily._dependencies,
+        allTransitiveDependencies:
+            RealmIdentityFamily._allTransitiveDependencies,
+        realmSlug: realmSlug,
+      );
+
+  RealmIdentityProvider._internal(
+    super._createNotifier, {
+    required super.name,
+    required super.dependencies,
+    required super.allTransitiveDependencies,
+    required super.debugGetCreateSourceHash,
+    required super.from,
+    required this.realmSlug,
+  }) : super.internal();
+
+  final String realmSlug;
+
+  @override
+  Override overrideWith(
+    FutureOr<SnRealmMember?> Function(RealmIdentityRef provider) create,
+  ) {
+    return ProviderOverride(
+      origin: this,
+      override: RealmIdentityProvider._internal(
+        (ref) => create(ref as RealmIdentityRef),
+        from: from,
+        name: null,
+        dependencies: null,
+        allTransitiveDependencies: null,
+        debugGetCreateSourceHash: null,
+        realmSlug: realmSlug,
+      ),
+    );
+  }
+
+  @override
+  AutoDisposeFutureProviderElement<SnRealmMember?> createElement() {
+    return _RealmIdentityProviderElement(this);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    return other is RealmIdentityProvider && other.realmSlug == realmSlug;
+  }
+
+  @override
+  int get hashCode {
+    var hash = _SystemHash.combine(0, runtimeType.hashCode);
+    hash = _SystemHash.combine(hash, realmSlug.hashCode);
+
+    return _SystemHash.finish(hash);
+  }
+}
+
+@Deprecated('Will be removed in 3.0. Use Ref instead')
+// ignore: unused_element
+mixin RealmIdentityRef on AutoDisposeFutureProviderRef<SnRealmMember?> {
+  /// The parameter `realmSlug` of this provider.
+  String get realmSlug;
+}
+
+class _RealmIdentityProviderElement
+    extends AutoDisposeFutureProviderElement<SnRealmMember?>
+    with RealmIdentityRef {
+  _RealmIdentityProviderElement(super.provider);
+
+  @override
+  String get realmSlug => (origin as RealmIdentityProvider).realmSlug;
+}
+
+// ignore_for_file: type=lint
+// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
diff --git a/lib/screens/realm/realms.dart b/lib/screens/realm/realms.dart
index 818cf9b..8f71a25 100644
--- a/lib/screens/realm/realms.dart
+++ b/lib/screens/realm/realms.dart
@@ -64,50 +64,11 @@ class RealmListScreen extends HookConsumerWidget {
                           ),
                           title: Text(value[item].name),
                           subtitle: Text(value[item].description),
-                          trailing: Row(
-                            mainAxisSize: MainAxisSize.min,
-                            children: [
-                              IconButton(
-                                padding: EdgeInsets.zero,
-                                visualDensity: VisualDensity.compact,
-                                icon: Icon(Symbols.delete),
-                                onPressed: () {
-                                  showConfirmAlert(
-                                    'deleteRealmHint'.tr(),
-                                    'deleteRealm'.tr(args: [value[item].name]),
-                                  ).then((confirm) {
-                                    if (confirm) {
-                                      final client = ref.watch(
-                                        apiClientProvider,
-                                      );
-                                      client.delete(
-                                        '/realms/${value[item].slug}',
-                                      );
-                                      ref.invalidate(publishersManagedProvider);
-                                    }
-                                  });
-                                },
-                              ),
-                              IconButton(
-                                padding: EdgeInsets.zero,
-                                visualDensity: VisualDensity.compact,
-                                icon: Icon(Symbols.edit),
-                                onPressed: () {
-                                  context.router
-                                      .push(
-                                        EditRealmRoute(slug: value[item].slug),
-                                      )
-                                      .then((value) {
-                                        if (value != null) {
-                                          ref.refresh(
-                                            realmsJoinedProvider.future,
-                                          );
-                                        }
-                                      });
-                                },
-                              ),
-                            ],
-                          ),
+                          onTap: () {
+                            context.router.push(
+                              RealmDetailRoute(slug: value[item].slug),
+                            );
+                          },
                           contentPadding: EdgeInsets.only(left: 16, right: 14),
                         );
                       },