Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
dd64899
[BOOK-259] chore: 사용자 액션 함수 ~ed 제거
easyhooon Aug 22, 2025
aa59266
[BOOK-259] refactor: 내부 블럭에 코루틴 스코프를 사용하지 않는 LaunchedEffect -> Rememb…
easyhooon Aug 22, 2025
958a988
Create README.md
easyhooon Aug 22, 2025
17b9b5f
[BOOK-259] chore: 사용하지 않는 라이브러리 의존성 제거
easyhooon Aug 22, 2025
bc7a4bb
[BOOK-259] chore: LaunchedEffect로 롭백
easyhooon Aug 22, 2025
d609cb3
Update README.md
easyhooon Aug 26, 2025
0b1c98b
[BOOK-298] feat: 비회원 도서 검색 API 연동
easyhooon Aug 27, 2025
04fea0f
[BOOK-298] feat: Guest Login 홈 화면 진입 시 화면 분기 처리 구현
easyhooon Aug 27, 2025
d66bbfb
[BOOK-298] feat: 도서 상태 업데이트 API 호출시 Guest Mode 분기 처리
easyhooon Aug 27, 2025
c927738
[BOOK-298] feat: 로그인 FullScreenDialog 구성
easyhooon Aug 27, 2025
4ea2bd0
[BOOK-298] feat: UiText 도입
easyhooon Aug 28, 2025
b08e865
Update README.md
easyhooon Aug 28, 2025
0d53f60
[BOOK-298] feat: Guest 모드 로그인 화면 Redirect 및 로그인 후 원래 화면으로 돌아오기 구현
easyhooon Aug 28, 2025
bd64507
[BOOK-298] feat: 도서 검색 화면 진입, 도서 검색 이벤트 로그 수집 분리
easyhooon Aug 28, 2025
0655dbf
[BOOK-298] feat: ReedTextButton 추가
easyhooon Aug 28, 2025
d107b0a
[BOOK-298] chore: Login 화면 ReedCloseAppBar visible 분기 처리
easyhooon Aug 28, 2025
bda573a
[BOOK-298] chore: code style check success
easyhooon Aug 28, 2025
e081cd5
[BOOK-298] chore: ReedTextButton colors ButtonDefaults.textButtonColo…
easyhooon Aug 28, 2025
6c87639
[BOOK-298] fix: 로그아웃/회원탈퇴 화면 전환 이전에 다이얼로그/바텀시트가 닫히도록 flag 호출 순서 변경
easyhooon Aug 28, 2025
8416732
[BOOK-298] refactor: BookRegisteredState enum class, BookSummaryModel…
easyhooon Aug 28, 2025
38edf2c
[BOOK-298] chore: 로그인 화면 리다이렉트로 진입시 누락된 로그인 버튼 하당 패딩 적용
easyhooon Aug 28, 2025
74f4d91
[BOOK-298] chore: 토끼 리뷰 반영
easyhooon Aug 28, 2025
e7ece1c
[BOOK-298] refactor: Guest Mode 홈/내 서재 화면 API 호출되지 않는 방식으로 변경
easyhooon Aug 28, 2025
84ef4e5
[BOOK-298] chore: 사용하지 않는 라이브러리 의존성 제거
easyhooon Aug 28, 2025
94317d8
[BOOK-298] fix: 내 서재 화면 rememberEffect key userState로 변경
easyhooon Aug 28, 2025
74f49c2
[BOOK-298] chore: 중첩 조건문 제거
easyhooon Aug 28, 2025
477bb27
Update README.md
easyhooon Aug 28, 2025
26825c5
[BOOK-298] chore: 도서 검색 결과 없음 로그 수집 제거
easyhooon Aug 28, 2025
ca25219
[BOOK-298] chore: app version update
easyhooon Aug 28, 2025
993f756
Merge pull request #174 from YAPP-Github/BOOK-298-feature/#172
easyhooon Aug 28, 2025
9fdbfd5
Update README.md
easyhooon Aug 28, 2025
6e782bc
[BOOK-304] feat: ReedLoadingIndicator에 delay 추가
seoyoon513 Aug 29, 2025
0388dd9
[BOOK-304] chore: MLKit 관련 코드 및 의존성 제거
seoyoon513 Aug 29, 2025
902ad8e
[BOOK-304] fix: BookItem 영역까지 스크롤 범위 수정
seoyoon513 Aug 29, 2025
7a9ad69
[BOOK-304] refactor: 바텀시트로 화면 이동 시 delayedGoTo를 사용하지 않고 hide() 후 전환되도…
seoyoon513 Aug 29, 2025
343afd2
[BOOK-304] fix: 시스템 카메라 권한 요청 시 PermissionDialog가 동시에 뜨는 문제 해결
seoyoon513 Aug 29, 2025
67043a1
[BOOK-304] fix: TextField 포커스 되지 않은 상태에서 감상문 선택 시 포커싱 되지 않는 문제 해결
seoyoon513 Aug 29, 2025
08252b1
[BOOK-304] chore: code style check success
seoyoon513 Aug 29, 2025
3e5c5f4
Merge branch 'develop' into BOOK-304-feature/#173
seoyoon513 Aug 29, 2025
5be1d31
[BOOK-304] fix: 기록 저장 시 중복 저장되는 문제를 막기 위해 로딩 인디케이터 delay를 0L로 설정
seoyoon513 Aug 29, 2025
09933a9
Update README.md
seoyoon513 Aug 29, 2025
85fe843
[BOOK-304] chore: 사용하지 않는 라이브러리 의존성 제거
easyhooon Aug 30, 2025
7099e42
Merge pull request #168 from YAPP-Github/BOOK-259-chore/#141
easyhooon Aug 30, 2025
e079b91
[BOOK-304] feat: 로그인 화면에서 약관동의화면 이동시 returnToScreen 전달
easyhooon Aug 31, 2025
657f67d
[BOOK-304] refactor: remember + LaunchedEffect를 produceState로 변경
seoyoon513 Sep 1, 2025
48dc971
Merge pull request #175 from YAPP-Github/BOOK-304-feature/#173
seoyoon513 Sep 1, 2025
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
148 changes: 148 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Reed - 문장과 감정을 함께 담는 독서 기록

