Skip to content

Commit 7079540

Browse files
authored
Merge pull request #187 from YAPP-Github/feat/4th-sprint
[feat] #179 #180 #181 #184 Sprint 4: 사진 이동/복제 + 앨범 삭제
2 parents f9ee230 + c8575ab commit 7079540

File tree

38 files changed

+1348
-165
lines changed

38 files changed

+1348
-165
lines changed

app/src/main/java/com/neki/android/app/main/MainScreen.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,39 @@
11
package com.neki.android.app.main
22

3-
import androidx.compose.animation.ContentTransform
4-
import androidx.compose.animation.EnterTransition
5-
import androidx.compose.animation.ExitTransition
63
import androidx.activity.compose.rememberLauncherForActivityResult
74
import androidx.activity.result.PickVisualMediaRequest
85
import androidx.activity.result.contract.ActivityResultContracts
6+
import androidx.compose.animation.ContentTransform
7+
import androidx.compose.animation.EnterTransition
8+
import androidx.compose.animation.ExitTransition
99
import androidx.compose.foundation.layout.PaddingValues
1010
import androidx.compose.foundation.layout.fillMaxSize
1111
import androidx.compose.foundation.layout.navigationBarsPadding
1212
import androidx.compose.foundation.layout.padding
13-
import androidx.compose.runtime.LaunchedEffect
1413
import androidx.compose.material3.Scaffold
1514
import androidx.compose.runtime.Composable
15+
import androidx.compose.runtime.LaunchedEffect
1616
import androidx.compose.runtime.getValue
1717
import androidx.compose.runtime.mutableStateOf
1818
import androidx.compose.runtime.remember
1919
import androidx.compose.runtime.snapshots.SnapshotStateList
2020
import androidx.compose.ui.Modifier
2121
import androidx.compose.ui.platform.LocalContext
22+
import androidx.core.net.toUri
2223
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
2324
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2425
import androidx.navigation3.runtime.NavEntry
2526
import androidx.navigation3.runtime.NavKey
2627
import androidx.navigation3.ui.NavDisplay
2728
import com.neki.android.app.main.component.AddPhotoBottomSheet
2829
import com.neki.android.app.main.component.AlbumUploadOption
29-
import com.neki.android.app.ui.BottomNavigationBar
3030
import com.neki.android.app.main.component.SelectWithAlbumDialog
31+
import com.neki.android.app.ui.BottomNavigationBar
3132
import com.neki.android.core.navigation.result.LocalResultEventBus
3233
import com.neki.android.core.navigation.result.ResultEffect
3334
import com.neki.android.core.ui.component.LoadingDialog
3435
import com.neki.android.core.ui.compose.collectWithLifecycle
3536
import com.neki.android.core.ui.toast.NekiToast
36-
import androidx.core.net.toUri
37-
import kotlinx.collections.immutable.ImmutableList
38-
import kotlinx.collections.immutable.persistentListOf
3937
import com.neki.android.feature.archive.api.ArchiveNavKey
4038
import com.neki.android.feature.archive.api.PhotoUploadedResult
4139
import com.neki.android.feature.map.api.MapNavKey
@@ -44,6 +42,8 @@ import com.neki.android.feature.photo_upload.api.PhotoUploadNavKey
4442
import com.neki.android.feature.photo_upload.api.QRScanResult
4543
import com.neki.android.feature.pose.api.PoseNavKey
4644
import com.neki.android.feature.select_album.api.SelectAlbumAction
45+
import kotlinx.collections.immutable.ImmutableList
46+
import kotlinx.collections.immutable.persistentListOf
4747
import timber.log.Timber
4848

