Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,6 @@ internal fun DMConversationScreen(
toProfile: (MicroBlogKey) -> Unit,
) {
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
val state by producePresenter(
key = "dm_conversation_${accountType}_$roomKey",
) {
Expand All @@ -94,6 +91,11 @@ internal fun DMConversationScreen(
roomKey = roomKey,
)
}
LaunchedEffect(state.pinCodePromptVisible) {
if (!state.pinCodePromptVisible) {
focusRequester.requestFocus()
}
}

FlareScaffold(
topBar = {
Expand Down Expand Up @@ -184,59 +186,61 @@ internal fun DMConversationScreen(
)
},
bottomBar = {
Surface {
Box {
HorizontalDivider(
modifier =
Modifier
.align(Alignment.TopCenter)
.fillMaxWidth(),
color = FlareDividerDefaults.color,
thickness = FlareDividerDefaults.thickness,
)
OutlinedTextField(
modifier =
Modifier
.padding(
bottom = LocalBottomBarHeight.current,
).windowInsetsPadding(
WindowInsets.systemBars.only(
WindowInsetsSides.Horizontal,
),
).consumeWindowInsets(
PaddingValues(
if (!state.pinCodePromptVisible) {
Surface {
Box {
HorizontalDivider(
modifier =
Modifier
.align(Alignment.TopCenter)
.fillMaxWidth(),
color = FlareDividerDefaults.color,
thickness = FlareDividerDefaults.thickness,
)
OutlinedTextField(
modifier =
Modifier
.padding(
bottom = LocalBottomBarHeight.current,
).windowInsetsPadding(
WindowInsets.systemBars.only(
WindowInsetsSides.Horizontal,
),
).consumeWindowInsets(
PaddingValues(
bottom = LocalBottomBarHeight.current,
),
).imePadding()
.fillMaxWidth()
.padding(
horizontal = screenHorizontalPadding,
vertical = 8.dp,
).focusRequester(
focusRequester = focusRequester,
),
).imePadding()
.fillMaxWidth()
.padding(
horizontal = screenHorizontalPadding,
vertical = 8.dp,
).focusRequester(
focusRequester = focusRequester,
),
state = state.text,
lineLimits = TextFieldLineLimits.SingleLine,
trailingIcon = {
IconButton(
onClick = {
state.send()
},
enabled = state.canSend,
) {
FAIcon(
FontAwesomeIcons.Solid.PaperPlane,
contentDescription = stringResource(id = R.string.send),
state = state.text,
lineLimits = TextFieldLineLimits.SingleLine,
trailingIcon = {
IconButton(
onClick = {
state.send()
},
enabled = state.canSend,
) {
FAIcon(
FontAwesomeIcons.Solid.PaperPlane,
contentDescription = stringResource(id = R.string.send),
)
}
},
shape = RoundedCornerShape(100),
placeholder = {
Text(
text = stringResource(id = R.string.dm_send_placeholder),
)
}
},
shape = RoundedCornerShape(100),
placeholder = {
Text(
text = stringResource(id = R.string.dm_send_placeholder),
)
},
)
},
)
}
}
}
},
Expand All @@ -249,50 +253,59 @@ internal fun DMConversationScreen(
}
}
}
LazyColumn(
state = listState,
reverseLayout = true,
contentPadding = contentPadding,
modifier =
Modifier
.consumeWindowInsets(contentPadding)
.fillMaxSize()
.imePadding()
.imeNestedScroll(),
verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.Bottom),
) {
items(
state.items,
key = {
it.id
},
if (state.pinCodePromptVisible) {
DirectMessagePinCodeGate(
isVerifying = state.pinCodeVerifying,
errorMessage = state.pinCodeErrorMessage,
onSubmit = state::submitPinCode,
modifier = Modifier.padding(contentPadding),
)
} else {
LazyColumn(
state = listState,
reverseLayout = true,
contentPadding = contentPadding,
modifier =
Modifier
.consumeWindowInsets(contentPadding)
.fillMaxSize()
.imePadding()
.imeNestedScroll(),
verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.Bottom),
) {
items(
state.items,
key = {
it.id
},
// emptyContent = {
//
// },
// errorContent = {
//
// },
loadingContent = {
DMLoadingItem()
},
itemContent = { item ->
DMItem(
item = item,
onRetry = {
state.retry(item.key)
},
modifier =
Modifier
.animateItem()
.padding(
horizontal = screenHorizontalPadding,
),
onUserClicked = {
toProfile.invoke(it.key)
},
)
},
)
loadingContent = {
DMLoadingItem()
},
itemContent = { item ->
DMItem(
item = item,
onRetry = {
state.retry(item.key)
},
modifier =
Modifier
.animateItem()
.padding(
horizontal = screenHorizontalPadding,
),
onUserClicked = {
toProfile.invoke(it.key)
},
)
},
)
}
}
}
}
Expand Down
53 changes: 31 additions & 22 deletions app/src/main/java/dev/dimension/flare/ui/screen/dm/DMListScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -85,28 +85,37 @@ internal fun DMListScreen(
)
},
) { contentPadding ->
RefreshContainer(
modifier =
Modifier
.fillMaxSize(),
indicatorPadding = contentPadding,
isRefreshing = state.isRefreshing,
onRefresh = state::refresh,
content = {
LazyColumn(
contentPadding = contentPadding,
modifier =
Modifier
.padding(horizontal = screenHorizontalPadding),
verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap),
) {
dmList(
data = state.items,
onItemClicked = onItemClicked,
)
}
},
)
if (state.pinCodePromptVisible) {
DirectMessagePinCodeGate(
isVerifying = state.pinCodeVerifying,
errorMessage = state.pinCodeErrorMessage,
onSubmit = state::submitPinCode,
modifier = Modifier.padding(contentPadding),
)
} else {
RefreshContainer(
modifier =
Modifier
.fillMaxSize(),
indicatorPadding = contentPadding,
isRefreshing = state.isRefreshing,
onRefresh = state::refresh,
content = {
LazyColumn(
contentPadding = contentPadding,
modifier =
Modifier
.padding(horizontal = screenHorizontalPadding),
verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap),
) {
dmList(
data = state.items,
onItemClicked = onItemClicked,
)
}
},
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package dev.dimension.flare.ui.screen.dm

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedSecureTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import dev.dimension.flare.R

@Composable
internal fun DirectMessagePinCodeGate(
isVerifying: Boolean,
errorMessage: String?,
onSubmit: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val pinCode = rememberTextFieldState()
val submit by rememberUpdatedState(onSubmit)

Box(
modifier = modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Column(
modifier =
Modifier
.widthIn(max = 360.dp)
.fillMaxWidth()
.padding(24.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResource(id = R.string.dm_pin_code_title),
style = MaterialTheme.typography.titleMedium,
)
Text(
text = stringResource(id = R.string.dm_pin_code_message),
style = MaterialTheme.typography.bodyMedium,
)
OutlinedSecureTextField(
state = pinCode,
modifier = Modifier.fillMaxWidth(),
enabled = !isVerifying,
label = {
Text(text = stringResource(id = R.string.dm_pin_code_label))
},
keyboardOptions =
KeyboardOptions(
keyboardType = KeyboardType.NumberPassword,
),
)
if (errorMessage != null) {
Text(
text = errorMessage,
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
)
}
Button(
modifier = Modifier.fillMaxWidth(),
enabled = !isVerifying && pinCode.text.isNotBlank(),
onClick = {
submit(pinCode.text.toString())
},
) {
Text(text = stringResource(id = android.R.string.ok))
}
}
}
}
3 changes: 3 additions & 0 deletions app/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,9 @@
<string name="dm_sending">正在发送</string>
<string name="dm_leave">退出对话</string>
<string name="dm_to_profile">显示个人资料</string>
<string name="dm_pin_code_title">输入 XChat PIN</string>
<string name="dm_pin_code_message">此 XChat 身份需要先输入 PIN 才能加载私信。</string>
<string name="dm_pin_code_label">PIN 码</string>
<string name="notification_login_expired">%1$s 的登录已过期</string>
<string name="following_title">正在关注</string>
<string name="fans_title">关注者</string>
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,9 @@
<string name="dm_sending">Sending</string>
<string name="dm_leave">Leave conversation</string>
<string name="dm_to_profile">Show profile</string>
<string name="dm_pin_code_title">Enter XChat PIN</string>
<string name="dm_pin_code_message">This XChat identity requires a PIN before direct messages can be loaded.</string>
<string name="dm_pin_code_label">PIN code</string>


<string name="notification_login_expired">Login expired for %1$s</string>
Expand Down
Loading
Loading