[![Kotlin](https://img.shields.io/badge/Kotlin-2.2.0-blue.svg)](https://kotlinlang.org)
[![Gradle](https://img.shields.io/badge/gradle-8.11.1-green.svg)](https://gradle.org/)
[![Android Studio](https://img.shields.io/badge/Android%20Studio-2025.1.2%20%28Narwhal%29-green)](https://developer.android.com/studio)
[![minSdkVersion](https://img.shields.io/badge/minSdkVersion-28-red)](https://developer.android.com/distribute/best-practices/develop/target-sdk)
[![targetSdkVersion](https://img.shields.io/badge/targetSdkVersion-35-orange)](https://developer.android.com/distribute/best-practices/develop/target-sdk)
![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/YAPP-Github/Reed-Android?utm_source=oss&utm_medium=github&utm_campaign=YAPP-Github%2FReed-Android&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews)
<br/>
<img width="1024" height="500" alt="reed_graphic" src="https://github.com/user-attachments/assets/357cab12-db36-4de0-8fad-664abc5df8c8" />

<a href="https://play.google.com/store/apps/details?id=com.ninecraft.booket&hl=ko">
<img src="https://github.com/user-attachments/assets/8699ffd3-3399-45eb-9e2b-88818ba62091" width="200" />
</a>

<p align="center">
<img src="https://github.com/user-attachments/assets/f31d5681-bbf0-4de4-93a6-37a2adf54df7" width="30%"/>
<img src="https://github.com/user-attachments/assets/db92e159-091d-425b-8cc2-2324d4191463" width="30%"/>
<img src="https://github.com/user-attachments/assets/5fd3f726-f493-4850-9d99-3933815dcd8b" width="30%"/>
</p>
<p align="center">
<img src="https://github.com/user-attachments/assets/bb7e3281-c7e4-453d-a921-cc4e9a051434" width="30%"/>
<img src="https://github.com/user-attachments/assets/7c211781-58e5-4413-9f20-f92b1d0c8f24" width="30%"/>
</p>

## Features
| 홈 | 도서 검색 및 등록 | 내서재 |
|:---:|:---:|:---:|
| <img width="230" alt="홈" src="https://github.com/user-attachments/assets/657e14e1-e578-4d7c-8c38-3591d61b1429" /> | <img width="230" alt="도서 검색 및 등록" src="https://github.com/user-attachments/assets/7a166abf-1160-4cdf-8ec5-f8071d7f0891" /> | <img width="230" alt="내서재" src="https://github.com/user-attachments/assets/d4679cd4-0188-4763-9adc-e210bef44ef0" /> |

| OCR | 기록 등록 | 도서 & 기록 상세 |
|:---:|:---:|:---:|
| <img width="230" alt="OCR" src="https://github.com/user-attachments/assets/8861b7ea-a0f6-4e26-91be-c15450259752" /> | <img width="230" alt="기록 등록" src="https://github.com/user-attachments/assets/32774861-c9fa-439c-a889-ccbc842eb528" /> | <img width="230" alt="도서 & 기록 상세" src="https://github.com/user-attachments/assets/34304a37-7de1-4876-bd55-f041077b3266" /> |

| 기록 카드 공유 |
|:---:|
| <img width="230" alt="기록 카드 공유" src="https://github.com/user-attachments/assets/4c01a5ed-e5a2-4be4-b950-96a457c87ad7" /> |

## TroubleShooting
- [[Compose] M3 ModalBottomSheet 드래그(터치 이벤트) 막는 법](https://velog.io/@mraz3068/Compose-M3-ModalBottomSheet-Drag-Disabled)
- [Circuit 찍먹해보기(부제: Circuit 희망편)](https://speakerdeck.com/easyhooon/circuit-jjigmeoghaebogi-buje-circuit-hyimangpyeon)
- [Circuit 찍먹해보기(부제: Circuit 절망편)](https://speakerdeck.com/easyhooon/circuit-jjigmeoghaebogi-buje-circuit-jeolmangpyeon)
- [Jetpack Compose에서 CameraX + MLKit으로 OCR을 구현해보자](https://velog.io/@syoon513/Jetpack-Compose%EC%97%90%EC%84%9C-CameraX-MLKit%EC%9C%BC%EB%A1%9C-OCR%EC%9D%84-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EC%9E%90)
- [[Android] 일회성 이벤트를 StateFlow, Compose의 State로 처리할 때 주의해야할 점](https://velog.io/@mraz3068/Handle-One-Time-Event-As-State)
- [Circuit Navigation 사용 시 feature 모듈간의 참조는 어떻게 해결했을까?](https://velog.io/@syoon513/Circuit-Navigation-%EC%82%AC%EC%9A%A9-%EC%8B%9C-feature-%EB%AA%A8%EB%93%88%EA%B0%84-%EC%88%9C%ED%99%98-%EC%B0%B8%EC%A1%B0%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%B4%EA%B2%B0%ED%96%88%EC%9D%84%EA%B9%8C)
- [Coroutine 에러 처리 패턴: 여러 API 호출을 한 번에 성공/실패 판정하기](https://velog.io/@syoon513/Coroutine-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC)
- [[Circuit] ImpressionEffect](https://velog.io/@mraz3068/Circuit-ImpressionEffect)
- [Coroutine CancellationException 따로 처리해야하는 케이스](https://velog.io/@mraz3068/Coroutine-CancellationException-UseCase)

## Development

### Required

- IDE : Android Studio 최신 버전
- JDK : Java 17을 실행할 수 있는 JDK
- (권장) Android Studio 설치 시 Embedded 된 JDK (Open JDK)
- Java 17을 사용하는 JDK (Open JDK, AdoptOpenJDK, GraalVM)
- Kotlin Language : 2.2.0

### Language

- Kotlin

### Libraries

- AndroidX
- Activity Compose
- Core
- DataStore
- StartUp
- Splash
- CameraX

- Kotlin Libraries (Coroutine, Serialization, Immutable Collection)
- Compose
- Material3

- [Circuit](https://github.com/slackhq/circuit)
- ~~Google ML Kit~~ Google Cloud Vision
- Dagger Hilt
- Retrofit, OkHttp3
- Lottie-Compose
- Firebase(Analytics, Crashlytics, Remote Config)
- Kakao-Auth
- [Logger](https://github.com/orhanobut/logger)
- [Compose-Stable-Marker](https://github.com/skydoves/compose-stable-marker)
- [Landscapist](https://github.com/skydoves/landscapist), Coil-Compose
- [ComposeExtensions](https://github.com/taehwandev/ComposeExtensions)
- [compose-effects](https://github.com/skydoves/compose-effects)
- [compose-shadow](https://github.com/adamglin0/compose-shadow)

#### Test & Code analysis

- Ktlint
- Detekt

#### Gradle Dependency

- Gradle Version Catalog

## Architecture
- Android App Architecture
- MVI

## Developers

|Android|Android|
|:---:|:---:|
|[이지훈](https://github.com/easyhooon)|[이서윤](https://github.com/seoyoon513)|
|<img width="144" src="https://github.com/user-attachments/assets/7e54768a-ce44-421d-8c03-9df7d0492855">|<img width="144" src="https://github.com/user-attachments/assets/a6cea688-cf9b-41ad-a3a0-9a0c11775fa2">|

## Module
<img width="1631" height="719" alt="image" src="https://github.com/user-attachments/assets/f6a26dcd-761a-4dab-ae3f-a32e87eb423b" />

## Package Structure
```
├── app
│   └── application
├── build-logic
├── core
│   ├── common
│   ├── data-api
│   ├── data-impl
│   ├── datastore-api
│   ├── datastore-impl
│   ├── designsystem
│   ├── model
│   ├── network
│   ├── ocr
│   └── ui
├── feature
│   ├── detail
│   ├── edit
│   ├── home
│   ├── library
│   ├── login
│   ├── main
│   ├── onboarding
│   ├── record
│   ├── screens
│   ├── settings
│   ├── splash
│   └── webview
├── gradle
   └── libs.versions.toml

```
<br/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.ninecraft.booket.core.common.utils

import android.content.Context
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource

// https://www.youtube.com/watch?v=mB1Lej0aDus
sealed class UiText {
data class DirectString(val value: String) : UiText()

class StringResource(
@StringRes val resId: Int,
vararg val args: Any,
) : UiText()

@Composable
fun asString() = when (this) {
is DirectString -> value
is StringResource -> stringResource(resId, *args)
}

fun asString(context: Context) = when (this) {
is DirectString -> value
is StringResource -> context.getString(resId, *args)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.ninecraft.booket.core.data.api.repository

import com.ninecraft.booket.core.model.AutoLoginState
import com.ninecraft.booket.core.model.UserState
import kotlinx.coroutines.flow.Flow

interface AuthRepository {
Expand All @@ -11,4 +12,8 @@ interface AuthRepository {
suspend fun withdraw(): Result<Unit>

val autoLoginState: Flow<AutoLoginState>

val userState: Flow<UserState>

suspend fun getCurrentUserState(): UserState
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ interface BookRepository {
val bookRecentSearches: Flow<List<String>>
val libraryRecentSearches: Flow<List<String>>

suspend fun searchBookAsGuest(
query: String,
start: Int,
): Result<BookSearchModel>

suspend fun searchBook(
query: String,
start: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import com.ninecraft.booket.core.network.response.BookSearchResponse
import com.ninecraft.booket.core.network.response.BookSummary
import com.ninecraft.booket.core.network.response.BookUpsertResponse
import com.ninecraft.booket.core.network.response.Category
import com.ninecraft.booket.core.network.response.GuestBookSearchResponse
import com.ninecraft.booket.core.network.response.GuestBookSummary
import com.ninecraft.booket.core.network.response.HomeResponse
import com.ninecraft.booket.core.network.response.LibraryBookSummary
import com.ninecraft.booket.core.network.response.LibraryBooks
Expand Down Expand Up @@ -79,6 +81,35 @@ internal fun BookSummary.toModel(): BookSummaryModel {
)
}

internal fun GuestBookSearchResponse.toModel(): BookSearchModel {
return BookSearchModel(
version = version,
title = title,
pubDate = pubDate,
totalResults = totalResults,
startIndex = startIndex,
itemsPerPage = itemsPerPage,
query = query,
searchCategoryId = searchCategoryId,
searchCategoryName = searchCategoryName,
lastPage = lastPage,
books = books.map { it.toModel() },
)
}

internal fun GuestBookSummary.toModel(): BookSummaryModel {
return BookSummaryModel(
isbn13 = isbn13,
title = title.decodeHtmlEntities(),
author = author,
publisher = publisher,
coverImageUrl = coverImageUrl,
link = link,
userBookStatus = "BEFORE_REGISTRATION",
key = "$title-$isbn13",
)
}

internal fun BookDetailResponse.toModel(): BookDetailModel {
return BookDetailModel(
version = version,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.ninecraft.booket.core.common.utils.runSuspendCatching
import com.ninecraft.booket.core.data.api.repository.AuthRepository
import com.ninecraft.booket.core.datastore.api.datasource.TokenDataSource
import com.ninecraft.booket.core.model.AutoLoginState
import com.ninecraft.booket.core.model.UserState
import com.ninecraft.booket.core.network.request.LoginRequest
import com.ninecraft.booket.core.network.service.ReedService
import kotlinx.coroutines.flow.map
Expand Down Expand Up @@ -48,9 +49,16 @@ internal class DefaultAuthRepository @Inject constructor(

override val autoLoginState = tokenDataSource.accessToken
.map { accessToken ->
when {
accessToken.isBlank() -> AutoLoginState.NOT_LOGGED_IN
else -> AutoLoginState.LOGGED_IN
}
if (accessToken.isBlank()) AutoLoginState.NOT_LOGGED_IN else AutoLoginState.LOGGED_IN
}

override val userState = tokenDataSource.accessToken
.map { accessToken ->
if (accessToken.isBlank()) UserState.Guest else UserState.LoggedIn
}

override suspend fun getCurrentUserState(): UserState {
val accessToken = tokenDataSource.getAccessToken()
return if (accessToken.isBlank()) UserState.Guest else UserState.LoggedIn
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ internal class DefaultBookRepository @Inject constructor(
override val bookRecentSearches = bookRecentSearchDataSource.recentSearches
override val libraryRecentSearches = libraryRecentSearchDataSource.recentSearches

override suspend fun searchBookAsGuest(
query: String,
start: Int,
) = runSuspendCatching {
val result = service.searchBookAsGuest(
query = query,
start = start,
).toModel()

bookRecentSearchDataSource.addRecentSearch(query)
result
}

override suspend fun searchBook(
query: String,
start: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@ package com.ninecraft.booket.core.designsystem.component.button

import androidx.compose.foundation.BorderStroke
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.ninecraft.booket.core.designsystem.theme.Kakao
import com.ninecraft.booket.core.designsystem.theme.ReedTheme

enum class ReedButtonColorStyle {
PRIMARY, SECONDARY, TERTIARY, STROKE, KAKAO;
PRIMARY, SECONDARY, TERTIARY, STROKE, TEXT, KAKAO;

@Composable
fun containerColor(isPressed: Boolean) = when (this) {
PRIMARY -> if (isPressed) ReedTheme.colors.bgPrimaryPressed else ReedTheme.colors.bgPrimary
SECONDARY -> if (isPressed) ReedTheme.colors.bgSecondaryPressed else ReedTheme.colors.bgSecondary
TERTIARY -> if (isPressed) ReedTheme.colors.bgTertiaryPressed else ReedTheme.colors.bgTertiary
STROKE -> if (isPressed) ReedTheme.colors.basePrimary else ReedTheme.colors.basePrimary
TEXT -> Color.Transparent
KAKAO -> Kakao
}

Expand All @@ -24,11 +26,15 @@ enum class ReedButtonColorStyle {
SECONDARY -> ReedTheme.colors.contentPrimary
TERTIARY -> ReedTheme.colors.contentBrand
STROKE -> ReedTheme.colors.contentBrand
TEXT -> ReedTheme.colors.borderBrand
KAKAO -> ReedTheme.colors.contentPrimary
}

@Composable
fun disabledContainerColor() = ReedTheme.colors.bgDisabled
fun disabledContainerColor() = when (this) {
TEXT -> Color.Transparent
else -> ReedTheme.colors.bgDisabled
}

@Composable
fun disabledContentColor() = ReedTheme.colors.contentDisabled
Expand Down
Loading
Loading