Skip to content

Commit 7985cfd

Browse files
committed
feat: 기능 전부 구현
1 parent 2a98f05 commit 7985cfd

32 files changed

Lines changed: 1935 additions & 62 deletions

app/src/main/java/com/no5ing/bbibbi/data/datasource/local/LocalDataStorage.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class LocalDataStorage @Inject constructor(val context: Context) {
4545
const val MISSION_WIDGET_PERIOD_KEY = "mission_widget_period"
4646
const val FAMILY_NAME_FEATURE_MAIN_KEY = "family_name_feature_main"
4747
const val FAMILY_NAME_FEATURE_FAMILY_KEY = "family_name_feature_family"
48+
const val AI_IMAGE_TERM_KEY = "ai_image_term"
4849
}
4950

5051
fun logOut() {
@@ -168,6 +169,17 @@ class LocalDataStorage @Inject constructor(val context: Context) {
168169
editor.commit()
169170
}
170171

172+
fun shouldAgreeAiImageTerm(): Boolean {
173+
return preferences.getBoolean(AI_IMAGE_TERM_KEY, true)
174+
}
175+
176+
fun setShouldAgreeAiImageTerm() {
177+
val editor = preferences.edit()
178+
editor.putBoolean(AI_IMAGE_TERM_KEY, false)
179+
editor.apply()
180+
editor.commit()
181+
}
182+
171183
fun getFamilyAndMemberNameFeatureFamily(): Boolean {
172184
return preferences.getBoolean(FAMILY_NAME_FEATURE_FAMILY_KEY, true)
173185
}

app/src/main/java/com/no5ing/bbibbi/data/datasource/network/RestAPI.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ import com.no5ing.bbibbi.data.model.member.MemberRealEmoji
3333
import com.no5ing.bbibbi.data.model.member.MemberRealEmojiList
3434
import com.no5ing.bbibbi.data.model.mission.Mission
3535
import com.no5ing.bbibbi.data.model.notification.NotificationModel
36+
import com.no5ing.bbibbi.data.model.post.AIImageCount
37+
import com.no5ing.bbibbi.data.model.post.AIImageResponse
38+
import com.no5ing.bbibbi.data.model.post.AIPost
3639
import com.no5ing.bbibbi.data.model.post.CalendarBanner
3740
import com.no5ing.bbibbi.data.model.post.CalendarElement
3841
import com.no5ing.bbibbi.data.model.post.DailyCalendarElement
@@ -45,12 +48,15 @@ import com.no5ing.bbibbi.data.model.view.FamilyInviteModel
4548
import com.no5ing.bbibbi.data.model.view.MainPageModel
4649
import com.no5ing.bbibbi.data.model.view.NightMainPageModel
4750
import com.skydoves.sandwich.ApiResponse
51+
import okhttp3.MultipartBody
4852
import retrofit2.http.Body
4953
import retrofit2.http.DELETE
5054
import retrofit2.http.GET
5155
import retrofit2.http.HTTP
56+
import retrofit2.http.Multipart
5257
import retrofit2.http.POST
5358
import retrofit2.http.PUT
59+
import retrofit2.http.Part
5460
import retrofit2.http.Path
5561
import retrofit2.http.Query
5662
import java.time.LocalDate
@@ -194,6 +200,12 @@ interface RestAPI {
194200
@Query("type") type: String? = null,
195201
): ApiResponse<Post>
196202

203+
@POST("v1/posts")
204+
suspend fun createAiPost(
205+
@Body body: CreatePostRequest,
206+
@Query("type") type: String? = null,
207+
): ApiResponse<AIPost>
208+
197209
@POST("v1/posts/image-upload-request")
198210
suspend fun getUploadPostImageRequest(
199211
@Body body: ImageUploadRequest,
@@ -309,6 +321,23 @@ interface RestAPI {
309321
suspend fun getMissionById(
310322
@Path("missionId") missionId: String,
311323
): ApiResponse<Mission>
324+
325+
@Multipart
326+
@POST("v1/ai-images/convert")
327+
suspend fun convertImage(
328+
@Part image: MultipartBody.Part
329+
): ApiResponse<AIImageResponse>
330+
331+
@GET("v1/posts/ai-images")
332+
suspend fun getAiImagePosts(
333+
@Query("page") page: Int?,
334+
@Query("size") size: Int?,
335+
@Query("memberId") memberId: String?,
336+
@Query("sort") sort: String? = "DESC",
337+
): ApiResponse<Pagination<AIPost>>
338+
339+
@GET("v1/posts/ai-images/count")
340+
suspend fun getAiImagePostCount(): ApiResponse<AIImageCount>
312341
}
313342

314343
/**
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.no5ing.bbibbi.data.model.post
2+
3+
import android.os.Parcelable
4+
import com.no5ing.bbibbi.data.model.BaseModel
5+
import kotlinx.parcelize.Parcelize
6+
7+
@Parcelize
8+
data class AIImageCount(
9+
val familyAiImageCount: Int,
10+
val availableAiImageCount: Int,
11+
): Parcelable, BaseModel() {
12+
fun hasAvailableImage(): Boolean {
13+
return availableAiImageCount > 0
14+
}
15+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.no5ing.bbibbi.data.model.post
2+
3+
import android.os.Parcelable
4+
import com.no5ing.bbibbi.data.model.BaseModel
5+
import kotlinx.parcelize.Parcelize
6+
7+
@Parcelize
8+
data class AIImageResponse(
9+
val imageUrl: String
10+
): Parcelable, BaseModel()

app/src/main/java/com/no5ing/bbibbi/data/model/post/Post.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,14 @@ data class Post(
1717
val content: String,
1818
val createdAt: ZonedDateTime,
1919
) : Parcelable, BaseModel()
20+
21+
@Parcelize
22+
data class AIPost(
23+
val postId: String,
24+
val authorId: String,
25+
val type: PostType,
26+
val imageUrl: String,
27+
val authorName: String?,
28+
val authorImageUrl: String?,
29+
val createdAt: ZonedDateTime,
30+
) : Parcelable, BaseModel()
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package com.no5ing.bbibbi.data.model.post
22

33
enum class PostType {
4-
SURVIVAL, MISSION
4+
SURVIVAL, MISSION, AI_IMAGE
55
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.no5ing.bbibbi.data.repository.post
2+
3+
import androidx.paging.Pager
4+
import androidx.paging.PagingConfig
5+
import androidx.paging.PagingData
6+
import com.no5ing.bbibbi.data.datasource.local.MemberCacheProvider
7+
import com.no5ing.bbibbi.data.datasource.network.RestAPI
8+
import com.no5ing.bbibbi.data.datasource.network.response.Pagination
9+
import com.no5ing.bbibbi.data.model.member.Member
10+
import com.no5ing.bbibbi.data.model.post.AIPost
11+
import com.no5ing.bbibbi.data.repository.Arguments
12+
import com.no5ing.bbibbi.data.repository.BasePageSource
13+
import com.no5ing.bbibbi.data.repository.BaseRepository
14+
import com.no5ing.bbibbi.presentation.feature.uistate.family.MainFeedUiState
15+
import com.no5ing.bbibbi.util.parallelMap
16+
import com.skydoves.sandwich.ApiResponse
17+
import com.skydoves.sandwich.mapSuccess
18+
import kotlinx.coroutines.Dispatchers
19+
import kotlinx.coroutines.flow.Flow
20+
import javax.inject.Inject
21+
import javax.inject.Singleton
22+
23+
@Singleton
24+
class GetAIPostsRepository @Inject constructor(
25+
private val memberCacheProvider: MemberCacheProvider,
26+
) : BaseRepository<PagingData<AIPost>>() {
27+
private lateinit var pagingSource: GetAIPostPagingSource
28+
override fun fetch(arguments: Arguments): Flow<PagingData<AIPost>> {
29+
return Pager(
30+
config = PagingConfig(
31+
pageSize = 10,
32+
initialLoadSize = 10,
33+
prefetchDistance = 5
34+
)
35+
) {
36+
pagingSource = GetAIPostPagingSource(restAPI, memberCacheProvider, arguments)
37+
pagingSource
38+
}.flow
39+
}
40+
41+
override fun closeResources() {
42+
super.closeResources()
43+
if (::pagingSource.isInitialized)
44+
pagingSource.invalidate()
45+
}
46+
}
47+
48+
class GetAIPostPagingSource @Inject constructor(
49+
private val restAPI: RestAPI,
50+
private val memberCacheProvider: MemberCacheProvider,
51+
arguments: Arguments
52+
) : BasePageSource<AIPost>(arguments) {
53+
override suspend fun requestAPI(
54+
arguments: Arguments,
55+
loadParams: LoadParams<Int>
56+
): ApiResponse<Pagination<AIPost>> {
57+
return restAPI.getPostApi().getAiImagePosts(
58+
memberId = null,
59+
page = loadParams.key ?: 1,
60+
size = loadParams.loadSize,
61+
).mapSuccess {
62+
Pagination(
63+
currentPage = currentPage,
64+
totalPage = totalPage,
65+
itemPerPage = itemPerPage,
66+
hasNext = hasNext,
67+
results = results.parallelMap(Dispatchers.IO) {
68+
val member = kotlin.runCatching {
69+
memberCacheProvider.getMember(it.authorId)
70+
}.getOrElse { Member.unknown() }
71+
it.copy(authorName = member.name, authorImageUrl = member.imageUrl)
72+
}
73+
)
74+
}
75+
}
76+
77+
}

app/src/main/java/com/no5ing/bbibbi/di/NetworkModule.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,12 @@ object NetworkModule {
6464

6565
builder.build()
6666
}
67+
val isLongTask = request.url.encodedPath.endsWith("/convert")
68+
val response = (if (isLongTask) {
69+
it.withReadTimeout(180, java.util.concurrent.TimeUnit.SECONDS) // 응답 대기 길게
70+
.withWriteTimeout(180, java.util.concurrent.TimeUnit.SECONDS) // 업로드도 길게
71+
} else it).proceed(modifiedRequest)
6772

68-
val response = it.proceed(modifiedRequest)
6973
val elapsed = System.currentTimeMillis() - start
7074
Timber.d("[NetworkModule] ${request.method} ${request.url} ${response.code} ${elapsed}ms")
7175
if (response.code == 426) {

app/src/main/java/com/no5ing/bbibbi/presentation/component/button/CTAButton.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import androidx.compose.animation.core.LinearEasing
44
import androidx.compose.animation.core.animateFloatAsState
55
import androidx.compose.animation.core.tween
66
import androidx.compose.foundation.layout.PaddingValues
7+
import androidx.compose.foundation.layout.RowScope
78
import androidx.compose.foundation.shape.RoundedCornerShape
89
import androidx.compose.material3.Button
910
import androidx.compose.material3.ButtonDefaults
@@ -52,4 +53,35 @@ fun CTAButton(
5253
style = MaterialTheme.bbibbiTypo.bodyOneBold,
5354
)
5455
}
56+
}
57+
58+
@Composable
59+
fun CustomCTAButton(
60+
modifier: Modifier = Modifier,
61+
buttonColor: Color = MaterialTheme.bbibbiScheme.mainYellow,
62+
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
63+
onClick: () -> Unit = {},
64+
isActive: Boolean = true,
65+
byPassCtaIgnore: Boolean = false,
66+
content: @Composable() (RowScope.() -> Unit),
67+
) {
68+
val opacityAlpha: Float by animateFloatAsState(
69+
targetValue = if (isActive) 1f else 0.2f,
70+
animationSpec = tween(
71+
durationMillis = 130,
72+
easing = LinearEasing,
73+
), label = ""
74+
)
75+
Button(
76+
shape = RoundedCornerShape(100.dp),
77+
onClick = { if (isActive || byPassCtaIgnore) onClick() },
78+
colors = ButtonDefaults.buttonColors(
79+
containerColor = buttonColor.copy(
80+
alpha = opacityAlpha
81+
)
82+
),
83+
modifier = modifier,
84+
contentPadding = contentPadding,
85+
content = content,
86+
)
5587
}

app/src/main/java/com/no5ing/bbibbi/presentation/feature/view/common/CustomAlertDialog.kt

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.no5ing.bbibbi.presentation.feature.view.common
22

3+
import androidx.compose.foundation.clickable
34
import androidx.compose.foundation.layout.Arrangement
45
import androidx.compose.foundation.layout.Box
56
import androidx.compose.foundation.layout.Column
@@ -48,13 +49,15 @@ fun CustomAlertDialog(
4849
enabledState: State<Boolean> = remember { mutableStateOf(false) },
4950
title: String,
5051
description: String,
52+
clickableTitle: String? = null,
5153
confirmRequest: () -> Unit = {},
5254
dismissRequest: () -> Unit = {
5355
if (enabledState is MutableState) enabledState.value = false
5456
},
5557
cancelRequest: () -> Unit = {
5658
if (enabledState is MutableState) enabledState.value = false
5759
},
60+
onClickClickableTitle: () -> Unit = {},
5861
confirmMessage: String = stringResource(id = R.string.dialog_confirm),
5962
cancelMessage: String = stringResource(id = R.string.dialog_cancel),
6063
hasCancel: Boolean = true,
@@ -112,12 +115,33 @@ fun CustomAlertDialog(
112115
)
113116
},
114117
text = {
115-
Text(
116-
description,
117-
color = MaterialTheme.bbibbiScheme.textSecondary,
118-
style = MaterialTheme.bbibbiTypo.bodyTwoRegular,
119-
textAlign = TextAlign.Center,
120-
)
118+
Column(
119+
horizontalAlignment = Alignment.CenterHorizontally,
120+
) {
121+
Text(
122+
description,
123+
color = MaterialTheme.bbibbiScheme.textSecondary,
124+
style = MaterialTheme.bbibbiTypo.bodyTwoRegular,
125+
textAlign = TextAlign.Center,
126+
)
127+
if (clickableTitle != null) {
128+
Box(
129+
modifier = Modifier
130+
.padding(top = 8.dp)
131+
.clickable {
132+
onClickClickableTitle()
133+
}
134+
) {
135+
Text(
136+
text = clickableTitle,
137+
style = MaterialTheme.bbibbiTypo.bodyTwoBold,
138+
color = MaterialTheme.bbibbiScheme.mainYellow,
139+
textAlign = TextAlign.Center,
140+
)
141+
}
142+
}
143+
}
144+
121145
},
122146
shape = RoundedCornerShape(14.dp),
123147
containerColor = MaterialTheme.bbibbiScheme.backgroundSecondary,

0 commit comments

Comments
 (0)