4949
@Composable
@@ -95,6 +95,7 @@ fun MainRoute(
9595
MainSideEffect.OpenGallery -> photoPicker.launch(
9696
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly),
9797
)
98+
9899
is MainSideEffect.NavigateToSelectAlbum -> navigateToSelectAlbum(sideEffect.action)
99100
is MainSideEffect.ShowToast -> nekiToast.showToast(sideEffect.message)
100101
MainSideEffect.RefreshArchive -> resultBus.sendResult(result = PhotoUploadedResult, allowDuplicate = false)
@@ -127,7 +128,6 @@ fun MainScreen(
127128
val shouldShowBottomBar by remember(currentKey) {
128129
mutableStateOf(currentKey in topLevelKeys)
129130
}
130-
131131
Scaffold(
132132
modifier = Modifier
133133
.fillMaxSize()

app/src/main/java/com/neki/android/app/main/component/AddPhotoBottomSheet.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ private fun AddPhotoBottomSheetContent(
8080
onClick = onClickQRScan,
8181
)
8282
AddPhotoOptionButton(
83-
iconRes = R.drawable.icon_add_photo,
83+
iconRes = R.drawable.icon_add_photo_gallery,
8484
label = "갤러리에서 추가",
8585
onClick = onClickGallery,
8686
)

core/data-api/src/main/java/com/neki/android/core/dataapi/repository/FolderRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ interface FolderRepository {
88
suspend fun deleteFolder(id: List<Long>, deletePhotos: Boolean): Result<Unit>
99
suspend fun removePhotosFromFolder(folderId: Long, photoIds: List<Long>): Result<Unit>
1010
suspend fun updateFolder(folderId: Long, name: String): Result<Unit>
11+
suspend fun movePhotos(sourceFolderId: Long, photoIds: List<Long>, targetFolderIds: List<Long>): Result<Unit>
12+
suspend fun copyPhotos(photoIds: List<Long>, targetFolderIds: List<Long>): Result<Unit>
1113
}

core/data/src/main/java/com/neki/android/core/data/remote/api/FolderService.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.neki.android.core.data.remote.api
22

3+
import com.neki.android.core.data.remote.model.request.CopyPhotosRequest
34
import com.neki.android.core.data.remote.model.request.CreateFolderRequest
45
import com.neki.android.core.data.remote.model.request.DeleteFolderRequest
56
import com.neki.android.core.data.remote.model.request.DeletePhotoRequest
7+
import com.neki.android.core.data.remote.model.request.MovePhotosRequest
68
import com.neki.android.core.data.remote.model.request.UpdateFolderRequest
79
import com.neki.android.core.data.remote.model.response.BasicNullableResponse
810
import com.neki.android.core.data.remote.model.response.BasicResponse
@@ -52,4 +54,14 @@ class FolderService @Inject constructor(
5254
setBody(requestBody)
5355
}.body()
5456
}
57+
58+
// 사진 이동
59+
suspend fun movePhotos(requestBody: MovePhotosRequest): BasicNullableResponse<Unit> {
60+
return client.patch("/api/folders/photos/move") { setBody(requestBody) }.body()
61+
}
62+
63+
// 사진 복제
64+
suspend fun copyPhotos(requestBody: CopyPhotosRequest): BasicNullableResponse<Unit> {
65+
return client.post("/api/folders/photos/copy") { setBody(requestBody) }.body()
66+
}
5567
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.neki.android.core.data.remote.model.request
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class CopyPhotosRequest(
8+
@SerialName("photoIds") val photoIds: List<Long>,
9+
@SerialName("targetFolderIds") val targetFolderIds: List<Long>,
10+
)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.neki.android.core.data.remote.model.request
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class MovePhotosRequest(
8+
@SerialName("sourceFolderId") val sourceFolderId: Long,
9+
@SerialName("photoIds") val photoIds: List<Long>,
10+
@SerialName("targetFolderIds") val targetFolderIds: List<Long>,
11+
)

core/data/src/main/java/com/neki/android/core/data/repository/impl/FolderRepositoryImpl.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package com.neki.android.core.data.repository.impl
22

33
import com.neki.android.core.data.remote.api.FolderService
4+
import com.neki.android.core.data.remote.model.request.CopyPhotosRequest
45
import com.neki.android.core.data.remote.model.request.CreateFolderRequest
56
import com.neki.android.core.data.remote.model.request.DeleteFolderRequest
67
import com.neki.android.core.data.remote.model.request.DeletePhotoRequest
8+
import com.neki.android.core.data.remote.model.request.MovePhotosRequest
79
import com.neki.android.core.data.remote.model.request.UpdateFolderRequest
810
import com.neki.android.core.data.util.runSuspendCatching
911
import com.neki.android.core.dataapi.repository.FolderRepository
@@ -43,4 +45,16 @@ class FolderRepositoryImpl @Inject constructor(
4345
requestBody = UpdateFolderRequest(name = name),
4446
)
4547
}
48+
49+
override suspend fun movePhotos(sourceFolderId: Long, photoIds: List<Long>, targetFolderIds: List<Long>): Result<Unit> = runSuspendCatching {
50+
folderService.movePhotos(
51+
requestBody = MovePhotosRequest(sourceFolderId = sourceFolderId, photoIds = photoIds, targetFolderIds = targetFolderIds),
52+
)
53+
}
54+
55+
override suspend fun copyPhotos(photoIds: List<Long>, targetFolderIds: List<Long>): Result<Unit> = runSuspendCatching {
56+
folderService.copyPhotos(
57+
requestBody = CopyPhotosRequest(photoIds = photoIds, targetFolderIds = targetFolderIds),
58+
)
59+
}
4660
}

