Skip to content
Merged
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
66 changes: 66 additions & 0 deletions .kotlin/errors/errors-1758100641387.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
kotlin version: 2.1.0
error message: Daemon compilation failed: Connection to the Kotlin daemon has been unexpectedly lost. This might be caused by the daemon being killed by another process or the operating system, or by JVM crash.
org.jetbrains.kotlin.gradle.tasks.DaemonCrashedException: Connection to the Kotlin daemon has been unexpectedly lost. This might be caused by the daemon being killed by another process or the operating system, or by JVM crash.
at org.jetbrains.kotlin.gradle.tasks.TasksUtilsKt.wrapAndRethrowCompilationException(tasksUtils.kt:55)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:243)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:187)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:120)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:162)
at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:264)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:128)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:133)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:157)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:126)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: java.rmi.UnmarshalException: Error unmarshaling return header; nested exception is:
java.net.SocketException: Connection reset
at java.rmi/sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:255)
at java.rmi/sun.rmi.server.UnicastRef.invoke(UnicastRef.java:165)
at java.rmi/java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:215)
at java.rmi/java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:160)
at jdk.proxy4/jdk.proxy4.$Proxy139.compile(Unknown Source)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.incrementalCompilationWithDaemon(GradleKotlinCompilerWork.kt:331)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:235)
... 38 more
Caused by: java.net.SocketException: Connection reset
at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:328)
at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:355)
at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:808)
at java.base/java.net.Socket$SocketInputStream.read(Socket.java:966)
at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:244)
at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:263)
at java.base/java.io.DataInputStream.readUnsignedByte(DataInputStream.java:288)
at java.base/java.io.DataInputStream.readByte(DataInputStream.java:268)
at java.rmi/sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:241)
... 44 more


4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ android {
applicationId = "com.example.chaining"
minSdk = 24
targetSdk = 35
versionCode = 1
versionName = "1.0"
versionCode = 2
versionName = "1.0.2"

buildConfigField("String", "DATA_OPEN_API_KEY", properties["DATA_OPEN_API_KEY"].toString())

Expand Down
Binary file modified app/release/app-release.aab
Binary file not shown.
20 changes: 10 additions & 10 deletions app/src/main/assets/korean_quizzes.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"problem": "화장실은 ______에 있어요?",
"translation": "Where is the restroom?",
"options": ["어디", "언제", "누구", "무엇"],
"answer": "어디 (Where)"
"answer": "어디"
},
{
"id": "kor_lv1_fb_02",
Expand All @@ -57,7 +57,7 @@
"problem": "이거 ______예요?",
"translation": "How much is this?",
"options": ["얼마", "왜", "어떻게", "몇"],
"answer": "얼마 (How much)"
"answer": "얼마"
},
{
"id": "kor_lv2_so_01",
Expand Down Expand Up @@ -107,7 +107,7 @@
"problem": "이 버스, 시청에 ______?",
"translation": "Does this bus go to the city hall?",
"options": ["가요", "와요", "먹어요", "자요"],
"answer": "가요 (go)"
"answer": "가요"
},
{
"id": "kor_lv2_fb_02",
Expand All @@ -117,7 +117,7 @@
"problem": "계산서 좀 ______.",
"translation": "The bill, please.",
"options": ["앉으세요", "주세요", "입으세요", "읽으세요"],
"answer": "주세요 (give me)"
"answer": "주세요"
},
{
"id": "kor_lv3_so_01",
Expand Down Expand Up @@ -167,7 +167,7 @@
"problem": "창가 쪽 자리로 ______.",
"translation": "I'd like a window seat, please.",
"options": ["부탁드립니다", "괜찮습니다", "모릅니다", "없습니다"],
"answer": "부탁드립니다 (I'd like to request)"
"answer": "부탁드립니다"
},
{
"id": "kor_lv3_fb_02",
Expand All @@ -177,7 +177,7 @@
"problem": "와이파이 비밀번호가 ______?",
"translation": "What is the Wi-Fi password?",
"options": ["뭐예요", "예뻐요", "비싸요", "멀어요"],
"answer": "뭐예요 (what is)"
"answer": "뭐예요"
},
{
"id": "kor_lv4_so_01",
Expand Down Expand Up @@ -227,7 +227,7 @@
"problem": "이 자리에 다른 분이 ______?",
"translation": "Is this seat taken? (Did someone else sit here?)",
"options": ["앉으셨어요", "서셨어요", "주무셨어요", "오셨어요"],
"answer": "앉으셨어요 (sat down)"
"answer": "앉으셨어요"
},
{
"id": "kor_lv4_fb_02",
Expand All @@ -237,7 +237,7 @@
"problem": "제가 한국 드라마를 좋아해서 한국어를 ______ 됐어요.",
"translation": "I like K-dramas, so I came to learn Korean.",
"options": ["배우게", "가르치게", "만들게", "보게"],
"answer": "배우게 (came to learn)"
"answer": "배우게"
},
{
"id": "kor_lv5_so_01",
Expand Down Expand Up @@ -287,7 +287,7 @@
"problem": "그 친구는 약속을 밥 ______ 어겨서 믿을 수가 없어.",
"translation": "I can't trust that friend because they break promises as habitually as they eat meals.",
"options": ["먹듯이", "보듯이", "자듯이", "입듯이"],
"answer": "먹듯이 (like eating)"
"answer": "먹듯이"
},
{
"id": "kor_lv5_fb_02",
Expand All @@ -297,6 +297,6 @@
"problem": "한국의 배달 문화는 ______ 감탄스러울 정도예요.",
"translation": "Korea's delivery culture is, once again, impressive.",
"options": ["새삼", "감히", "차마", "결코"],
"answer": "새삼 (newly, once again)"
"answer": "새삼"
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,9 @@ class ApplicationRepository
)

