Skip to content

Commit 826b272

Browse files
authored
๐Ÿ”€ :: (#910) ์ด๋ฏธ์ง€ ์ •์ฑ… ๊ฐ•ํ™”๋กœ ์ธํ•œ ์ด๋ฏธ์ง€ ํ”ฝ์ปค ๊ตฌํ˜„
๐Ÿ”€ :: (#910) ์ด๋ฏธ์ง€ ์ •์ฑ… ๊ฐ•ํ™”๋กœ ์ธํ•œ ์ด๋ฏธ์ง€ ํ”ฝ์ปค ๊ตฌํ˜„
2 parents 9fa6e01 + d2db099 commit 826b272

8 files changed

Lines changed: 103 additions & 160 deletions

File tree

โ€Žapp/src/main/AndroidManifest.xmlโ€Ž

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
<uses-permission android:name="android.permission.CAMERA" />
1212
<uses-permission android:name="android.permission.VIBRATE" />
1313
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
14-
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
15-
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
1614

1715
<application
1816
android:name=".android.app.DmsApplication"

โ€Žapp/src/main/kotlin/team/aliens/dms/android/app/ui/DmsApp.ktโ€Ž

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,12 +234,15 @@ fun DmsApp(
234234
onNavigateSetting = {
235235
backStack.add(SettingScreenNav)
236236
},
237+
onNavigateNotification = {
238+
backStack.add(NotificationScreenNav)
239+
},
240+
onNavigateSelectProfile = {
241+
backStack.add(SelectProfileScreenNav)
242+
},
237243
onShowSnackBar = { snackBarType, message ->
238244
appState.showSnackBar(snackBarType, message)
239245
},
240-
onNavigateNotification = {
241-
backStack.add(NotificationScreenNav)
242-
}
243246
)
244247
}
245248
entry<MealScreenNav> {

โ€Žfeature/build.gradle.ktsโ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ dependencies {
6666

6767
implementation(libs.androidx.core)
6868
implementation(libs.androidx.appcompat)
69+
implementation(libs.androidx.activity.compose)
6970

7071
implementation(libs.androidx.compose)
7172
implementation(libs.androidx.compose.util)

โ€Žfeature/src/main/kotlin/team/aliens/dms/android/feature/main/mypage/navigation/MyPageRoute.ktโ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ fun MyPageRoute(
1010
onNavigatePointHistory: (PointType) -> Unit,
1111
onNavigateSetting: () -> Unit,
1212
onNavigateNotification: () -> Unit,
13+
onNavigateSelectProfile: () -> Unit,
1314
onShowSnackBar: (DmsSnackBarType, String) -> Unit,
1415
) {
1516
MyPage(
1617
onNavigatePointHistory = onNavigatePointHistory,
1718
onNavigateSetting = onNavigateSetting,
1819
onNavigateNotification = onNavigateNotification,
20+
onNavigateSelectProfile = onNavigateSelectProfile,
1921
onShowSnackBar = onShowSnackBar,
2022
)
2123
}

โ€Žfeature/src/main/kotlin/team/aliens/dms/android/feature/main/mypage/ui/MyPageScreen.ktโ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ internal fun MyPage(
3838
onNavigatePointHistory: (PointType) -> Unit,
3939
onNavigateSetting: () -> Unit,
4040
onNavigateNotification: () -> Unit,
41+
onNavigateSelectProfile: () -> Unit,
4142
onShowSnackBar: (DmsSnackBarType, String) -> Unit,
4243
) {
4344
val viewModel: MyPageViewModel = hiltViewModel()
@@ -58,6 +59,7 @@ internal fun MyPage(
5859
state = state,
5960
onNavigatePointHistory = onNavigatePointHistory,
6061
onNavigateNotification = onNavigateNotification,
62+
onNavigateSelectProfile = onNavigateSelectProfile,
6163
onSettingClick = onNavigateSetting,
6264
)
6365
}
@@ -67,6 +69,7 @@ private fun MyPageScreen(
6769
state: MyPageState,
6870
onNavigatePointHistory: (PointType) -> Unit,
6971
onNavigateNotification: () -> Unit,
72+
onNavigateSelectProfile: () -> Unit,
7073
onSettingClick: () -> Unit,
7174
) {
7275
Column(
@@ -103,6 +106,7 @@ private fun MyPageScreen(
103106
schoolName = state.myPage.schoolName,
104107
genderType = state.myPage.sex,
105108
profileImageUrl = state.myPage.profileImageUrl,
109+
onProfileClick = onNavigateSelectProfile,
106110
)
107111
PhraseContent(
108112
phrase = state.myPage.phrase,

โ€Žfeature/src/main/kotlin/team/aliens/dms/android/feature/main/mypage/ui/component/ProfileContent.ktโ€Ž

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package team.aliens.dms.android.feature.main.mypage.ui.component
22

33
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.clickable
45
import androidx.compose.foundation.layout.Arrangement
56
import androidx.compose.foundation.layout.Box
67
import androidx.compose.foundation.layout.Column
@@ -35,6 +36,7 @@ internal fun ProfileContent(
3536
schoolName: String,
3637
genderType: Sex,
3738
profileImageUrl: String?,
39+
onProfileClick: () -> Unit,
3840
modifier: Modifier = Modifier,
3941
) {
4042
Row(
@@ -47,7 +49,8 @@ internal fun ProfileContent(
4749
AsyncImage(
4850
modifier = Modifier
4951
.size(60.dp)
50-
.clip(CircleShape),
52+
.clip(CircleShape)
53+
.clickable(onClick = onProfileClick),
5154
model = profileImageUrl ?: DmsIcon.ProfileDefault,
5255
contentDescription = null,
5356
contentScale = ContentScale.Crop,
Lines changed: 80 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,39 @@
11
package team.aliens.dms.android.feature.profile.ui
22

3-
import android.Manifest
43
import android.net.Uri
5-
import android.os.Build
4+
import androidx.activity.compose.rememberLauncherForActivityResult
5+
import androidx.activity.result.PickVisualMediaRequest
6+
import androidx.activity.result.contract.ActivityResultContracts
67
import androidx.compose.foundation.background
78
import androidx.compose.foundation.border
89
import androidx.compose.foundation.clickable
9-
import androidx.compose.foundation.layout.Arrangement
1010
import androidx.compose.foundation.layout.Box
1111
import androidx.compose.foundation.layout.Column
12-
import androidx.compose.foundation.layout.PaddingValues
13-
import androidx.compose.foundation.layout.aspectRatio
12+
import androidx.compose.foundation.layout.Spacer
1413
import androidx.compose.foundation.layout.fillMaxSize
15-
import androidx.compose.foundation.layout.padding
14+
import androidx.compose.foundation.layout.height
1615
import androidx.compose.foundation.layout.size
1716
import androidx.compose.foundation.layout.systemBarsPadding
18-
import androidx.compose.foundation.lazy.grid.GridCells
19-
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
20-
import androidx.compose.foundation.lazy.grid.items
2117
import androidx.compose.foundation.shape.CircleShape
2218
import androidx.compose.material.icons.Icons
23-
import androidx.compose.material.icons.filled.CheckCircle
24-
import androidx.compose.material.icons.outlined.Circle
19+
import androidx.compose.material.icons.filled.AddAPhoto
2520
import androidx.compose.material3.Icon
21+
import androidx.compose.material3.Text
2622
import androidx.compose.runtime.Composable
2723
import androidx.compose.runtime.LaunchedEffect
2824
import androidx.compose.runtime.getValue
2925
import androidx.compose.runtime.rememberUpdatedState
3026
import androidx.compose.ui.Alignment
3127
import androidx.compose.ui.Modifier
32-
import androidx.compose.ui.graphics.Color
28+
import androidx.compose.ui.draw.clip
3329
import androidx.compose.ui.layout.ContentScale
3430
import androidx.compose.ui.unit.dp
3531
import androidx.core.net.toUri
3632
import androidx.hilt.navigation.compose.hiltViewModel
3733
import androidx.lifecycle.compose.collectAsStateWithLifecycle
3834
import coil.compose.AsyncImage
39-
import com.google.accompanist.permissions.ExperimentalPermissionsApi
40-
import com.google.accompanist.permissions.isGranted
41-
import com.google.accompanist.permissions.rememberPermissionState
42-
import kotlinx.collections.immutable.ImmutableList
43-
import kotlinx.collections.immutable.toPersistentList
4435
import team.aliens.dms.android.core.designsystem.DmsTheme
36+
import team.aliens.dms.android.core.designsystem.labelM
4537
import team.aliens.dms.android.core.designsystem.appbar.DmsTopAppBar
4638
import team.aliens.dms.android.core.designsystem.button.ButtonColor
4739
import team.aliens.dms.android.core.designsystem.button.ButtonType
@@ -50,27 +42,33 @@ import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType
5042
import team.aliens.dms.android.feature.profile.viewmodel.SelectProfileSideEffect
5143
import team.aliens.dms.android.feature.profile.viewmodel.SelectProfileViewModel
5244

53-
@OptIn(ExperimentalPermissionsApi::class)
5445
@Composable
5546
internal fun SelectProfile(
5647
onBack: () -> Unit,
5748
onNavigateAdjustProfile: (String) -> Unit,
5849
onShowSnackBar: (DmsSnackBarType, String) -> Unit,
5950
) {
6051
val viewModel: SelectProfileViewModel = hiltViewModel()
61-
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
62-
Manifest.permission.READ_MEDIA_IMAGES
63-
} else {
64-
Manifest.permission.READ_EXTERNAL_STORAGE
65-
}
66-
val request = rememberPermissionState(permission = permission)
67-
6852
val state by viewModel.uiState.collectAsStateWithLifecycle()
6953
val updatedOnNavigateAdjustProfile by rememberUpdatedState(onNavigateAdjustProfile)
7054
val updatedOnShowSnackBar by rememberUpdatedState(onShowSnackBar)
7155

56+
val updatedOnBack by rememberUpdatedState(onBack)
57+
val photoPickerLauncher = rememberLauncherForActivityResult(
58+
contract = ActivityResultContracts.PickVisualMedia(),
59+
) { uri ->
60+
if (uri != null) {
61+
viewModel.selectImage(uri.toString())
62+
} else if (state.selectedUri.isBlank()) {
63+
updatedOnBack()
64+
}
65+
}
66+
67+
LaunchedEffect(Unit) {
68+
photoPickerLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
69+
}
70+
7271
LaunchedEffect(Unit) {
73-
request.launchPermissionRequest()
7472
viewModel.sideEffect.collect {
7573
when (it) {
7674
is SelectProfileSideEffect.SuccessProfileImage -> updatedOnNavigateAdjustProfile(it.profileImageUrl)
@@ -81,107 +79,91 @@ internal fun SelectProfile(
8179
}
8280
}
8381

84-
LaunchedEffect(request.status.isGranted) {
85-
if (request.status.isGranted) {
86-
viewModel.loadImagesIfNeeded()
87-
}
88-
}
89-
9082
SelectProfileScreen(
9183
onBack = onBack,
9284
uploadProfileImage = viewModel::uploadProfileImage,
93-
uriList = state.uriList.toPersistentList(),
9485
selectedUri = state.selectedUri,
9586
enabled = state.enabled,
96-
onSelectImage = viewModel::selectImage,
87+
onPickImage = {
88+
photoPickerLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
89+
},
9790
)
9891
}
9992

10093
@Composable
10194
private fun SelectProfileScreen(
10295
onBack: () -> Unit,
10396
uploadProfileImage: (Uri?) -> Unit,
104-
uriList: ImmutableList<String>,
10597
selectedUri: String,
10698
enabled: Boolean,
107-
onSelectImage: (String) -> Unit,
99+
onPickImage: () -> Unit,
108100
) {
109101
Column(
110102
modifier = Modifier
111103
.fillMaxSize()
112104
.systemBarsPadding(),
105+
horizontalAlignment = Alignment.CenterHorizontally,
113106
) {
114-
DmsTopAppBar(onBackClick = onBack, actions = {
115-
DmsButton(
116-
onClick = { uploadProfileImage(selectedUri.toUri()) },
117-
text = "์„ ํƒ",
118-
buttonType = ButtonType.Text,
119-
buttonColor = ButtonColor.Primary,
120-
enabled = enabled,
121-
)
122-
},)
123-
LazyVerticalGrid(
124-
columns = GridCells.Fixed(3),
125-
horizontalArrangement = Arrangement.spacedBy(4.dp),
126-
verticalArrangement = Arrangement.spacedBy(4.dp),
127-
contentPadding = PaddingValues(4.dp),
128-
modifier = Modifier.fillMaxSize(),
129-
) {
130-
items(uriList) { uri ->
131-
val isSelected = selectedUri.isNotEmpty() && selectedUri == uri
132-
ImageItem(
133-
imageUri = uri,
134-
isSelected = isSelected,
135-
onClick = { onSelectImage(uri) },
107+
DmsTopAppBar(
108+
onBackClick = onBack,
109+
actions = {
110+
DmsButton(
111+
onClick = { uploadProfileImage(selectedUri.toUri()) },
112+
text = "์„ ํƒ",
113+
buttonType = ButtonType.Text,
114+
buttonColor = ButtonColor.Primary,
115+
enabled = enabled,
136116
)
137-
}
138-
}
117+
},
118+
)
119+
Spacer(modifier = Modifier.weight(1f))
120+
ProfileImagePicker(
121+
selectedUri = selectedUri,
122+
onPickImage = onPickImage,
123+
)
124+
Spacer(modifier = Modifier.weight(1f))
139125
}
140126
}
141127

142128
@Composable
143-
fun ImageItem(
144-
imageUri: String,
145-
isSelected: Boolean,
146-
onClick: () -> Unit
129+
private fun ProfileImagePicker(
130+
selectedUri: String,
131+
onPickImage: () -> Unit,
147132
) {
148-
Box(
149-
modifier = Modifier
150-
.aspectRatio(1f)
151-
.clickable(onClick = onClick)
133+
val hasImage = selectedUri.isNotEmpty()
134+
Column(
135+
horizontalAlignment = Alignment.CenterHorizontally,
152136
) {
153-
AsyncImage(
154-
model = imageUri,
155-
contentDescription = null,
156-
contentScale = ContentScale.Crop,
157-
modifier = Modifier.matchParentSize()
158-
)
159-
if (isSelected) {
160-
Box(
161-
modifier = Modifier
162-
.matchParentSize()
163-
.background(Color.Black.copy(alpha = 0.4f)) // ๋ฐ˜ํˆฌ๋ช… ๊ฒ€์€์ƒ‰
164-
)
165-
}
166-
Icon(
167-
imageVector = if (isSelected) Icons.Filled.CheckCircle else Icons.Outlined.Circle,
168-
contentDescription = if (isSelected) "์„ ํƒ๋จ" else "์„ ํƒ ์•ˆ๋จ",
169-
tint = if (isSelected) DmsTheme.colorScheme.onPrimary else Color.White, // ์„ ํƒ๋˜๋ฉด ๊ฐ•์กฐ์ƒ‰, ์•„๋‹ˆ๋ฉด ํฐ์ƒ‰
137+
Box(
170138
modifier = Modifier
171-
.align(Alignment.TopEnd)
172-
.padding(8.dp)
173-
.size(24.dp)
174-
.background(
175-
color = if (!isSelected) Color.Black.copy(alpha = 0.2f) else Color.Transparent,
176-
shape = CircleShape
139+
.size(160.dp)
140+
.clip(CircleShape)
141+
.background(DmsTheme.colorScheme.surfaceVariant)
142+
.border(2.dp, DmsTheme.colorScheme.primary, CircleShape)
143+
.clickable(onClick = onPickImage),
144+
contentAlignment = Alignment.Center,
145+
) {
146+
if (hasImage) {
147+
AsyncImage(
148+
model = selectedUri,
149+
contentDescription = null,
150+
contentScale = ContentScale.Crop,
151+
modifier = Modifier.matchParentSize(),
177152
)
178-
)
179-
if (isSelected) {
180-
Box(
181-
modifier = Modifier
182-
.matchParentSize()
183-
.border(3.dp, DmsTheme.colorScheme.onPrimary) // ๊ตต์€ ํ…Œ๋‘๋ฆฌ
184-
)
153+
} else {
154+
Icon(
155+
imageVector = Icons.Filled.AddAPhoto,
156+
contentDescription = "์‚ฌ์ง„ ์„ ํƒ",
157+
tint = DmsTheme.colorScheme.primary,
158+
modifier = Modifier.size(48.dp),
159+
)
160+
}
185161
}
162+
Spacer(modifier = Modifier.height(16.dp))
163+
Text(
164+
text = "ํƒญํ•˜์—ฌ ์‚ฌ์ง„ ์„ ํƒ",
165+
style = DmsTheme.typography.labelM,
166+
color = DmsTheme.colorScheme.inverseOnSurface,
167+
)
186168
}
187169
}

0 commit comments

Comments
ย (0)