core/designsystem/src/main/java/com/neki/android/core/designsystem/actionbar/NekiActionBar.kt

Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package com.neki.android.core.designsystem.actionbar
33
import androidx.compose.foundation.layout.Arrangement
44
import androidx.compose.foundation.layout.Box
55
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.PaddingValues
67
import androidx.compose.foundation.layout.Row
8+
import androidx.compose.foundation.layout.RowScope
79
import androidx.compose.foundation.layout.fillMaxWidth
810
import androidx.compose.foundation.layout.padding
911
import androidx.compose.foundation.layout.size
@@ -21,9 +23,10 @@ import com.neki.android.core.designsystem.button.NekiIconButton
2123
import com.neki.android.core.designsystem.ui.theme.NekiTheme
2224

2325
@Composable
24-
fun NekiStartActionBar(
26+
fun NekiActionBar(
2527
modifier: Modifier = Modifier,
26-
content: @Composable () -> Unit,
28+
padding: PaddingValues = PaddingValues(),
29+
content: @Composable RowScope.() -> Unit,
2730
) {
2831
Column(
2932
modifier = modifier,
@@ -33,18 +36,22 @@ fun NekiStartActionBar(
3336
thickness = 1.dp,
3437
color = NekiTheme.colorScheme.gray75,
3538
)
36-
Box(
37-
modifier = Modifier.fillMaxWidth(),
38-
contentAlignment = Alignment.CenterStart,
39+
Row(
40+
modifier = Modifier
41+
.fillMaxWidth()
42+
.padding(padding),
43+
horizontalArrangement = Arrangement.SpaceBetween,
44+
verticalAlignment = Alignment.CenterVertically,
3945
) {
4046
content()
4147
}
4248
}
4349
}
4450

4551
@Composable
46-
fun NekiEndActionBar(
52+
fun NekiStartActionBar(
4753
modifier: Modifier = Modifier,
54+
padding: PaddingValues = PaddingValues(),
4855
content: @Composable () -> Unit,
4956
) {
5057
Column(
@@ -56,19 +63,21 @@ fun NekiEndActionBar(
5663
color = NekiTheme.colorScheme.gray75,
5764
)
5865
Box(
59-
modifier = Modifier.fillMaxWidth(),
60-
contentAlignment = Alignment.CenterEnd,
66+
modifier = Modifier
67+
.fillMaxWidth()
68+
.padding(padding),
69+
contentAlignment = Alignment.CenterStart,
6170
) {
6271
content()
6372
}
6473
}
6574
}
6675

6776
@Composable
68-
fun NekiBothSidesActionBar(
77+
fun NekiEndActionBar(
6978
modifier: Modifier = Modifier,
70-
startContent: @Composable () -> Unit,
71-
endContent: @Composable () -> Unit,
79+
padding: PaddingValues = PaddingValues(),
80+
content: @Composable () -> Unit,
7281
) {
7382
Column(
7483
modifier = modifier,
@@ -78,17 +87,49 @@ fun NekiBothSidesActionBar(
7887
thickness = 1.dp,
7988
color = NekiTheme.colorScheme.gray75,
8089
)
81-
Row(
82-
modifier = Modifier.fillMaxWidth(),
83-
horizontalArrangement = Arrangement.SpaceBetween,
84-
verticalAlignment = Alignment.CenterVertically,
90+
Box(
91+
modifier = Modifier
92+
.fillMaxWidth()
93+
.padding(padding),
94+
contentAlignment = Alignment.CenterEnd,
8595
) {
86-
startContent()
87-
endContent()
96+
content()
8897
}
8998
}
9099
}
91100

101+
@ComponentPreview
102+
@Composable
103+
private fun NekiActionBarPreview() {
104+
NekiTheme {
105+
NekiActionBar(
106+
modifier = Modifier.fillMaxWidth(),
107+
content = {
108+
NekiIconButton(
109+
modifier = Modifier.padding(8.dp),
110+
onClick = {},
111+
) {
112+
Icon(
113+
imageVector = ImageVector.vectorResource(R.drawable.icon_arrow_left),
114+
contentDescription = null,
115+
tint = NekiTheme.colorScheme.gray900,
116+
)
117+
}
118+
NekiIconButton(
119+
modifier = Modifier.padding(8.dp),
120+
onClick = {},
121+
) {
122+
Icon(
123+
imageVector = ImageVector.vectorResource(R.drawable.icon_bookmark_stroked),
124+
contentDescription = null,
125+
tint = NekiTheme.colorScheme.gray500,
126+
)
127+
}
128+
},
129+
)
130+
}
131+
}
132+
92133
@ComponentPreview
93134
@Composable
94135
private fun NekiStartActionBarPreview() {
@@ -131,37 +172,3 @@ private fun NekiEndActionBarPreview() {
131172
}
132173
}
133174
}
134-
135-
@ComponentPreview
136-
@Composable
137-
private fun NekiBothSidesActionBarPreview() {
138-
NekiTheme {
139-
NekiBothSidesActionBar(
140-
modifier = Modifier.fillMaxWidth(),
141-
startContent = {
142-
NekiIconButton(
143-
modifier = Modifier.padding(8.dp),
144-
onClick = {},
145-
) {
146-
Icon(
147-
imageVector = ImageVector.vectorResource(R.drawable.icon_arrow_left),
148-
contentDescription = null,
149-
tint = NekiTheme.colorScheme.gray900,
150-
)
151-
}
152-
},
153-
endContent = {
154-
NekiIconButton(
155-
modifier = Modifier.padding(8.dp),
156-
onClick = {},
157-
) {
158-
Icon(
159-
imageVector = ImageVector.vectorResource(R.drawable.icon_bookmark_stroked),
160-
contentDescription = null,
161-
tint = NekiTheme.colorScheme.gray500,
162-
)
163-
}
164-
},
165-
)
166-
}
167-
}

core/designsystem/src/main/res/drawable/icon_add_photo.xml renamed to core/designsystem/src/main/res/drawable/icon_add_photo_gallery.xml

File renamed without changes.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="28dp"
3+
android:height="28dp"
4+
android:viewportWidth="28"
5+
android:viewportHeight="28">
6+
<group>
7+
<clip-path
8+
android:pathData="M8.5,3L23.5,3A1.5,1.5 0,0 1,25 4.5L25,19.5A1.5,1.5 0,0 1,23.5 21L8.5,21A1.5,1.5 0,0 1,7 19.5L7,4.5A1.5,1.5 0,0 1,8.5 3z"/>
9+
<path
10+
android:pathData="M8.5,3L23.5,3A1.5,1.5 0,0 1,25 4.5L25,19.5A1.5,1.5 0,0 1,23.5 21L8.5,21A1.5,1.5 0,0 1,7 19.5L7,4.5A1.5,1.5 0,0 1,8.5 3z"
11+
android:strokeWidth="4"
12+
android:fillColor="#00000000"
13+
android:strokeColor="#616575"/>
14+
</group>
15+
<path
16+
android:pathData="M4,7V22.5C4,23.328 4.672,24 5.5,24H21"
17+
android:strokeWidth="2"
18+
android:fillColor="#00000000"
19+
android:strokeColor="#616575"
20+
android:strokeLineCap="round"/>
21+
<path
22+
android:pathData="M12,12H20"
23+
android:strokeWidth="2"
24+
android:fillColor="#00000000"
25+
android:strokeColor="#616575"
26+
android:strokeLineCap="round"/>
27+
<path
28+
android:pathData="M16.006,8.006L16.006,16.006"
29+
android:strokeWidth="2"
30+
android:fillColor="#00000000"
31+
android:strokeColor="#616575"
32+
android:strokeLineCap="round"/>
33+
</vector>

0 commit comments

Comments
 (0)