val postOwnerId = application.owner.id

val newNotificationKey =
rootRef.child("notifications")
.child(postOwnerId).push().key ?: error("알림 ID 생성 실패")

val notification =
Notification(
id = newNotificationKey,
Expand All @@ -76,6 +74,7 @@ class ApplicationRepository
createdAt = System.currentTimeMillis(),
isRead = false,
uid = postOwnerId,
closeAt = application.closeAt,
)

updates["/notifications/$postOwnerId/$newNotificationKey"] = notification
Expand Down Expand Up @@ -175,7 +174,6 @@ class ApplicationRepository
return Result.failure(Exception("이미 처리된 지원서입니다."))
}
// 멀티패스 업데이트 경로 구성
println("호시기" + application.applicant.id + application.applicationId)
val updates =
hashMapOf<String, Any?>(
// 1. applications 노드에 지원서 저장
Expand Down Expand Up @@ -205,6 +203,7 @@ class ApplicationRepository
status = value,
createdAt = System.currentTimeMillis(),
isRead = false,
closeAt = application.closeAt,
)
updates["/notifications/${application.applicant.id}/$newNotificationKey"] = notification
// 원자적 업데이트 수행
Expand Down
138 changes: 100 additions & 38 deletions app/src/main/java/com/example/chaining/data/repository/UserRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -106,43 +106,6 @@ class UserRepository
auth.removeAuthStateListener(authStateListener)
}
}
// fun observeMyUser(): Flow<User?> = callbackFlow {
// val uid = uidOrThrow()
// val ref = usersRef().child(uid)
//
// val listener = object : ValueEventListener {
// override fun onDataChange(snapshot: DataSnapshot) {
// val user = snapshot.getValue(User::class.java)?.copy(id = uid)
// if (user != null) {
// // Firebase → Room DB에 저장
// val entity = user.toEntity()
// CoroutineScope(Dispatchers.IO).launch {
// userDao.insertUser(entity)
// }
// }
// }
//
// override fun onCancelled(error: DatabaseError) {
// close(error.toException())
// }
// }
//
// ref.addValueEventListener(listener)
//
// // Room DB Flow 구독 → UI에 전달
// val dbFlow = userDao.getUser(uid)
// val job = CoroutineScope(Dispatchers.IO).launch {
// dbFlow.collect { entity ->
// val user = entity?.toUser() // UserEntity → User 변환
// trySend(user).isSuccess
// }
// }
//
// awaitClose {
// ref.removeEventListener(listener)
// job.cancel()
// }
// }

