diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 64377c36d..b11f66945 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -48,6 +48,7 @@ timber = "5.0.1"
mockk = "1.13.8"
tosspayments = "0.1.15"
immutable = "0.3.7"
+reorderable = "0.9.6"
[libraries]
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity-ktx" }
@@ -115,6 +116,7 @@ retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "immutable" }
+reorderable = { group = "org.burnoutcrew.composereorderable", name = "reorderable", version.ref = "reorderable" }
[plugins]
android-application = { id = "com.android.application", version.ref = "android" }
diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts
index 4bfabf9eb..5ad2851a3 100644
--- a/presentation/build.gradle.kts
+++ b/presentation/build.gradle.kts
@@ -93,6 +93,7 @@ dependencies {
implementation(libs.timber)
implementation(libs.zxing.android.embedded)
+ implementation(libs.reorderable)
androidTestImplementation(libs.bundles.android.test)
androidTestImplementation(platform(libs.andoridx.compose.compose.bom))
diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/profileedit/profile/ProfileEditScreen.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/profileedit/profile/ProfileEditScreen.kt
index 03936dc00..8dd0acec8 100644
--- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/profileedit/profile/ProfileEditScreen.kt
+++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/profileedit/profile/ProfileEditScreen.kt
@@ -17,8 +17,11 @@ import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.KeyboardOptions
@@ -72,6 +75,11 @@ import com.nexters.boolti.presentation.util.ObserveAsEvents
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
+import org.burnoutcrew.reorderable.ReorderableItem
+import org.burnoutcrew.reorderable.ReorderableState
+import org.burnoutcrew.reorderable.detectReorder
+import org.burnoutcrew.reorderable.rememberReorderableLazyListState
+import org.burnoutcrew.reorderable.reorderable
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
@@ -147,6 +155,8 @@ fun ProfileEditScreen(
onClickAddLink = { navigateToLinkEdit(null) },
onClickEditSns = { sns -> navigateToSnsEdit(sns) },
onClickEditLink = { link -> navigateToLinkEdit(link) },
+ onReorderSns = viewModel::reorderSns,
+ onReorderLink = viewModel::reorderLink,
)
}
@@ -170,6 +180,8 @@ fun ProfileEditScreen(
onClickAddLink: () -> Unit,
onClickEditSns: (Sns) -> Unit,
onClickEditLink: (Link) -> Unit,
+ onReorderSns: (from: Int, to: Int) -> Unit,
+ onReorderLink: (from: Int, to: Int) -> Unit,
) {
val scrollState = rememberScrollState()
val snackbarHostState = LocalSnackbarController.current
@@ -334,43 +346,91 @@ fun ProfileEditScreen(
)
}
+ val snsReorderState = rememberReorderableLazyListState(
+ onMove = { from, to ->
+ onReorderSns(from.index - 1, to.index - 1)
+ },
+ )
Section(
modifier = Modifier.padding(top = 12.dp),
title = stringResource(R.string.profile_edit_sns_title),
) {
- Column {
- LinkAddButton(
- modifier = Modifier.padding(top = 4.dp),
- label = stringResource(R.string.sns_add),
- onClick = onClickAddSns,
- enabled = !saving,
- )
- snsList.forEach { sns ->
- SnsItem(
- modifier = Modifier.padding(top = 12.dp),
- sns = sns,
- ) { if (!saving) onClickEditSns(sns) }
+ LazyColumn(
+ state = snsReorderState.listState,
+ modifier = Modifier
+ .heightIn(max = 100.dp * (snsList.size + 1)) // 대충 넉넉하게 잡은 높이
+ .reorderable(snsReorderState),
+ ) {
+ item(
+ contentType = "SnsAddButton",
+ ) {
+ LinkAddButton(
+ modifier = Modifier.padding(top = 4.dp),
+ label = stringResource(R.string.sns_add),
+ onClick = onClickAddSns,
+ enabled = !saving,
+ )
+ }
+ items(
+ items = snsList,
+ key = { it.id },
+ contentType = { "SnsItem" },
+ ) { sns ->
+ ReorderableItem(
+ state = snsReorderState,
+ key = sns.id,
+ ) {
+ SnsItem(
+ modifier = Modifier.padding(top = 12.dp),
+ sns = sns,
+ reorderableState = snsReorderState,
+ ) { if (!saving) onClickEditSns(sns) }
+ }
}
}
}
+ val linkReorderState = rememberReorderableLazyListState(
+ onMove = { from, to ->
+ onReorderLink(from.index - 1, to.index - 1)
+ },
+ )
Section(
modifier = Modifier.padding(top = 12.dp),
title = stringResource(R.string.label_links),
) {
- Column {
- LinkAddButton(
- modifier = Modifier.padding(top = 4.dp),
- label = stringResource(R.string.link_add_btn),
- onClick = onClickAddLink,
- enabled = !saving,
- )
- links.forEach { link ->
- LinkItem(
- modifier = Modifier.padding(top = 12.dp),
- title = link.name,
- url = link.url,
- ) { if (!saving) onClickEditLink(link) }
+ LazyColumn(
+ state = linkReorderState.listState,
+ modifier = Modifier
+ .heightIn(max = 100.dp * (links.size + 1)) // 대충 넉넉하게 잡은 높이
+ .reorderable(linkReorderState),
+ ) {
+ item(
+ contentType = "LinkAddButton",
+ ) {
+ LinkAddButton(
+ modifier = Modifier.padding(top = 4.dp),
+ label = stringResource(R.string.link_add_btn),
+ onClick = onClickAddLink,
+ enabled = !saving,
+ )
+ }
+ items(
+ items = links,
+ key = { it.id },
+ contentType = { "LinkItem" },
+ ) { link ->
+ ReorderableItem(
+ state = linkReorderState,
+ key = link.id,
+ ) {
+ LinkItem(
+ modifier = Modifier.padding(top = 12.dp),
+ title = link.name,
+ url = link.url,
+ reorderableState = linkReorderState,
+ ) { if (!saving) onClickEditLink(link) }
+ }
}
}
}
@@ -454,6 +514,7 @@ private fun LinkAddButton(
private fun SnsItem(
sns: Sns,
modifier: Modifier = Modifier,
+ reorderableState: ReorderableState<*>,
onClickEdit: () -> Unit,
) {
Row(
@@ -488,10 +549,13 @@ private fun SnsItem(
color = Grey15,
)
Icon(
- modifier = Modifier.size(20.dp),
- imageVector = ImageVector.vectorResource(R.drawable.ic_edit_pen),
+ modifier = Modifier
+ .padding(start = 20.dp)
+ .size(20.dp)
+ .detectReorder(reorderableState),
+ imageVector = ImageVector.vectorResource(R.drawable.ic_reordable_handle),
tint = Grey50,
- contentDescription = stringResource(R.string.link_edit),
+ contentDescription = stringResource(R.string.sns_reorder_description),
)
}
}
@@ -500,6 +564,7 @@ private fun SnsItem(
private fun LinkItem(
title: String,
url: String,
+ reorderableState: ReorderableState<*>,
modifier: Modifier = Modifier,
onClickEdit: () -> Unit,
) {
@@ -533,10 +598,11 @@ private fun LinkItem(
Icon(
modifier = Modifier
.padding(start = 20.dp)
- .size(20.dp),
- imageVector = ImageVector.vectorResource(R.drawable.ic_edit_pen),
+ .size(20.dp)
+ .detectReorder(reorderableState),
+ imageVector = ImageVector.vectorResource(R.drawable.ic_reordable_handle),
tint = Grey50,
- contentDescription = stringResource(R.string.link_edit),
+ contentDescription = stringResource(R.string.link_reorder_description),
)
}
}
diff --git a/presentation/src/main/java/com/nexters/boolti/presentation/screen/profileedit/profile/ProfileEditViewModel.kt b/presentation/src/main/java/com/nexters/boolti/presentation/screen/profileedit/profile/ProfileEditViewModel.kt
index d7bd915b3..bf625c583 100644
--- a/presentation/src/main/java/com/nexters/boolti/presentation/screen/profileedit/profile/ProfileEditViewModel.kt
+++ b/presentation/src/main/java/com/nexters/boolti/presentation/screen/profileedit/profile/ProfileEditViewModel.kt
@@ -108,6 +108,28 @@ class ProfileEditViewModel @Inject constructor(
event(ProfileEditEvent.OnSnsRemoved)
}
+ fun reorderSns(from: Int, to: Int) {
+ val snsList = uiState.value.snsList.toMutableList()
+ if (from !in snsList.indices || to !in snsList.indices) return
+
+ _uiState.update {
+ it.copy(
+ snsList = snsList.apply { add(to, removeAt(from)) },
+ )
+ }
+ }
+
+ fun reorderLink(from: Int, to: Int) {
+ val links = uiState.value.links.toMutableList()
+ if (from !in links.indices || to !in links.indices) return
+
+ _uiState.update {
+ it.copy(
+ links = links.apply { add(to, removeAt(from)) },
+ )
+ }
+ }
+
fun completeEdits(thumbnailFile: File?) {
viewModelScope.launch(recordExceptionHandler) {
_uiState.update { it.copy(saving = true) }
diff --git a/presentation/src/main/res/drawable/ic_reordable_handle.xml b/presentation/src/main/res/drawable/ic_reordable_handle.xml
new file mode 100644
index 000000000..1ea6eac18
--- /dev/null
+++ b/presentation/src/main/res/drawable/ic_reordable_handle.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml
index 626bf778b..17bdc172a 100644
--- a/presentation/src/main/res/values/strings.xml
+++ b/presentation/src/main/res/values/strings.xml
@@ -298,6 +298,7 @@
SNS를 추가했어요
SNS를 편집했어요
SNS를 삭제했어요
+ SNS 순서 변경
ex) boolti_official
\@을 제외한 Username을 입력해 주세요
지원하지 않는 특수문자가 포함되어 있습니다
@@ -310,6 +311,7 @@
링크 추가
링크 추가
링크 편집
+ 링크 순서 변경
링크 삭제
링크 이름
URL