Skip to content

Commit 8766602

Browse files
committed
feat: add ability to open/close rooms for hosts
Signed-off-by: Brandon McAnsh <[email protected]>
1 parent d5c9a01 commit 8766602

File tree

33 files changed

+936
-87
lines changed

33 files changed

+936
-87
lines changed

definitions/flipchat/protos/src/main/proto/chat/v1/chat_service.proto

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ service Chat {
3636
rpc JoinChat(JoinChatRequest) returns (JoinChatResponse);
3737
// LeaveChat leaves a given chat.
3838
rpc LeaveChat(LeaveChatRequest) returns (LeaveChatResponse);
39+
// OpenChat opens a chat up for messaging across all members
40+
rpc OpenChat(OpenChatRequest) returns (OpenChatResponse);
41+
// CloseChat closes a chat up for messaging to just the chat owner
42+
rpc CloseChat(CloseChatRequest) returns (CloseChatResponse);
3943
// SetDisplayName sets a chat's display name. If the display name isn't allowed,
4044
// then a set of alternate suggestions may be provided
4145
rpc SetDisplayName(SetDisplayNameRequest) returns (SetDisplayNameResponse);
@@ -239,6 +243,30 @@ message LeaveChatResponse {
239243
OK = 0;
240244
}
241245
}
246+
message OpenChatRequest {
247+
// The chat that is being opened
248+
common.v1.ChatId chat_id = 1;
249+
common.v1.Auth auth = 2;
250+
}
251+
message OpenChatResponse {
252+
Result result = 1;
253+
enum Result {
254+
OK = 0;
255+
DENIED = 1;
256+
}
257+
}
258+
message CloseChatRequest {
259+
/// The chat that is being closed
260+
common.v1.ChatId chat_id = 1;
261+
common.v1.Auth auth = 2;
262+
}
263+
message CloseChatResponse {
264+
Result result = 1;
265+
enum Result {
266+
OK = 0;
267+
DENIED = 1;
268+
}
269+
}
242270
message SetDisplayNameRequest {
243271
common.v1.ChatId chat_id = 1;
244272
string display_name = 2 ;
@@ -371,6 +399,17 @@ message Metadata {
371399
common.v1.PaymentAmount cover_charge = 9;
372400
// The timestamp of the last activity in this chat
373401
google.protobuf.Timestamp last_activity = 10;
402+
// The status as to whether the room is open or closed. This may be
403+
// omitted for chats where it doesn't apply. If not provided, it's
404+
// safe to assume the chat is open indefinitely until otherwise provided.
405+
OpenStatus open_status = 12;
406+
}
407+
// todo: In the future, we may add additional fields like open/closed until a timestamp, etc.
408+
// For backwards compatibility, client can always refer to is_currently_open for whether
409+
// a room is open right now or not for the purposes of sending messages.
410+
// todo: A better name for this
411+
message OpenStatus {
412+
bool is_currently_open = 1;
374413
}
375414
message MetadataUpdate {
376415
oneof kind {
@@ -379,6 +418,7 @@ message MetadataUpdate {
379418
DisplayNameChanged display_name_changed = 3;
380419
CoverChargeChanged cover_charge_changed = 4;
381420
LastActivityChanged last_activity_changed = 5;
421+
OpenStatusChanged open_status_changed = 6;
382422
}
383423
// Refreshes the entire chat metadata
384424
message FullRefresh {
@@ -404,6 +444,10 @@ message MetadataUpdate {
404444
message LastActivityChanged {
405445
google.protobuf.Timestamp new_last_activity = 1;
406446
}
447+
// The open status has changed to a newer value
448+
message OpenStatusChanged {
449+
OpenStatus new_open_status = 1;
450+
}
407451
}
408452
message Member {
409453
common.v1.UserId user_id = 1;

definitions/flipchat/protos/src/main/proto/messaging/v1/messaging_service.proto

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ service Messaging {
1111
rpc StreamMessages(stream StreamMessagesRequest) returns (stream StreamMessagesResponse);
1212
// GetMessage gets a single message in a chat
1313
rpc GetMessage(GetMessageRequest) returns (GetMessageResponse);
14-
// GetMessages gets the set of messages for a chat using a paged API
14+
// GetMessages gets the set of messages for a chat using a paged and batched APIs
1515
rpc GetMessages(GetMessagesRequest) returns (GetMessagesResponse);
1616
// SendMessage sends a message to a chat.
1717
rpc SendMessage(SendMessageRequest) returns (SendMessageResponse);
@@ -49,9 +49,6 @@ message StreamMessagesResponse {
4949
DENIED = 0;
5050
}
5151
}
52-
message MessageBatch {
53-
repeated Message messages = 1 ;
54-
}
5552
}
5653
message GetMessageRequest {
5754
common.v1.ChatId chat_id = 1;
@@ -69,7 +66,11 @@ message GetMessageResponse {
6966
}
7067
message GetMessagesRequest {
7168
common.v1.ChatId chat_id = 1;
72-
common.v1.QueryOptions query_options = 2;
69+
// If not set, defaults to an ascending query option without a page token and server-defined limit
70+
oneof query {
71+
common.v1.QueryOptions options = 2;
72+
MessageIdBatch message_ids = 3;
73+
}
7374
common.v1.Auth auth = 5;
7475
}
7576
message GetMessagesResponse {

definitions/flipchat/protos/src/main/proto/messaging/v1/model.proto

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ message MessageId {
1111
// chat history.
1212
bytes value = 1 ;
1313
}
14+
message MessageIdBatch {
15+
repeated MessageId message_ids = 1 ;
16+
}
1417
// A message in a chat
1518
message Message {
1619
// Globally unique ID for this message
@@ -24,6 +27,9 @@ message Message {
2427
// any time-based UUID message IDs.
2528
google.protobuf.Timestamp ts = 4;
2629
}
30+
message MessageBatch {
31+
repeated Message messages = 1 ;
32+
}
2733
// Pointer in a chat indicating a user's message history state in a chat.
2834
message Pointer {
2935
// The type of pointer indicates which user's message history state can be

flipchatApp/src/main/kotlin/xyz/flipchat/app/beta/LabsController.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,14 @@ sealed interface Lab {
5555
override val launched: Boolean = false
5656
}
5757

58+
data object OpenCloseRoom : Lab {
59+
override val key: String = "open_close_room_enabled"
60+
override val default: Boolean = false
61+
override val launched: Boolean = false
62+
}
63+
5864
companion object {
59-
val entries = listOf(ReplyToMessage, FollowerMode, StartChatAtUnread, RoomNameChanges, DeleteMessage)
65+
val entries = listOf(ReplyToMessage, FollowerMode, StartChatAtUnread, RoomNameChanges, DeleteMessage, OpenCloseRoom)
6066
internal fun byKey(key: Preferences.Key<*>): Lab? {
6167
return entries.firstOrNull { it.key == key.name }
6268
}

flipchatApp/src/main/kotlin/xyz/flipchat/app/features/beta/BetaFlagsScreen.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ private val Lab.title: String
104104
Lab.StartChatAtUnread -> "Open Conversation @ Last Unread"
105105
Lab.RoomNameChanges -> "Room Name Changes For Hosts"
106106
Lab.DeleteMessage -> "Delete Message Support"
107+
Lab.OpenCloseRoom -> "Open/Close Rooms"
107108
}
108109

109110
private val Lab.message: String
@@ -113,4 +114,5 @@ private val Lab.message: String
113114
Lab.StartChatAtUnread -> "When enabled, conversations will resume at the last message you read"
114115
Lab.RoomNameChanges -> "When enabled, hosts will gain the ability to set a desired name for their room"
115116
Lab.DeleteMessage -> "When enabled, hosts will gain the ability to delete messages"
117+
Lab.OpenCloseRoom -> "When enabled, hosts will gain the ability to temporarily close (and reopen) their rooms"
116118
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package xyz.flipchat.app.features.chat.conversation
2+
3+
import com.getcode.model.Kin
4+
5+
6+
sealed interface ChattableState {
7+
interface Active
8+
data object DisabledByMute: ChattableState, Active
9+
data class Spectator(val cover: Kin): ChattableState
10+
data object Enabled: ChattableState
11+
data object DisabledByClosedRoom: ChattableState
12+
13+
fun isActiveMember() = this is Active
14+
}

flipchatApp/src/main/kotlin/xyz/flipchat/app/features/chat/conversation/ConversationChatInput.kt

Lines changed: 117 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,24 @@ import androidx.compose.animation.ExitTransition
66
import androidx.compose.animation.slideInVertically
77
import androidx.compose.animation.togetherWith
88
import androidx.compose.foundation.background
9+
import androidx.compose.foundation.layout.Arrangement
10+
import androidx.compose.foundation.layout.Column
11+
import androidx.compose.foundation.layout.PaddingValues
12+
import androidx.compose.foundation.layout.Row
913
import androidx.compose.foundation.layout.Spacer
1014
import androidx.compose.foundation.layout.fillMaxWidth
1115
import androidx.compose.foundation.layout.height
1216
import androidx.compose.foundation.layout.navigationBarsPadding
1317
import androidx.compose.foundation.layout.padding
18+
import androidx.compose.foundation.shape.CircleShape
1419
import androidx.compose.material.Text
1520
import androidx.compose.runtime.Composable
1621
import androidx.compose.runtime.LaunchedEffect
1722
import androidx.compose.runtime.getValue
1823
import androidx.compose.runtime.mutableStateOf
1924
import androidx.compose.runtime.remember
2025
import androidx.compose.runtime.setValue
26+
import androidx.compose.ui.Alignment
2127
import androidx.compose.ui.Modifier
2228
import androidx.compose.ui.focus.FocusRequester
2329
import androidx.compose.ui.res.stringResource
@@ -28,6 +34,8 @@ import com.getcode.theme.CodeTheme
2834
import com.getcode.ui.components.chat.ChatInput
2935
import com.getcode.ui.theme.ButtonState
3036
import com.getcode.ui.theme.CodeButton
37+
import com.getcode.ui.utils.keyboardAsState
38+
import com.getcode.ui.utils.withTopBorder
3139
import com.getcode.util.resources.LocalResources
3240
import com.getcode.utils.Kin
3341
import com.getcode.utils.formatAmountString
@@ -40,6 +48,7 @@ fun ConversationChatInput(
4048
dispatchEvent: (ConversationViewModel.Event) -> Unit,
4149
) {
4250
var previousState by remember { mutableStateOf<ChattableState?>(null) }
51+
val keyboardVisible by keyboardAsState()
4352

4453
AnimatedContent(
4554
targetState = state.chattableState,
@@ -55,7 +64,11 @@ fun ConversationChatInput(
5564
) { chattableState ->
5665
when (chattableState) {
5766
null -> {
58-
Spacer(modifier = Modifier.fillMaxWidth().height(100.dp))
67+
Spacer(
68+
modifier = Modifier
69+
.fillMaxWidth()
70+
.height(100.dp)
71+
)
5972
}
6073

6174
ChattableState.DisabledByMute -> {
@@ -75,39 +88,84 @@ fun ConversationChatInput(
7588
)
7689
}
7790

78-
ChattableState.Enabled -> {
79-
ChatInput(
80-
modifier = Modifier.navigationBarsPadding(),
81-
state = state.textFieldState,
82-
sendCashEnabled = false,
83-
focusRequester = focusRequester,
84-
onSendMessage = { dispatchEvent(ConversationViewModel.Event.SendMessage) },
85-
onSendCash = { dispatchEvent(ConversationViewModel.Event.SendCash) }
86-
)
91+
is ChattableState.Enabled -> {
92+
Column {
93+
if (state.isHost && !keyboardVisible && state.isOpenCloseEnabled) {
94+
RoomOpenControlBar(
95+
modifier = Modifier.fillMaxWidth(),
96+
isOpen = state.isRoomOpen
97+
) { dispatchEvent(ConversationViewModel.Event.OnOpenStateChangedRequested) }
98+
}
99+
100+
ChatInput(
101+
modifier = Modifier.navigationBarsPadding(),
102+
state = state.textFieldState,
103+
sendCashEnabled = false,
104+
focusRequester = focusRequester,
105+
onSendMessage = { dispatchEvent(ConversationViewModel.Event.SendMessage) },
106+
onSendCash = { dispatchEvent(ConversationViewModel.Event.SendCash) }
107+
)
108+
}
87109
}
88110

89111
is ChattableState.Spectator -> {
90-
CodeButton(
112+
Column {
113+
if (!state.isRoomOpen && state.isOpenCloseEnabled) {
114+
Text(
115+
modifier = Modifier
116+
.fillMaxWidth()
117+
.background(CodeTheme.colors.secondary)
118+
.padding(
119+
top = CodeTheme.dimens.grid.x1,
120+
bottom = CodeTheme.dimens.grid.x3
121+
)
122+
.navigationBarsPadding(),
123+
textAlign = TextAlign.Center,
124+
text = stringResource(R.string.title_roomIsClosed),
125+
style = CodeTheme.typography.textSmall,
126+
color = CodeTheme.colors.textSecondary
127+
)
128+
}
129+
130+
CodeButton(
131+
modifier = Modifier
132+
.fillMaxWidth()
133+
.padding(
134+
start = CodeTheme.dimens.inset,
135+
end = CodeTheme.dimens.inset
136+
)
137+
.navigationBarsPadding(),
138+
buttonState = ButtonState.Filled,
139+
text = stringResource(
140+
R.string.action_joinRoomWithCost,
141+
formatAmountString(
142+
resources = LocalResources.current!!,
143+
currency = Currency.Kin,
144+
amount = chattableState.cover.quarks.toDouble(),
145+
suffix = stringResource(R.string.core_kin)
146+
)
147+
),
148+
) {
149+
dispatchEvent(ConversationViewModel.Event.OnJoinRequestedFromSpectating)
150+
}
151+
}
152+
}
153+
154+
ChattableState.DisabledByClosedRoom -> {
155+
Text(
91156
modifier = Modifier
92157
.fillMaxWidth()
158+
.background(CodeTheme.colors.secondary)
93159
.padding(
94-
start = CodeTheme.dimens.inset,
95-
end = CodeTheme.dimens.inset
160+
top = CodeTheme.dimens.grid.x1,
161+
bottom = CodeTheme.dimens.grid.x3
96162
)
97163
.navigationBarsPadding(),
98-
buttonState = ButtonState.Filled,
99-
text = stringResource(
100-
R.string.action_joinRoomWithCost,
101-
formatAmountString(
102-
resources = LocalResources.current!!,
103-
currency = Currency.Kin,
104-
amount = chattableState.cover.quarks.toDouble(),
105-
suffix = stringResource(R.string.core_kin)
106-
)
107-
),
108-
) {
109-
dispatchEvent(ConversationViewModel.Event.OnJoinRequestedFromSpectating)
110-
}
164+
textAlign = TextAlign.Center,
165+
text = stringResource(R.string.title_roomIsClosed),
166+
style = CodeTheme.typography.textSmall,
167+
color = CodeTheme.colors.textSecondary
168+
)
111169
}
112170
}
113171
}
@@ -117,3 +175,36 @@ fun ConversationChatInput(
117175
previousState = state.chattableState
118176
}
119177
}
178+
179+
@Composable
180+
private fun RoomOpenControlBar(
181+
modifier: Modifier = Modifier,
182+
isOpen: Boolean,
183+
onChangeRequest: () -> Unit,
184+
) {
185+
Row(
186+
modifier = modifier
187+
.withTopBorder(color = CodeTheme.colors.dividerVariant)
188+
.padding(CodeTheme.dimens.grid.x2),
189+
verticalAlignment = Alignment.CenterVertically,
190+
horizontalArrangement = Arrangement.SpaceBetween
191+
) {
192+
Text(
193+
text = stringResource(
194+
if (isOpen) R.string.subtitle_roomIsOpen else R.string.subtitle_roomIsClosed
195+
),
196+
style = CodeTheme.typography.textSmall,
197+
color = CodeTheme.colors.textMain
198+
)
199+
200+
CodeButton(
201+
text = stringResource(R.string.action_change),
202+
shape = CircleShape,
203+
buttonState = ButtonState.Filled,
204+
overrideContentPadding = true,
205+
contentPadding = PaddingValues(horizontal = CodeTheme.dimens.grid.x2),
206+
) {
207+
onChangeRequest()
208+
}
209+
}
210+
}

0 commit comments

Comments
 (0)