/** Update (관심글 추가 / 삭제) */
suspend fun toggleLikedPost(
Expand Down Expand Up @@ -180,13 +143,112 @@ class UserRepository
/** 프로필 사진 변경 */
suspend fun updateProfileImage(newUrl: String) {
val uid = uidOrThrow()
usersRef().child(uid).child("profileImageUrl").setValue(newUrl).await()
// usersRef().child(uid).child("profileImageUrl").setValue(newUrl).await()

// 1. users/{uid}/profileImageUrl 업데이트
val updates =
hashMapOf<String, Any?>(
"/users/$uid/profileImageUrl" to newUrl,
)

val myFollowingSnapshot = usersRef().child(uid).child("following").get().await()
for (followingSnap in myFollowingSnapshot.children) {
val followedUid = followingSnap.key ?: continue

updates["/users/$followedUid/follower/$uid/profileImageUrl"] = newUrl
}

// 2. 내가 작성한 posts (owner)
val myPosts = usersRef().child(uid).child("recruitPosts").get().await()
for (postSnap in myPosts.children) {
val postId = postSnap.key ?: continue
updates["/posts/$postId/owner/profileImageUrl"] = newUrl
updates["/users/$uid/posts/$postId/owner/profileImageUrl"] = newUrl
}

// 3. 내가 작성한 applications (applicant)
val myApplications = usersRef().child(uid).child("applications").get().await()
for (appSnap in myApplications.children) {
val applicationId = appSnap.key ?: continue
updates["/applications/$applicationId/applicant/profileImageUrl"] = newUrl
updates["/users/$uid/applications/$applicationId/applicant/profileImageUrl"] = newUrl
}

// 4. notifications에서 sender.id == uid 인 것들 갱신
val notifications = rootRef.child("notifications").get().await()
for (userSnap in notifications.children) {
val targetUid = userSnap.key ?: continue
for (notiSnap in userSnap.children) {
val senderId = notiSnap.child("sender/id").getValue(String::class.java)
val notiId = notiSnap.key ?: continue
if (senderId == uid) {
updates["/notifications/$targetUid/$notiId/sender/profileImageUrl"] = newUrl
}
}
}

// 5. 원자적 업데이트 실행
rootRef.updateChildren(updates).await()

val current = userDao.getUser(uid).firstOrNull() ?: return
val updatedEntity = current.copy(profileImageUrl = newUrl)
userDao.updateUser(updatedEntity)
}

/** 닉네임 변경 */
suspend fun updateNickname(newNickname: String) {
val uid = uidOrThrow()

// 1. users/{uid}/profileImageUrl 업데이트
val updates =
hashMapOf<String, Any?>(
"/users/$uid/nickname" to newNickname,
)

val myFollowingSnapshot = usersRef().child(uid).child("following").get().await()
for (followingSnap in myFollowingSnapshot.children) {
val followedUid = followingSnap.key ?: continue

updates["/users/$followedUid/follower/$uid/nickname"] = newNickname
}

// 2. 내가 작성한 posts (owner)
val myPosts = usersRef().child(uid).child("recruitPosts").get().await()
for (postSnap in myPosts.children) {
val postId = postSnap.key ?: continue
updates["/posts/$postId/owner/nickname"] = newNickname
updates["/users/$uid/posts/$postId/owner/nickname"] = newNickname
}

// 3. 내가 작성한 applications (applicant)
val myApplications = usersRef().child(uid).child("applications").get().await()
for (appSnap in myApplications.children) {
val applicationId = appSnap.key ?: continue
updates["/applications/$applicationId/applicant/nickname"] = newNickname
updates["/users/$uid/applications/$applicationId/applicant/nickname"] = newNickname
}

// 4. notifications에서 sender.id == uid 인 것들 갱신
val notifications = rootRef.child("notifications").get().await()
for (userSnap in notifications.children) {
val targetUid = userSnap.key ?: continue
for (notiSnap in userSnap.children) {
val senderId = notiSnap.child("sender/id").getValue(String::class.java)
val notiId = notiSnap.key ?: continue
if (senderId == uid) {
updates["/notifications/$targetUid/$notiId/sender/nickname"] = newNickname
}
}
}

// 5. 원자적 업데이트 실행
rootRef.updateChildren(updates).await()

val current = userDao.getUser(uid).firstOrNull() ?: return
val updatedEntity = current.copy(nickname = newNickname)
userDao.updateUser(updatedEntity)
}

/** 테스트 결과 변경 */
suspend fun updateTestResult(languagePref: LanguagePref) {
val uid = uidOrThrow()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ data class Application(
// Soft Delete 플래그 추가
@get:PropertyName("isDeleted")
val isDeleted: Boolean = false,
val closeAt: Long = 0L,
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.example.chaining.domain.model

import java.util.UUID

data class QuizItem(
// 고유 ID
val id: String = "",
Expand Down Expand Up @@ -29,3 +31,9 @@ enum class QuizType {
// 문장 빈칸 채우기
FILL_IN_THE_BLANK,
}

data class WordChip(
val text: String,
// 각 단어에 고유한 ID를 자동으로 부여
val id: UUID = UUID.randomUUID(),
)
Loading
Loading