Skip to content

Commit 74eef99

Browse files
authored
feat: add endReached flag and created/archived system messages (#175)
1 parent a60fdb1 commit 74eef99

File tree

12 files changed

+134
-34
lines changed

12 files changed

+134
-34
lines changed

android/fastlane/Fastfile

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ platform :android do
4545
end
4646

4747
upload_to_play_store(
48-
release_status: "draft", # TODO: remove this when ready
4948
aab: "../build/app/outputs/bundle/release/app-release.aab",
5049
track: "internal",
5150
version_name: version_string,
@@ -57,15 +56,20 @@ platform :android do
5756
skip_upload_images: true,
5857
skip_upload_screenshots: true,
5958
)
60-
# TODO: uncomment this when ready
61-
# internal_app_url = upload_to_play_store_internal_app_sharing(
62-
# aab: "../build/app/outputs/bundle/release/app-release.aab",
63-
# )
59+
begin
60+
internal_app_url = upload_to_play_store_internal_app_sharing(
61+
aab: "../build/app/outputs/bundle/release/app-release.aab",
62+
)
63+
rescue => e
64+
UI.message("Error during internal app sharing upload: #{e.message}")
65+
internal_app_url = "Failed"
66+
end
67+
6468
slack(
6569
payload: {
6670
"Build Date" => Time.now.to_s,
6771
"Build Number" => build_number,
68-
# "Internal App" => "<#{internal_app_url}>",
72+
"Internal App" => internal_app_url == "Failed" ? "Failed" : "<#{internal_app_url}>",
6973
}
7074
) if ENV['CI']
7175
end

assets/i18n/en.i18n.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,21 @@
141141
},
142142
"chat_room": {
143143
"system_messages": {
144+
"created": {
145+
"description": "$user created the pot."
146+
},
144147
"user_in": {
145148
"description": "$user joined the pot."
146149
},
147150
"user_leave": {
148-
"description": "$user left the pot."
151+
"description": "$user left the pot.",
152+
"description_with_aux_user": "$user left the pot. $aux_user is now the host."
149153
},
150154
"user_kicked": {
151155
"description": "$user was kicked from the pot."
156+
},
157+
"archived": {
158+
"description": "The pot has been archived."
152159
}
153160
},
154161
"fofo": {

assets/i18n/ko.i18n.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,21 @@
138138
},
139139
"chat_room": {
140140
"system_messages": {
141+
"created": {
142+
"description": "$user 님이 팟을 생성하셨습니다"
143+
},
141144
"user_in": {
142145
"description": "$user 님이 입장하셨습니다"
143146
},
144147
"user_leave": {
145-
"description": "$user 님이 나갔습니다"
148+
"description": "$user 님이 나갔습니다",
149+
"description_with_aux_user": "$user 님이 나갔습니다. $aux_user 님이 방장이 되었습니다"
146150
},
147151
"user_kicked": {
148152
"description": "$user 님이 팟에서 제외되었습니다"
153+
},
154+
"archived": {
155+
"description": "팟이 해산되었습니다"
149156
}
150157
},
151158
"fofo": {

lib/app/modules/chat/data/models/system_message_model.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ sealed class SystemMessageModel
1111
implements SystemMessageEntity {
1212
const factory SystemMessageModel({
1313
required SystemMessageType type,
14-
required PotUserEntity relatedUser,
14+
PotUserEntity? relatedUser,
15+
PotUserEntity? auxRelatedUser,
1516
@dateTimeConverter required DateTime createdAt,
1617
}) = _SystemMessageModel;
1718
}

lib/app/modules/chat/data/repositories/websocket_chat_repository.dart

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import 'package:pot_g/app/modules/chat/domain/repositories/chat_repository.dart'
1010
import 'package:pot_g/app/modules/socket/data/data_sources/websocket.dart';
1111
import 'package:pot_g/app/modules/socket/data/models/events/pot_event_model.dart';
1212
import 'package:pot_g/app/modules/socket/data/models/events/send_chat_response_model.dart';
13+
import 'package:pot_g/app/modules/socket/data/models/pot_events/archive_v1_event.dart';
1314
import 'package:pot_g/app/modules/socket/data/models/pot_events/chat_v1_event.dart';
15+
import 'package:pot_g/app/modules/socket/data/models/pot_events/create_v1_event.dart';
1416
import 'package:pot_g/app/modules/socket/data/models/pot_events/popo_chat_v1_event.dart';
1517
import 'package:pot_g/app/modules/socket/data/models/pot_events/user_in_v1_event.dart';
1618
import 'package:pot_g/app/modules/socket/data/models/pot_events/user_kick_v1_event.dart';
@@ -19,48 +21,72 @@ import 'package:pot_g/app/modules/socket/data/models/requests/send_chat_model.da
1921

2022
bool _isChatEvent(PotEventModel e) {
2123
return e is PotEventModel<ChatV1Event> ||
24+
e is PotEventModel<CreateV1Event> ||
2225
e is PotEventModel<UserInV1Event> ||
2326
e is PotEventModel<UserLeaveV1Event> ||
2427
e is PotEventModel<UserKickV1Event> ||
25-
e is PotEventModel<PopoChatV1Event>;
28+
e is PotEventModel<PopoChatV1Event> ||
29+
e is PotEventModel<ArchiveV1Event>;
2630
}
2731

2832
String? _getRelatedUserId(PotEventModel e) {
2933
return switch (e) {
3034
PotEventModel<ChatV1Event>() => e.data.from,
35+
PotEventModel<CreateV1Event>() => e.data.createdBy,
3136
PotEventModel<UserInV1Event>() => e.data.userPk,
3237
PotEventModel<UserLeaveV1Event>() => e.data.userPk,
3338
PotEventModel<UserKickV1Event>() => e.data.kickedUserPk,
3439
PotEventModel<PopoChatV1Event>() => null,
40+
PotEventModel<ArchiveV1Event>() => null,
3541
_ => throw StateError('Unknown event type'),
3642
};
3743
}
3844

45+
String? _getAuxRelatedUserId(PotEventModel e) {
46+
return switch (e) {
47+
PotEventModel<UserLeaveV1Event>() => e.data.hostChangedTo,
48+
_ => null,
49+
};
50+
}
51+
3952
Sendable _makeChatEntity(PotEventModel e, PotInfoEntity pot) {
4053
final users = pot.usersInfo.users;
4154
final user = users.firstWhereOrNull((u) => u.id == _getRelatedUserId(e));
55+
final auxUser = users.firstWhereOrNull(
56+
(u) => u.id == _getAuxRelatedUserId(e),
57+
);
4258
return switch (e) {
4359
PotEventModel<ChatV1Event>() => ChatModel(
4460
message: e.data.content,
4561
user: user!,
4662
createdAt: e.timestamp,
4763
),
64+
PotEventModel<CreateV1Event>() => SystemMessageModel(
65+
type: SystemMessageType.created,
66+
relatedUser: user,
67+
createdAt: e.timestamp,
68+
),
4869
PotEventModel<UserInV1Event>() => SystemMessageModel(
4970
type: SystemMessageType.userIn,
50-
relatedUser: user!,
71+
relatedUser: user,
5172
createdAt: e.timestamp,
5273
),
5374
PotEventModel<UserLeaveV1Event>() => SystemMessageModel(
5475
type: SystemMessageType.userLeave,
55-
relatedUser: user!,
76+
relatedUser: user,
77+
auxRelatedUser: auxUser,
5678
createdAt: e.timestamp,
5779
),
5880
PotEventModel<UserKickV1Event>() => SystemMessageModel(
5981
type: SystemMessageType.userKicked,
60-
relatedUser: user!,
82+
relatedUser: user,
6183
createdAt: e.timestamp,
6284
),
6385
PotEventModel<PopoChatV1Event>() => e.data.toEntity(e.timestamp),
86+
PotEventModel<ArchiveV1Event>() => SystemMessageModel(
87+
type: SystemMessageType.archived,
88+
createdAt: e.timestamp,
89+
),
6490
_ => throw StateError('Unknown event type'),
6591
};
6692
}

lib/app/modules/chat/domain/entities/chat_entity.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ abstract class ChatEntity implements Sendable {
1111
PotUserEntity get user;
1212
}
1313

14-
enum SystemMessageType { userIn, userLeave, userKicked }
14+
enum SystemMessageType { userIn, userLeave, userKicked, created, archived }
1515

1616
abstract class SystemMessageEntity implements Sendable {
1717
SystemMessageType get type;
18-
PotUserEntity get relatedUser;
18+
PotUserEntity? get relatedUser;
19+
PotUserEntity? get auxRelatedUser;
1920
}
2021

2122
abstract class FofoChatEntity implements Sendable {

lib/app/modules/chat/presentation/bloc/chat_bloc.dart

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,16 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
3131
_completer.complete();
3232
try {
3333
final chats = await _chatRepository.getChats(_pot, DateTime.now());
34-
emit(ChatState.loaded(chats.reversed.toList()));
34+
emit(
35+
ChatState.loaded(chats.reversed.toList(), endReached: chats.isEmpty),
36+
);
3537
return emit.forEach(
3638
_chatRepository.getChatsStream(_pot),
3739
onData: (chat) {
38-
return ChatState.loaded([chat, ...state.chats]);
40+
return ChatState.loaded([
41+
chat,
42+
...state.chats,
43+
], endReached: state.endReached);
3944
},
4045
);
4146
} catch (e) {
@@ -61,10 +66,17 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
6166
await _completer.future;
6267
await _mutex.acquire();
6368
try {
69+
if (state.endReached) return;
70+
if (state.chats.isEmpty) return;
6471
emit(ChatState.loading(state.chats));
6572
final lastChat = state.chats.last;
6673
final chats = await _chatRepository.getChats(_pot, lastChat.createdAt);
67-
emit(ChatState.loaded([...state.chats, ...chats.reversed]));
74+
emit(
75+
ChatState.loaded([
76+
...state.chats,
77+
...chats.reversed,
78+
], endReached: chats.isEmpty),
79+
);
6880
} finally {
6981
_mutex.release();
7082
}
@@ -85,7 +97,10 @@ sealed class ChatState with _$ChatState {
8597
ChatInitial;
8698
const factory ChatState.loading([@Default([]) List<Sendable> chats]) =
8799
ChatLoading;
88-
const factory ChatState.loaded(List<Sendable> chats) = ChatLoaded;
100+
const factory ChatState.loaded(
101+
List<Sendable> chats, {
102+
@Default(false) bool endReached,
103+
}) = ChatLoaded;
89104
const factory ChatState.error(List<Sendable> chats, String message) =
90105
ChatError;
91106

@@ -98,4 +113,8 @@ sealed class ChatState with _$ChatState {
98113
ChatError(:final message) => message,
99114
_ => null,
100115
};
116+
bool get endReached => switch (this) {
117+
ChatLoaded(:final endReached) => endReached,
118+
_ => false,
119+
};
101120
}

lib/app/modules/chat/presentation/pages/chat_room_page.dart

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class _Layout extends StatelessWidget {
100100
final pot = state.pot!;
101101
final disabled = state.isArchived;
102102
return Scaffold(
103-
backgroundColor: disabled ? Palette.borderGrey : null,
103+
backgroundColor: disabled ? const Color(0xfff0f0f0) : null,
104104
appBar: PotAppBar(title: Text(pot.name)),
105105
onEndDrawerChanged: (value) {
106106
if (value) {
@@ -238,11 +238,14 @@ class _ChatListState extends State<_ChatList> {
238238
@override
239239
void initState() {
240240
super.initState();
241-
_controller.addListener(() {
242-
if (_controller.position.pixels >= _controller.position.maxScrollExtent) {
243-
context.read<ChatBloc>().add(ChatLoadMore());
244-
}
245-
});
241+
_controller.addListener(_onScroll);
242+
}
243+
244+
void _onScroll() {
245+
if (!_controller.hasClients) return;
246+
if (_controller.position.pixels >= _controller.position.maxScrollExtent) {
247+
context.read<ChatBloc>().add(ChatLoadMore());
248+
}
246249
}
247250

248251
@override
@@ -253,7 +256,12 @@ class _ChatListState extends State<_ChatList> {
253256

254257
@override
255258
Widget build(BuildContext context) {
256-
return BlocBuilder<ChatBloc, ChatState>(
259+
return BlocConsumer<ChatBloc, ChatState>(
260+
listener: (context, state) {
261+
if (!state.endReached && !state.isLoading) {
262+
_onScroll();
263+
}
264+
},
257265
builder: (context, state) {
258266
bool isLast(int index) {
259267
final chat = state.chats[index];
@@ -267,6 +275,7 @@ class _ChatListState extends State<_ChatList> {
267275
}
268276

269277
return ListView.separated(
278+
physics: const AlwaysScrollableScrollPhysics(),
270279
controller: _controller,
271280
reverse: true,
272281
padding: const EdgeInsets.all(12) - EdgeInsets.only(right: 6),

lib/app/modules/chat/presentation/widgets/system_message.dart

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,25 @@ class SystemMessage extends StatelessWidget {
1313
Widget build(BuildContext context) {
1414
final trans = context.t.chat_room.system_messages;
1515
final text = switch (message.type) {
16-
SystemMessageType.userIn => trans.user_in.description(
17-
user: message.relatedUser.name,
16+
SystemMessageType.created => trans.created.description(
17+
user: message.relatedUser?.name ?? 'Unknown',
1818
),
19-
SystemMessageType.userLeave => trans.user_leave.description(
20-
user: message.relatedUser.name,
19+
SystemMessageType.userIn => trans.user_in.description(
20+
user: message.relatedUser?.name ?? 'Unknown',
2121
),
22+
SystemMessageType.userLeave =>
23+
message.auxRelatedUser == null
24+
? trans.user_leave.description(
25+
user: message.relatedUser?.name ?? 'Unknown',
26+
)
27+
: trans.user_leave.description_with_aux_user(
28+
user: message.relatedUser?.name ?? 'Unknown',
29+
aux_user: message.auxRelatedUser!.name,
30+
),
2231
SystemMessageType.userKicked => trans.user_kicked.description(
23-
user: message.relatedUser.name,
32+
user: message.relatedUser?.name ?? 'Unknown',
2433
),
34+
SystemMessageType.archived => trans.archived.description,
2535
};
2636
return Container(
2737
padding: const EdgeInsets.symmetric(vertical: 8),

lib/app/modules/socket/data/models/converter/server_converter.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:pot_g/app/modules/socket/data/models/pot_events/accounting_confi
77
import 'package:pot_g/app/modules/socket/data/models/pot_events/accounting_request_v1_event.dart';
88
import 'package:pot_g/app/modules/socket/data/models/pot_events/archive_v1_event.dart';
99
import 'package:pot_g/app/modules/socket/data/models/pot_events/chat_v1_event.dart';
10+
import 'package:pot_g/app/modules/socket/data/models/pot_events/create_v1_event.dart';
1011
import 'package:pot_g/app/modules/socket/data/models/pot_events/departure_confirm_v1_event.dart';
1112
import 'package:pot_g/app/modules/socket/data/models/pot_events/popo_chat_v1_event.dart';
1213
import 'package:pot_g/app/modules/socket/data/models/pot_events/user_in_v1_event.dart';
@@ -45,8 +46,7 @@ BaseServerMessageModel<T> convertServerMessage<
4546
);
4647
final data = switch (type) {
4748
'pot_event_receive' => switch (jsonData['body']['event_type']) {
48-
// TODO: handle create_v1 event
49-
'create_v1' => pe(ArchiveV1Event.fromJson),
49+
'create_v1' => pe(CreateV1Event.fromJson),
5050
'chat_v1' => pe(ChatV1Event.fromJson),
5151
'popo_chat_v1' => pe(PopoChatV1Event.fromJson),
5252
'user_in_v1' => pe(UserInV1Event.fromJson),

0 commit comments

Comments
 (0)