From ae6fbc614ae93198a92015b9d16d0f971a653757 Mon Sep 17 00:00:00 2001 From: fromitive Date: Thu, 28 Nov 2024 21:09:13 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=8B=9C=EA=B7=B8=EB=8B=88=EC=B2=98=20=EB=B2=88=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android.yml | 82 +-- android/app/build.gradle.kts | 157 +++-- .../chongdae/CommentRoomsFragmentTest.kt | 30 + android/app/src/main/AndroidManifest.xml | 16 +- .../java/com/zzang/chongdae/ChongdaeApp.kt | 86 ++- .../source/OfferingLocalDataSourceImpl.kt | 20 +- .../local/source/UserPreferencesDataStore.kt | 64 ++ .../data/mapper/CommentCreatedAtMapper.kt | 11 + .../data/mapper/CommentOfferingInfoMapper.kt | 14 + .../data/mapper/CommentRoomResponseMapper.kt | 14 + .../chongdae/data/mapper/CommentsMapper.kt | 31 + .../data/mapper/CurrentCountMapper.kt | 5 + .../chongdae/data/mapper/FilterMapper.kt | 33 + .../data/mapper/LocalDateTimeMapper.kt | 12 + .../data/mapper/MeetingsResponseMapper.kt | 12 + .../chongdae/data/mapper/MemberMapper.kt | 11 + .../data/mapper/OfferingConditionMapper.kt | 13 + .../mapper/OfferingDetailResponseMapper.kt | 24 + .../chongdae/data/mapper/OfferingMapper.kt | 18 + .../mapper/ParticipationsResponseMapper.kt | 10 + .../chongdae/data/mapper/ProductUrlMapper.kt | 17 + .../mapper/participant/ParticipantsMapper.kt | 38 ++ .../data/remote/api/AuthApiService.kt | 17 + .../data/remote/api/NetworkManager.kt | 6 +- .../data/remote/api/OfferingApiService.kt | 14 - .../remote/dto/request/AccessTokenRequest.kt | 9 + .../dto/response/auth/MemberResponse.kt | 10 + .../offering/OfferingDetailResponse.kt | 1 - .../remote/source/AuthRemoteDataSourceImpl.kt | 21 + .../source/CommentRemoteDataSourceImpl.kt | 48 +- .../source/CommentRoomsDataSourceImpl.kt | 20 +- .../source/OfferingDetailDataSourceImpl.kt | 31 +- .../source/OfferingRemoteDataSourceImpl.kt | 82 +-- .../source/ParticipantRemoteDataSourceImpl.kt | 32 +- .../data/remote/util/CallApiHandler.kt | 4 +- .../data/remote/util/TokensCookieJar.kt | 2 +- .../data/repository/AuthRepositoryImpl.kt | 37 ++ .../repository/CommentDetailRepositoryImpl.kt | 62 +- .../repository/CommentRoomsRepositoryImpl.kt | 24 +- .../OfferingDetailRepositoryImpl.kt | 42 +- .../data/repository/OfferingRepositoryImpl.kt | 120 ++-- .../repository/ParticipantRepositoryImpl.kt | 34 +- .../data/source/AuthRemoteDataSource.kt | 12 + .../data/source/CommentRoomsDataSource.kt | 4 +- .../data/source/OfferingDetailDataSource.kt | 6 +- .../source/ParticipantRemoteDataSource.kt | 4 +- .../source/comment/CommentRemoteDataSource.kt | 4 +- .../offering/OfferingRemoteDataSource.kt | 10 +- .../com/zzang/chongdae/domain/model/Member.kt | 6 + .../chongdae/domain/model/OfferingDetail.kt | 1 - .../domain/paging/OfferingPagingSource.kt | 6 +- .../domain/repository/AuthRepository.kt | 11 + .../repository/CommentDetailRepository.kt | 4 +- .../repository/CommentRoomsRepository.kt | 4 +- .../repository/OfferingDetailRepository.kt | 6 +- .../domain/repository/OfferingRepository.kt | 14 +- .../repository/ParticipantRepository.kt | 4 +- .../zzang/chongdae/domain/util/DataError.kt | 16 + .../com/zzang/chongdae/domain/util/Error.kt | 3 + .../com/zzang/chongdae/domain/util/Result.kt | 21 + .../util/AccessTokenExpirationHandler.kt | 19 + .../presentation/util/BindingAdapters.kt | 11 - .../util/FirebaseAnalyticsManager.kt | 32 + .../presentation/view/MainActivity.kt | 40 -- .../view/comment/CommentRoomsFragment.kt | 12 +- .../view/comment/CommentRoomsViewModel.kt | 74 ++- .../commentdetail/CommentDetailActivity.kt | 48 +- .../commentdetail/CommentDetailViewModel.kt | 458 ++++++------- .../presentation/view/home/HomeFragment.kt | 41 +- .../view/home/OfferingViewModel.kt | 351 +++++----- .../presentation/view/login/LoginActivity.kt | 14 +- .../presentation/view/login/LoginViewModel.kt | 89 +-- .../view/mypage/MyPageFragment.kt | 29 +- .../view/mypage/MyPageViewModel.kt | 86 ++- .../offeringdetail/OfferingDetailFragment.kt | 96 +-- .../offeringdetail/OfferingDetailViewModel.kt | 324 ++++----- .../OnParticipationClickListener.kt | 2 +- .../write/OfferingWriteEssentialFragment.kt | 20 +- .../write/OfferingWriteOptionalFragment.kt | 16 +- .../view/write/OfferingWriteUiModel.kt | 15 + .../view/write/OfferingWriteViewModel.kt | 620 +++++++++--------- .../write/OnOfferingWriteClickListener.kt | 7 + .../res/layout/activity_comment_detail.xml | 16 +- .../src/main/res/layout/activity_login.xml | 2 +- .../main/res/layout/dialog_date_picker.xml | 28 +- .../res/layout/dialog_date_time_picker.xml | 6 +- .../main/res/layout/dialog_update_status.xml | 4 +- .../app/src/main/res/layout/fragment_home.xml | 221 +++---- .../src/main/res/layout/fragment_my_page.xml | 8 +- .../res/layout/fragment_offering_detail.xml | 12 +- .../fragment_offering_write_essential.xml | 8 +- .../fragment_offering_write_optional.xml | 13 +- .../layout/item_comment_room_participant.xml | 2 +- .../res/layout/item_comment_room_proposer.xml | 6 +- .../src/main/res/layout/item_my_comment.xml | 1 - .../app/src/main/res/layout/item_offering.xml | 10 +- .../res/navigation/bottom_menu_navigation.xml | 26 +- android/app/src/main/res/values/strings.xml | 19 +- android/app/src/main/res/values/style.xml | 7 +- .../view/comment/CommentRoomsViewModelTest.kt | 2 +- .../CommentDetailViewModelTest.kt | 2 +- .../view/home/OfferingViewModelTest.kt | 4 +- .../OfferingDetailViewModelTest.kt | 10 +- .../view/write/OfferingWriteViewModelTest.kt | 2 +- .../chongdae/repository/FakeAuthRepository.kt | 8 +- .../repository/FakeCommentDetailRepository.kt | 4 +- .../repository/FakeCommentRoomsRepository.kt | 4 +- .../FakeOfferingDetailRepository.kt | 10 +- .../repository/FakeOfferingRepository.kt | 16 +- .../repository/FakeParticipantRepository.kt | 4 +- .../com/zzang/chongdae/util/TestFixture.kt | 7 +- android/build.gradle.kts | 1 - android/gradle/libs.versions.toml | 106 +-- 113 files changed, 2235 insertions(+), 2208 deletions(-) create mode 100644 android/app/src/androidTest/java/com/zzang/chongdae/CommentRoomsFragmentTest.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/local/source/UserPreferencesDataStore.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/mapper/CommentCreatedAtMapper.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/mapper/CommentOfferingInfoMapper.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/mapper/CommentRoomResponseMapper.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/mapper/CommentsMapper.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/mapper/CurrentCountMapper.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/mapper/FilterMapper.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/mapper/LocalDateTimeMapper.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/mapper/MeetingsResponseMapper.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/mapper/MemberMapper.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/mapper/OfferingConditionMapper.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/mapper/OfferingDetailResponseMapper.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/mapper/OfferingMapper.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/mapper/ParticipationsResponseMapper.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/mapper/ProductUrlMapper.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/mapper/participant/ParticipantsMapper.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/remote/api/AuthApiService.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/remote/dto/request/AccessTokenRequest.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/remote/dto/response/auth/MemberResponse.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/remote/source/AuthRemoteDataSourceImpl.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/repository/AuthRepositoryImpl.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/data/source/AuthRemoteDataSource.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/domain/model/Member.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/domain/repository/AuthRepository.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/domain/util/DataError.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/domain/util/Error.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/domain/util/Result.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/presentation/util/AccessTokenExpirationHandler.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/presentation/util/FirebaseAnalyticsManager.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteUiModel.kt create mode 100644 android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OnOfferingWriteClickListener.kt diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index df1ae7300..4529bfbec 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -1,10 +1,14 @@ -name: Android CI CD +name: Android CI on: + push: + branches: [ "develop-AN" ] + paths: + - 'android/**' pull_request: - branches: - - "develop-AN" - - "release*" + branches: [ "develop-AN" ] + paths: + - 'android/**' defaults: run: @@ -85,7 +89,7 @@ jobs: echo "native_app_key=$NATIVE_APP_KEY" >> ./local.properties - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' @@ -102,71 +106,3 @@ jobs: - name: Run Unit Test run: ./gradlew test - - deploy: - runs-on: ubuntu-latest - needs: build_and_test - if: startsWith(github.event.pull_request.base.ref, 'release-') - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Create google-services.json - env: - GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} - run: | - echo "$GOOGLE_SERVICES_JSON" > app/google-services.json - - - name: Create service_account.json - id: createServiceAccount - run: echo '${{ secrets.SERVICE_ACCOUNT_JSON }}' > app/service_account.json - - - name: Set up environment variable for BuildConfig - env: - BASE_URL: ${{ secrets.BASE_URL }} - TOKEN: ${{ secrets.TOKEN }} - NATIVE_APP_KEY: ${{ secrets.NATIVE_APP_KEY }} - run: | - echo "base_url=$BASE_URL" >> ./local.properties - echo "token=$TOKEN" >> ./local.properties - echo "native_app_key=$NATIVE_APP_KEY" >> ./local.properties - - - name: Cache Gradle - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/buildSrc/**/*.kt') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Build release AAB - run: ./gradlew bundleRelease - - - name: Sign AAB - id: sign - uses: r0adkll/sign-android-release@v1 - with: - releaseDirectory: ./android/app/build/outputs/bundle/release - output: ./android/build/release/signed - signingKeyBase64: ${{ secrets.ENCODED_KEYSTORE }} - alias: ${{ secrets.AN_ALIAS }} - keyStorePassword: ${{ secrets.AN_KEYSTORE_PASSWORD }} - keyPassword: ${{ secrets.AN_KEY_PASSWORD }} - - - name: Upload AAB to Google Play - uses: r0adkll/upload-google-play@v1 - with: - serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} - packageName: com.zzang.chongdae - releaseFiles: ./android/app/build/outputs/bundle/release/app-release.aab - track: "총대마켓 - 비공개 테스트" diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index b7b8cc5c9..add5aac42 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -9,7 +9,6 @@ plugins { id("com.google.gms.google-services") kotlin("plugin.serialization") version "2.0.0" id("com.google.firebase.crashlytics") - id("com.google.dagger.hilt.android") } android { @@ -29,8 +28,8 @@ android { applicationId = "com.zzang.chongdae" minSdk = 26 targetSdk = 34 - versionCode = 6 - versionName = "1.1.4" + versionCode = 2 + versionName = "1.1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunnerArguments["runnerBuilder"] = "de.mannodermaus.junit5.AndroidJUnit5Builder" @@ -50,7 +49,11 @@ android { buildTypes { debug { - isMinifyEnabled = false + isMinifyEnabled = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro", + ) } release { isMinifyEnabled = true @@ -83,93 +86,89 @@ android { } dependencies { + val navigationVersion = "2.7.7" + val fragmentVersion = "1.8.1" + implementation("androidx.core:core-ktx:1.10.1") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.10.0") + implementation("androidx.activity:activity-ktx:1.8.2") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation("androidx.test.ext:junit-ktx:1.1.5") + testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") + testImplementation("org.assertj:assertj-core:3.25.3") + testImplementation("io.kotest:kotest-runner-junit5:5.8.0") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + androidTestImplementation("androidx.test:runner:1.4.0") + androidTestImplementation("org.junit.jupiter:junit-jupiter:5.10.2") + androidTestImplementation("org.assertj:assertj-core:3.25.3") + androidTestImplementation("io.kotest:kotest-runner-junit5:5.8.0") + androidTestImplementation("de.mannodermaus.junit5:android-test-core:1.3.0") + androidTestRuntimeOnly("de.mannodermaus.junit5:android-test-runner:1.3.0") + // Testing Navigation + androidTestImplementation("androidx.navigation:navigation-testing:$navigationVersion") + + implementation("androidx.room:room-runtime:2.6.1") + kapt("androidx.room:room-compiler:2.6.1") + implementation("androidx.room:room-ktx:2.6.1") + implementation("com.google.code.gson:gson:2.8.8") + + implementation("com.github.bumptech.glide:glide:4.12.0") + kapt("com.github.bumptech.glide:compiler:4.12.0") + testImplementation("androidx.arch.core:core-testing:2.1.0") + implementation("com.squareup.okhttp3:mockwebserver:4.12.0") + + implementation("com.squareup.retrofit2:retrofit:2.11.0") + implementation("com.squareup.retrofit2:converter-gson:2.11.0") + + implementation("androidx.room:room-ktx:2.6.1") + + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") + implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0") + + kapt("com.github.bumptech.glide:compiler:4.13.2") + + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0") + implementation("androidx.activity:activity-ktx:1.9.0") + implementation("androidx.fragment:fragment-ktx:1.7.0") + implementation("androidx.core:core-ktx:1.10.1") implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) - implementation(libs.material) - implementation(libs.androidx.activity) - implementation(libs.androidx.fragment) - implementation(libs.androidx.constraintlayout) - - // Test - implementation(libs.androidx.junit) - testImplementation(libs.junit.jupiter) - testImplementation(libs.assertj.core) - testImplementation(libs.kotest.runner.junit5) - testImplementation(libs.core.testing) - - // Android Test - androidTestImplementation(libs.junit.jupiter) - androidTestImplementation(libs.assertj.core) - androidTestImplementation(libs.kotest.runner.junit5) - androidTestImplementation(libs.mannodermaus.test.core) - androidTestImplementation(libs.mannodermaus.test.runner) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.test.runner) - - // Espresso 및 관련 - androidTestImplementation(libs.androidx.espresso.core) - androidTestImplementation(libs.androidx.espresso.contrib) - - // UI Test: Fragment Scenario - debugImplementation(libs.androidx.fragment.testing) - androidTestImplementation(libs.androidx.fragment.testing) - - // DataStore - implementation(libs.androidx.datastore.preferences) - - // Lifecycle - implementation(libs.androidx.lifecycle.runtime.ktx) - implementation(libs.androidx.lifecycle.livedata.ktx) - implementation(libs.androidx.lifecycle.viewmodel.ktx) - - // Room - implementation(libs.androidx.room.runtime) - kapt(libs.androidx.room.compiler) - implementation(libs.androidx.room.ktx) - - // json - implementation(libs.kotlinx.serialization.json) - - // Glide - implementation(libs.glide) - kapt(libs.glide.compiler) - - // Retrofit - implementation(libs.retrofit) - implementation(libs.retrofit.converter.gson) - implementation(libs.retrofit.kotlinx.serialization) // Navigation - implementation(libs.androidx.navigation.fragment) - implementation(libs.androidx.navigation.ui) - androidTestImplementation(libs.androidx.navigation.testing) + implementation("androidx.navigation:navigation-fragment-ktx:$navigationVersion") + implementation("androidx.navigation:navigation-ui-ktx:$navigationVersion") + + // UI Test - Fragment Scenario + debugImplementation("androidx.fragment:fragment-testing-manifest:$fragmentVersion") + androidTestImplementation("androidx.fragment:fragment-testing:$fragmentVersion") + + // Espresso + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + androidTestImplementation("androidx.test:runner:1.4.0") + + // Espresso RecyclerView Actions + androidTestImplementation("androidx.test.espresso:espresso-contrib:3.3.0") // Pagination - implementation(libs.androidx.paging.runtime) + implementation("androidx.paging:paging-runtime-ktx:3.3.0") // WebView - implementation(libs.androidx.webkit) + implementation("androidx.webkit:webkit:1.9.0") // Firebase - implementation(platform(libs.firebase.bom)) - implementation(libs.firebase.analytics) - implementation(libs.firebase.crashlytics) + implementation(platform("com.google.firebase:firebase-bom:33.1.2")) + implementation("com.google.firebase:firebase-analytics") // 카카오 로그인 - implementation(libs.kakao.sdk) + implementation("com.kakao.sdk:v2-all:2.20.3") - // Mockk - implementation(libs.mockwebserver) - testImplementation(libs.mockk) + // data store + implementation("androidx.datastore:datastore-preferences:1.0.0") - // Swipe Refresh Layout - implementation(libs.androidx.swiperefreshlayout) - - // Hilt - implementation(libs.hilt.android) - kapt(libs.hilt.compiler) -} + implementation("com.google.firebase:firebase-crashlytics") -kapt { - correctErrorTypes = true + // mockk + testImplementation("io.mockk:mockk:1.13.10") } diff --git a/android/app/src/androidTest/java/com/zzang/chongdae/CommentRoomsFragmentTest.kt b/android/app/src/androidTest/java/com/zzang/chongdae/CommentRoomsFragmentTest.kt new file mode 100644 index 000000000..82e05c116 --- /dev/null +++ b/android/app/src/androidTest/java/com/zzang/chongdae/CommentRoomsFragmentTest.kt @@ -0,0 +1,30 @@ +package com.zzang.chongdae + +import androidx.fragment.app.testing.FragmentScenario +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.zzang.chongdae.presentation.view.comment.CommentRoomsFragment +import org.junit.Before +import org.junit.Test +import org.junit.jupiter.api.DisplayName +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class CommentRoomsFragmentTest { + private lateinit var scenario: FragmentScenario + + @Before + fun setUp() { + scenario = FragmentScenario.launchInContainer(CommentRoomsFragment::class.java) + } + + @Test + @DisplayName("댓글방 목록으로 이동하면 채팅이라는 텍스트뷰가 보여야 한다") + fun commentRoomTest1() { + // then + onView(withId(R.id.tv_comment_text)).check(matches(isDisplayed())) + } +} diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index d411e8557..236f2f386 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -36,20 +36,8 @@ - - - - - - - - - + android:exported="false" + android:windowSoftInputMode="stateAlwaysHidden|adjustPan" /> ) { - offeringDao.insertAll(offerings) - } +class OfferingLocalDataSourceImpl(private val offeringDao: OfferingDao) : OfferingLocalDataSource { + override suspend fun insertOfferings(offerings: List) { + offeringDao.insertAll(offerings) + } - override suspend fun insertOffering(offering: OfferingEntity) { - offeringDao.insertOffering(offering) - } + override suspend fun insertOffering(offering: OfferingEntity) { + offeringDao.insertOffering(offering) } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/local/source/UserPreferencesDataStore.kt b/android/app/src/main/java/com/zzang/chongdae/data/local/source/UserPreferencesDataStore.kt new file mode 100644 index 000000000..1f257360d --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/local/source/UserPreferencesDataStore.kt @@ -0,0 +1,64 @@ +package com.zzang.chongdae.data.local.source + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.longPreferencesKey +import androidx.datastore.preferences.core.stringPreferencesKey +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class UserPreferencesDataStore(private val dataStore: DataStore) { + val memberIdFlow: Flow = + dataStore.data.map { preferences -> + preferences[MEMBER_ID_KEY] + } + + val nickNameFlow: Flow = + dataStore.data.map { preferences -> + preferences[NICKNAME_KEY] + } + + val accessTokenFlow: Flow = + dataStore.data.map { preferences -> + preferences[ACCESS_TOKEN_KEY] + } + + val refreshTokenFlow: Flow = + dataStore.data.map { preferences -> + preferences[REFRESH_TOKEN_KEY] + } + + suspend fun saveMember( + memberId: Long, + nickName: String, + ) { + dataStore.edit { preferences -> + preferences[MEMBER_ID_KEY] = memberId + preferences[NICKNAME_KEY] = nickName + } + } + + suspend fun saveTokens( + accessToken: String, + refreshToken: String, + ) { + dataStore.edit { preferences -> + preferences[ACCESS_TOKEN_KEY] = accessToken + preferences[REFRESH_TOKEN_KEY] = refreshToken + } + } + + suspend fun removeAllData() { + dataStore.edit { preferences -> + preferences.clear() + } + } + + companion object { + val MEMBER_ID_KEY = longPreferencesKey("member_id_key") + val NICKNAME_KEY = stringPreferencesKey("nickname_key") + val ACCESS_TOKEN_KEY = stringPreferencesKey("access_token_key") + val REFRESH_TOKEN_KEY = stringPreferencesKey("refresh_token_key") + } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/mapper/CommentCreatedAtMapper.kt b/android/app/src/main/java/com/zzang/chongdae/data/mapper/CommentCreatedAtMapper.kt new file mode 100644 index 000000000..987c3bb08 --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/mapper/CommentCreatedAtMapper.kt @@ -0,0 +1,11 @@ +package com.zzang.chongdae.data.mapper + +import com.zzang.chongdae.data.remote.dto.response.comment.CommentCreatedAtResponse +import com.zzang.chongdae.domain.model.CommentCreatedAt + +fun CommentCreatedAtResponse.toDomain(): CommentCreatedAt { + return CommentCreatedAt( + date = this.date.toLocalDate(), + time = this.time.toLocalTime(), + ) +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/mapper/CommentOfferingInfoMapper.kt b/android/app/src/main/java/com/zzang/chongdae/data/mapper/CommentOfferingInfoMapper.kt new file mode 100644 index 000000000..9240ac815 --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/mapper/CommentOfferingInfoMapper.kt @@ -0,0 +1,14 @@ +package com.zzang.chongdae.data.mapper + +import com.zzang.chongdae.data.remote.dto.response.comment.CommentOfferingInfoResponse +import com.zzang.chongdae.domain.model.CommentOfferingInfo + +fun CommentOfferingInfoResponse.toDomain() = + CommentOfferingInfo( + status = this.status, + imageUrl = this.imageUrl, + buttonText = this.buttonText, + message = this.message, + title = this.title, + isProposer = this.isProposer, + ) diff --git a/android/app/src/main/java/com/zzang/chongdae/data/mapper/CommentRoomResponseMapper.kt b/android/app/src/main/java/com/zzang/chongdae/data/mapper/CommentRoomResponseMapper.kt new file mode 100644 index 000000000..d38acfaed --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/mapper/CommentRoomResponseMapper.kt @@ -0,0 +1,14 @@ +package com.zzang.chongdae.data.mapper + +import com.zzang.chongdae.data.remote.dto.response.commentroom.CommentRoomResponse +import com.zzang.chongdae.domain.model.CommentRoom + +fun CommentRoomResponse.toDomain(): CommentRoom { + return CommentRoom( + id = this.offeringId, + title = this.offeringTitle, + latestComment = this.latestComment.content ?: "", + latestCommentTime = this.latestComment.createdAt?.toLocalDateTime(), + isProposer = this.isProposer, + ) +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/mapper/CommentsMapper.kt b/android/app/src/main/java/com/zzang/chongdae/data/mapper/CommentsMapper.kt new file mode 100644 index 000000000..0d8f09e26 --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/mapper/CommentsMapper.kt @@ -0,0 +1,31 @@ +package com.zzang.chongdae.data.mapper + +import com.zzang.chongdae.data.local.model.CommentEntity +import com.zzang.chongdae.data.remote.dto.response.comment.CommentResponse +import com.zzang.chongdae.domain.model.Comment + +fun CommentResponse.toDomain(): Comment { + return Comment( + content = this.content, + commentCreatedAt = this.commentCreatedAtResponse.toDomain(), + isMine = this.isMine, + isProposer = this.isProposer, + nickname = this.nickname, + ) +} + +fun mapToCommentEntity( + offeringId: Long, + commentResponse: CommentResponse, +): CommentEntity { + return CommentEntity( + offeringId = offeringId, + commentId = commentResponse.commentId, + content = commentResponse.content, + isMine = commentResponse.isMine, + isProposer = commentResponse.isProposer, + nickname = commentResponse.nickname, + commentCreatedAtDate = commentResponse.commentCreatedAtResponse.date, + commentCreatedAtTime = commentResponse.commentCreatedAtResponse.time, + ) +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/mapper/CurrentCountMapper.kt b/android/app/src/main/java/com/zzang/chongdae/data/mapper/CurrentCountMapper.kt new file mode 100644 index 000000000..9d7cb36e0 --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/mapper/CurrentCountMapper.kt @@ -0,0 +1,5 @@ +package com.zzang.chongdae.data.mapper + +import com.zzang.chongdae.domain.model.CurrentCount + +fun Int.toCurrentCount() = CurrentCount(this) diff --git a/android/app/src/main/java/com/zzang/chongdae/data/mapper/FilterMapper.kt b/android/app/src/main/java/com/zzang/chongdae/data/mapper/FilterMapper.kt new file mode 100644 index 000000000..1209d172a --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/mapper/FilterMapper.kt @@ -0,0 +1,33 @@ +@file:Suppress("UNUSED_EXPRESSION") + +package com.zzang.chongdae.data.mapper + +import com.zzang.chongdae.data.remote.dto.response.offering.RemoteFilter +import com.zzang.chongdae.data.remote.dto.response.offering.RemoteFilterName +import com.zzang.chongdae.data.remote.dto.response.offering.RemoteFilterType +import com.zzang.chongdae.domain.model.Filter +import com.zzang.chongdae.domain.model.FilterName +import com.zzang.chongdae.domain.model.FilterType + +fun RemoteFilter.toDomain() = + Filter( + name = this.name.toDomain(), + value = this.value, + type = this.type.toDomain(), + ) + +fun RemoteFilterName.toDomain(): FilterName { + return when (this) { + RemoteFilterName.JOINABLE -> FilterName.JOINABLE + RemoteFilterName.IMMINENT -> FilterName.IMMINENT + RemoteFilterName.HIGH_DISCOUNT -> FilterName.HIGH_DISCOUNT + RemoteFilterName.RECENT -> FilterName.RECENT + } +} + +fun RemoteFilterType.toDomain(): FilterType { + return when (this) { + RemoteFilterType.VISIBLE -> FilterType.VISIBLE + RemoteFilterType.INVISIBLE -> FilterType.INVISIBLE + } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/mapper/LocalDateTimeMapper.kt b/android/app/src/main/java/com/zzang/chongdae/data/mapper/LocalDateTimeMapper.kt new file mode 100644 index 000000000..6b4bbe711 --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/mapper/LocalDateTimeMapper.kt @@ -0,0 +1,12 @@ +package com.zzang.chongdae.data.mapper + +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.format.DateTimeFormatter + +fun String.toLocalDateTime(): LocalDateTime = LocalDateTime.parse(this, DateTimeFormatter.ISO_LOCAL_DATE_TIME) + +fun String.toLocalDate(): LocalDate = LocalDate.parse(this, DateTimeFormatter.ISO_LOCAL_DATE) + +fun String.toLocalTime(): LocalTime = LocalTime.parse(this, DateTimeFormatter.ISO_LOCAL_TIME) diff --git a/android/app/src/main/java/com/zzang/chongdae/data/mapper/MeetingsResponseMapper.kt b/android/app/src/main/java/com/zzang/chongdae/data/mapper/MeetingsResponseMapper.kt new file mode 100644 index 000000000..49e75195f --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/mapper/MeetingsResponseMapper.kt @@ -0,0 +1,12 @@ +package com.zzang.chongdae.data.mapper + +import com.zzang.chongdae.data.remote.dto.response.offering.MeetingsResponse +import com.zzang.chongdae.domain.model.Meetings + +fun MeetingsResponse.toDomain() = + Meetings( + meetingDate = this.meetingDate.toLocalDateTime(), + meetingAddress = this.meetingAddress, + meetingAddressDetail = this.meetingAddressDetail, + meetingAddressDong = this.meetingAddressDong, + ) diff --git a/android/app/src/main/java/com/zzang/chongdae/data/mapper/MemberMapper.kt b/android/app/src/main/java/com/zzang/chongdae/data/mapper/MemberMapper.kt new file mode 100644 index 000000000..1e34a613e --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/mapper/MemberMapper.kt @@ -0,0 +1,11 @@ +package com.zzang.chongdae.data.mapper + +import com.zzang.chongdae.data.remote.dto.response.auth.MemberResponse +import com.zzang.chongdae.domain.model.Member + +fun MemberResponse.toDomain(): Member { + return Member( + memberId = this.memberId, + nickName = this.nickname, + ) +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/mapper/OfferingConditionMapper.kt b/android/app/src/main/java/com/zzang/chongdae/data/mapper/OfferingConditionMapper.kt new file mode 100644 index 000000000..1231fc25f --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/mapper/OfferingConditionMapper.kt @@ -0,0 +1,13 @@ +package com.zzang.chongdae.data.mapper + +import com.zzang.chongdae.data.remote.dto.response.offering.RemoteOfferingStatus +import com.zzang.chongdae.domain.model.OfferingCondition + +fun RemoteOfferingStatus.toDomain(): OfferingCondition { + return when (this) { + RemoteOfferingStatus.FULL -> OfferingCondition.FULL + RemoteOfferingStatus.IMMINENT -> OfferingCondition.IMMINENT + RemoteOfferingStatus.CONFIRMED -> OfferingCondition.CONFIRMED + RemoteOfferingStatus.AVAILABLE -> OfferingCondition.AVAILABLE + } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/mapper/OfferingDetailResponseMapper.kt b/android/app/src/main/java/com/zzang/chongdae/data/mapper/OfferingDetailResponseMapper.kt new file mode 100644 index 000000000..e40e3440e --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/mapper/OfferingDetailResponseMapper.kt @@ -0,0 +1,24 @@ +package com.zzang.chongdae.data.mapper + +import com.zzang.chongdae.data.remote.dto.response.offering.OfferingDetailResponse +import com.zzang.chongdae.domain.model.OfferingDetail + +fun OfferingDetailResponse.toDomain() = + OfferingDetail( + id = this.id, + title = this.title, + nickname = this.nickname, + isProposer = this.isProposer, + productUrl = this.productUrl, + dividedPrice = this.dividedPrice, + thumbnailUrl = this.thumbnailUrl, + totalPrice = this.totalPrice, + meetingDate = this.meetingDate.toLocalDateTime(), + currentCount = this.currentCount.toCurrentCount(), + totalCount = this.totalCount, + meetingAddress = this.meetingAddress, + meetingAddressDetail = this.meetingAddressDetail, + description = this.description, + condition = this.condition.toDomain(), + isParticipated = this.isParticipated, + ) diff --git a/android/app/src/main/java/com/zzang/chongdae/data/mapper/OfferingMapper.kt b/android/app/src/main/java/com/zzang/chongdae/data/mapper/OfferingMapper.kt new file mode 100644 index 000000000..09818f45b --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/mapper/OfferingMapper.kt @@ -0,0 +1,18 @@ +package com.zzang.chongdae.data.mapper + +import com.zzang.chongdae.data.remote.dto.response.offering.RemoteOffering +import com.zzang.chongdae.domain.model.Offering + +fun RemoteOffering.toDomain() = + Offering( + id = this.id, + title = this.title, + meetingAddressDong = this.meetingAddressDong ?: "", + thumbnailUrl = this.thumbnailUrl, + totalCount = this.totalCount, + currentCount = this.currentCount, + dividedPrice = this.dividedPrice, + originPrice = this.originPrice, + status = this.status.toDomain(), + isOpen = this.isOpen, + ) diff --git a/android/app/src/main/java/com/zzang/chongdae/data/mapper/ParticipationsResponseMapper.kt b/android/app/src/main/java/com/zzang/chongdae/data/mapper/ParticipationsResponseMapper.kt new file mode 100644 index 000000000..a4bc9365a --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/mapper/ParticipationsResponseMapper.kt @@ -0,0 +1,10 @@ +package com.zzang.chongdae.data.mapper + +import com.zzang.chongdae.data.remote.dto.response.offering.ParticipationResponse +import com.zzang.chongdae.domain.model.Participation + +fun ParticipationResponse.toDomain() = + Participation( + offeringCondition = this.offeringCondition.toDomain(), + currentCount = this.currentCount.toCurrentCount(), + ) diff --git a/android/app/src/main/java/com/zzang/chongdae/data/mapper/ProductUrlMapper.kt b/android/app/src/main/java/com/zzang/chongdae/data/mapper/ProductUrlMapper.kt new file mode 100644 index 000000000..655e20aaf --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/mapper/ProductUrlMapper.kt @@ -0,0 +1,17 @@ +package com.zzang.chongdae.data.mapper + +import com.zzang.chongdae.data.remote.dto.request.ProductUrlRequest +import com.zzang.chongdae.data.remote.dto.response.offering.ProductUrlResponse +import com.zzang.chongdae.domain.model.ProductUrl + +fun ProductUrlResponse.toDomain(): ProductUrl { + return ProductUrl( + imageUrl = this.imageUrl, + ) +} + +fun String.toProductUrlRequest(): ProductUrlRequest { + return ProductUrlRequest( + productUrl = this, + ) +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/mapper/participant/ParticipantsMapper.kt b/android/app/src/main/java/com/zzang/chongdae/data/mapper/participant/ParticipantsMapper.kt new file mode 100644 index 000000000..45901377b --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/mapper/participant/ParticipantsMapper.kt @@ -0,0 +1,38 @@ +package com.zzang.chongdae.data.mapper.participant + +import com.zzang.chongdae.data.remote.dto.response.participants.ParticipantsResponse +import com.zzang.chongdae.data.remote.dto.response.participants.RemoteCount +import com.zzang.chongdae.data.remote.dto.response.participants.RemoteParticipant +import com.zzang.chongdae.data.remote.dto.response.participants.RemoteProposer +import com.zzang.chongdae.domain.model.participant.Participant +import com.zzang.chongdae.domain.model.participant.ParticipantCount +import com.zzang.chongdae.domain.model.participant.Participants +import com.zzang.chongdae.domain.model.participant.Proposer + +fun ParticipantsResponse.toDomain(): Participants { + return Participants( + proposer = this.remoteProposer.toDomain(), + participants = this.participants.map { it.toDomain() }, + participantCount = this.remoteCount.toDomain(), + price = this.price, + ) +} + +fun RemoteProposer.toDomain(): Proposer { + return Proposer( + nickname = this.nickname, + ) +} + +fun RemoteParticipant.toDomain(): Participant { + return Participant( + nickname = this.nickname, + ) +} + +fun RemoteCount.toDomain(): ParticipantCount { + return ParticipantCount( + totalCount = this.totalCount, + currentCount = this.currentCount, + ) +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/remote/api/AuthApiService.kt b/android/app/src/main/java/com/zzang/chongdae/data/remote/api/AuthApiService.kt new file mode 100644 index 000000000..d90aefe39 --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/remote/api/AuthApiService.kt @@ -0,0 +1,17 @@ +package com.zzang.chongdae.data.remote.api + +import com.zzang.chongdae.data.remote.dto.request.AccessTokenRequest +import com.zzang.chongdae.data.remote.dto.response.auth.MemberResponse +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.POST + +interface AuthApiService { + @POST("/auth/login/kakao") + suspend fun postLogin( + @Body accessToken: AccessTokenRequest, + ): Response + + @POST("/auth/refresh") + suspend fun postRefresh(): Response +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/remote/api/NetworkManager.kt b/android/app/src/main/java/com/zzang/chongdae/data/remote/api/NetworkManager.kt index 671e82596..30098821e 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/remote/api/NetworkManager.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/remote/api/NetworkManager.kt @@ -4,8 +4,7 @@ import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFact import com.zzang.chongdae.BuildConfig import com.zzang.chongdae.ChongdaeApp import com.zzang.chongdae.ChongdaeApp.Companion.dataStore -import com.zzang.chongdae.auth.api.AuthApiService -import com.zzang.chongdae.common.datastore.UserPreferencesDataStore +import com.zzang.chongdae.data.local.source.UserPreferencesDataStore import com.zzang.chongdae.data.remote.util.TokensCookieJar import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType @@ -22,8 +21,7 @@ object NetworkManager { } private fun getRetrofit(): Retrofit { - val userDataStore = - UserPreferencesDataStore(ChongdaeApp.chongdaeAppContext.dataStore) + val userDataStore = UserPreferencesDataStore(ChongdaeApp.chongdaeApplicationContext.dataStore) if (instance == null) { val contentType = "application/json".toMediaType() instance = diff --git a/android/app/src/main/java/com/zzang/chongdae/data/remote/api/OfferingApiService.kt b/android/app/src/main/java/com/zzang/chongdae/data/remote/api/OfferingApiService.kt index 57225068b..8107fd35f 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/remote/api/OfferingApiService.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/remote/api/OfferingApiService.kt @@ -1,6 +1,5 @@ package com.zzang.chongdae.data.remote.api -import com.zzang.chongdae.data.remote.dto.request.OfferingModifyRequest import com.zzang.chongdae.data.remote.dto.request.OfferingWriteRequest import com.zzang.chongdae.data.remote.dto.request.ProductUrlRequest import com.zzang.chongdae.data.remote.dto.response.offering.FiltersResponse @@ -12,10 +11,8 @@ import com.zzang.chongdae.data.remote.dto.response.offering.RemoteOffering import okhttp3.MultipartBody import retrofit2.Response import retrofit2.http.Body -import retrofit2.http.DELETE import retrofit2.http.GET import retrofit2.http.Multipart -import retrofit2.http.PATCH import retrofit2.http.POST import retrofit2.http.Part import retrofit2.http.Path @@ -63,15 +60,4 @@ interface OfferingApiService { suspend fun postProductImageS3( @Part image: MultipartBody.Part, ): Response - - @PATCH("/offerings/{offering-id}") - suspend fun patchOffering( - @Path("offering-id") offeringId: Long, - @Body offeringModifyRequest: OfferingModifyRequest, - ): Response - - @DELETE("/offerings/{offering-id}") - suspend fun deleteOffering( - @Path("offering-id") offeringId: Long, - ): Response } diff --git a/android/app/src/main/java/com/zzang/chongdae/data/remote/dto/request/AccessTokenRequest.kt b/android/app/src/main/java/com/zzang/chongdae/data/remote/dto/request/AccessTokenRequest.kt new file mode 100644 index 000000000..a73a07ffa --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/remote/dto/request/AccessTokenRequest.kt @@ -0,0 +1,9 @@ +package com.zzang.chongdae.data.remote.dto.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class AccessTokenRequest( + @SerialName("accessToken") val accessToken: String, +) diff --git a/android/app/src/main/java/com/zzang/chongdae/data/remote/dto/response/auth/MemberResponse.kt b/android/app/src/main/java/com/zzang/chongdae/data/remote/dto/response/auth/MemberResponse.kt new file mode 100644 index 000000000..74fceb3b7 --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/remote/dto/response/auth/MemberResponse.kt @@ -0,0 +1,10 @@ +package com.zzang.chongdae.data.remote.dto.response.auth + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class MemberResponse( + @SerialName("memberId") val memberId: Long, + @SerialName("nickname") val nickname: String, +) diff --git a/android/app/src/main/java/com/zzang/chongdae/data/remote/dto/response/offering/OfferingDetailResponse.kt b/android/app/src/main/java/com/zzang/chongdae/data/remote/dto/response/offering/OfferingDetailResponse.kt index e4db2180d..efcde9653 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/remote/dto/response/offering/OfferingDetailResponse.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/remote/dto/response/offering/OfferingDetailResponse.kt @@ -17,7 +17,6 @@ data class OfferingDetailResponse( @SerialName("thumbnailUrl") val thumbnailUrl: String?, @SerialName("dividedPrice") val dividedPrice: Int, @SerialName("totalPrice") val totalPrice: Int, - @SerialName("originPrice") val originPrice: Int?, @SerialName("status") val condition: RemoteOfferingStatus, @SerialName("isProposer") val isProposer: Boolean, @SerialName("nickname") val nickname: String, diff --git a/android/app/src/main/java/com/zzang/chongdae/data/remote/source/AuthRemoteDataSourceImpl.kt b/android/app/src/main/java/com/zzang/chongdae/data/remote/source/AuthRemoteDataSourceImpl.kt new file mode 100644 index 000000000..fa01a5385 --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/remote/source/AuthRemoteDataSourceImpl.kt @@ -0,0 +1,21 @@ +package com.zzang.chongdae.data.remote.source + +import com.zzang.chongdae.data.remote.api.AuthApiService +import com.zzang.chongdae.data.remote.dto.request.AccessTokenRequest +import com.zzang.chongdae.data.remote.dto.response.auth.MemberResponse +import com.zzang.chongdae.data.remote.util.safeApiCall +import com.zzang.chongdae.data.source.AuthRemoteDataSource +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result + +class AuthRemoteDataSourceImpl( + private val service: AuthApiService, +) : AuthRemoteDataSource { + override suspend fun saveLogin(accessTokenRequest: AccessTokenRequest): Result { + return safeApiCall { service.postLogin(accessTokenRequest) } + } + + override suspend fun saveRefresh(): Result { + return safeApiCall { service.postRefresh() } + } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/remote/source/CommentRemoteDataSourceImpl.kt b/android/app/src/main/java/com/zzang/chongdae/data/remote/source/CommentRemoteDataSourceImpl.kt index 88df30413..7977fb2fd 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/remote/source/CommentRemoteDataSourceImpl.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/remote/source/CommentRemoteDataSourceImpl.kt @@ -1,7 +1,5 @@ package com.zzang.chongdae.data.remote.source -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result import com.zzang.chongdae.data.remote.api.CommentApiService import com.zzang.chongdae.data.remote.dto.request.CommentRequest import com.zzang.chongdae.data.remote.dto.response.comment.CommentOfferingInfoResponse @@ -9,31 +7,29 @@ import com.zzang.chongdae.data.remote.dto.response.comment.CommentsResponse import com.zzang.chongdae.data.remote.dto.response.comment.UpdatedStatusResponse import com.zzang.chongdae.data.remote.util.safeApiCall import com.zzang.chongdae.data.source.comment.CommentRemoteDataSource -import com.zzang.chongdae.di.annotations.CommentDetailApiServiceQualifier -import javax.inject.Inject +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result -class CommentRemoteDataSourceImpl - @Inject - constructor( - @CommentDetailApiServiceQualifier private val service: CommentApiService, - ) : CommentRemoteDataSource { - override suspend fun saveComment(commentRequest: CommentRequest): Result = - safeApiCall { - service.postComment(commentRequest) - } +class CommentRemoteDataSourceImpl( + private val service: CommentApiService, +) : CommentRemoteDataSource { + override suspend fun saveComment(commentRequest: CommentRequest): Result = + safeApiCall { + service.postComment(commentRequest) + } - override suspend fun fetchComments(offeringId: Long): Result = - safeApiCall { - service.getComments(offeringId) - } + override suspend fun fetchComments(offeringId: Long): Result = + safeApiCall { + service.getComments(offeringId) + } - override suspend fun fetchCommentOfferingInfo(offeringId: Long): Result = - safeApiCall { - service.getCommentOfferingInfo(offeringId) - } + override suspend fun fetchCommentOfferingInfo(offeringId: Long): Result = + safeApiCall { + service.getCommentOfferingInfo(offeringId) + } - override suspend fun updateOfferingStatus(offeringId: Long): Result = - safeApiCall { - service.patchOfferingStatus(offeringId) - } - } + override suspend fun updateOfferingStatus(offeringId: Long): Result = + safeApiCall { + service.patchOfferingStatus(offeringId) + } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/remote/source/CommentRoomsDataSourceImpl.kt b/android/app/src/main/java/com/zzang/chongdae/data/remote/source/CommentRoomsDataSourceImpl.kt index 2d5002e6f..87c72925c 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/remote/source/CommentRoomsDataSourceImpl.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/remote/source/CommentRoomsDataSourceImpl.kt @@ -1,20 +1,16 @@ package com.zzang.chongdae.data.remote.source -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result import com.zzang.chongdae.data.remote.api.CommentApiService import com.zzang.chongdae.data.remote.dto.response.commentroom.CommentRoomsResponse import com.zzang.chongdae.data.remote.util.safeApiCall import com.zzang.chongdae.data.source.CommentRoomsDataSource -import com.zzang.chongdae.di.annotations.CommentRoomsApiServiceQualifier -import javax.inject.Inject +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result -class CommentRoomsDataSourceImpl - @Inject - constructor( - @CommentRoomsApiServiceQualifier private val commentApiService: CommentApiService, - ) : CommentRoomsDataSource { - override suspend fun fetchCommentRooms(): Result { - return safeApiCall { commentApiService.getCommentRooms() } - } +class CommentRoomsDataSourceImpl( + private val commentApiService: CommentApiService, +) : CommentRoomsDataSource { + override suspend fun fetchCommentRooms(): Result { + return safeApiCall { commentApiService.getCommentRooms() } } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/remote/source/OfferingDetailDataSourceImpl.kt b/android/app/src/main/java/com/zzang/chongdae/data/remote/source/OfferingDetailDataSourceImpl.kt index 2c66f5e8d..38f79e7d1 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/remote/source/OfferingDetailDataSourceImpl.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/remote/source/OfferingDetailDataSourceImpl.kt @@ -1,30 +1,21 @@ package com.zzang.chongdae.data.remote.source -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result import com.zzang.chongdae.data.remote.api.OfferingApiService import com.zzang.chongdae.data.remote.api.ParticipationApiService import com.zzang.chongdae.data.remote.dto.request.ParticipationRequest import com.zzang.chongdae.data.remote.dto.response.offering.OfferingDetailResponse import com.zzang.chongdae.data.remote.util.safeApiCall import com.zzang.chongdae.data.source.OfferingDetailDataSource -import com.zzang.chongdae.di.annotations.OfferingApiServiceQualifier -import com.zzang.chongdae.di.annotations.ParticipantApiServiceQualifier -import javax.inject.Inject +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result -class OfferingDetailDataSourceImpl - @Inject - constructor( - @OfferingApiServiceQualifier private val offeringApiService: OfferingApiService, - @ParticipantApiServiceQualifier private val participationApiService: ParticipationApiService, - ) : OfferingDetailDataSource { - override suspend fun fetchOfferingDetail(offeringId: Long): Result = - safeApiCall { offeringApiService.getOfferingDetail(offeringId) } +class OfferingDetailDataSourceImpl( + private val offeringApiService: OfferingApiService, + private val participationApiService: ParticipationApiService, +) : OfferingDetailDataSource { + override suspend fun fetchOfferingDetail(offeringId: Long): Result = + safeApiCall { offeringApiService.getOfferingDetail(offeringId) } - override suspend fun saveParticipation(participationRequest: ParticipationRequest): Result = - safeApiCall { participationApiService.postParticipations(participationRequest) } - - override suspend fun deleteOffering(offeringId: Long): Result { - return safeApiCall { offeringApiService.deleteOffering(offeringId) } - } - } + override suspend fun saveParticipation(participationRequest: ParticipationRequest): Result = + safeApiCall { participationApiService.postParticipations(participationRequest) } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/remote/source/OfferingRemoteDataSourceImpl.kt b/android/app/src/main/java/com/zzang/chongdae/data/remote/source/OfferingRemoteDataSourceImpl.kt index df2dc3782..f3994d1a2 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/remote/source/OfferingRemoteDataSourceImpl.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/remote/source/OfferingRemoteDataSourceImpl.kt @@ -1,60 +1,48 @@ package com.zzang.chongdae.data.remote.source -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result +import com.zzang.chongdae.data.mapper.toProductUrlRequest import com.zzang.chongdae.data.remote.api.OfferingApiService -import com.zzang.chongdae.data.remote.dto.request.OfferingModifyRequest import com.zzang.chongdae.data.remote.dto.request.OfferingWriteRequest import com.zzang.chongdae.data.remote.dto.response.offering.FiltersResponse import com.zzang.chongdae.data.remote.dto.response.offering.MeetingsResponse import com.zzang.chongdae.data.remote.dto.response.offering.OfferingsResponse import com.zzang.chongdae.data.remote.dto.response.offering.ProductUrlResponse import com.zzang.chongdae.data.remote.dto.response.offering.RemoteOffering -import com.zzang.chongdae.data.remote.mapper.toProductUrlRequest import com.zzang.chongdae.data.remote.util.safeApiCall import com.zzang.chongdae.data.source.offering.OfferingRemoteDataSource -import com.zzang.chongdae.di.annotations.OfferingApiServiceQualifier +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result import okhttp3.MultipartBody -import javax.inject.Inject - -class OfferingRemoteDataSourceImpl - @Inject - constructor( - @OfferingApiServiceQualifier private val service: OfferingApiService, - ) : OfferingRemoteDataSource { - override suspend fun fetchOffering(offeringId: Long): Result = - safeApiCall { service.getOffering(offeringId) } - - override suspend fun fetchOfferings( - filter: String?, - search: String?, - lastOfferingId: Long?, - pageSize: Int?, - ): Result = safeApiCall { service.getOfferings(filter, search, lastOfferingId, pageSize) } - - override suspend fun saveOffering(offeringWriteRequest: OfferingWriteRequest): Result = - safeApiCall { service.postOfferingWrite((offeringWriteRequest)) } - - override suspend fun saveProductImageOg(productUrl: String): Result = - safeApiCall { service.postProductImageOg((productUrl.toProductUrlRequest())) } - - override suspend fun saveProductImageS3(image: MultipartBody.Part): Result = - safeApiCall { service.postProductImageS3(image) } - - override suspend fun fetchFilters(): Result = safeApiCall { service.getFilters() } - - override suspend fun fetchMeetings(offeringId: Long): Result = - safeApiCall { service.getMeetings(offeringId) } - - override suspend fun patchOffering( - offeringId: Long, - offeringModifyRequest: OfferingModifyRequest, - ): Result { - return safeApiCall { service.patchOffering(offeringId, offeringModifyRequest) } - } - - companion object { - private const val ERROR_PREFIX = "에러 발생: " - private const val ERROR_NULL_MESSAGE = "null" - } + +class OfferingRemoteDataSourceImpl( + private val service: OfferingApiService, +) : OfferingRemoteDataSource { + override suspend fun fetchOffering(offeringId: Long): Result = + safeApiCall { service.getOffering(offeringId) } + + override suspend fun fetchOfferings( + filter: String?, + search: String?, + lastOfferingId: Long?, + pageSize: Int?, + ): Result = safeApiCall { service.getOfferings(filter, search, lastOfferingId, pageSize) } + + override suspend fun saveOffering(offeringWriteRequest: OfferingWriteRequest): Result = + safeApiCall { service.postOfferingWrite((offeringWriteRequest)) } + + override suspend fun saveProductImageOg(productUrl: String): Result = + safeApiCall { service.postProductImageOg((productUrl.toProductUrlRequest())) } + + override suspend fun saveProductImageS3(image: MultipartBody.Part): Result = + safeApiCall { service.postProductImageS3(image) } + + override suspend fun fetchFilters(): Result = safeApiCall { service.getFilters() } + + override suspend fun fetchMeetings(offeringId: Long): Result = + safeApiCall { service.getMeetings(offeringId) } + + companion object { + private const val ERROR_PREFIX = "에러 발생: " + private const val ERROR_NULL_MESSAGE = "null" } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/remote/source/ParticipantRemoteDataSourceImpl.kt b/android/app/src/main/java/com/zzang/chongdae/data/remote/source/ParticipantRemoteDataSourceImpl.kt index 250222a5c..a847e76df 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/remote/source/ParticipantRemoteDataSourceImpl.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/remote/source/ParticipantRemoteDataSourceImpl.kt @@ -1,26 +1,22 @@ package com.zzang.chongdae.data.remote.source -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result import com.zzang.chongdae.data.remote.api.ParticipationApiService import com.zzang.chongdae.data.remote.dto.response.participants.ParticipantsResponse import com.zzang.chongdae.data.remote.util.safeApiCall import com.zzang.chongdae.data.source.ParticipantRemoteDataSource -import com.zzang.chongdae.di.annotations.ParticipantApiServiceQualifier -import javax.inject.Inject +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result -class ParticipantRemoteDataSourceImpl - @Inject - constructor( - @ParticipantApiServiceQualifier private val service: ParticipationApiService, - ) : ParticipantRemoteDataSource { - override suspend fun fetchParticipants(offeringId: Long): Result = - safeApiCall { - service.getParticipants(offeringId) - } +class ParticipantRemoteDataSourceImpl( + private val service: ParticipationApiService, +) : ParticipantRemoteDataSource { + override suspend fun fetchParticipants(offeringId: Long): Result = + safeApiCall { + service.getParticipants(offeringId) + } - override suspend fun deleteParticipations(offeringId: Long): Result = - safeApiCall { - service.deleteParticipations(offeringId) - } - } + override suspend fun deleteParticipations(offeringId: Long): Result = + safeApiCall { + service.deleteParticipations(offeringId) + } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/remote/util/CallApiHandler.kt b/android/app/src/main/java/com/zzang/chongdae/data/remote/util/CallApiHandler.kt index 148630f71..aabc7932f 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/remote/util/CallApiHandler.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/remote/util/CallApiHandler.kt @@ -1,7 +1,7 @@ package com.zzang.chongdae.data.remote.util -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result import retrofit2.HttpException import retrofit2.Response import java.io.IOException diff --git a/android/app/src/main/java/com/zzang/chongdae/data/remote/util/TokensCookieJar.kt b/android/app/src/main/java/com/zzang/chongdae/data/remote/util/TokensCookieJar.kt index 4c94fb81d..07b839fbb 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/remote/util/TokensCookieJar.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/remote/util/TokensCookieJar.kt @@ -1,7 +1,7 @@ package com.zzang.chongdae.data.remote.util import com.zzang.chongdae.BuildConfig -import com.zzang.chongdae.common.datastore.UserPreferencesDataStore +import com.zzang.chongdae.data.local.source.UserPreferencesDataStore import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first diff --git a/android/app/src/main/java/com/zzang/chongdae/data/repository/AuthRepositoryImpl.kt b/android/app/src/main/java/com/zzang/chongdae/data/repository/AuthRepositoryImpl.kt new file mode 100644 index 000000000..cdf833974 --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/repository/AuthRepositoryImpl.kt @@ -0,0 +1,37 @@ +package com.zzang.chongdae.data.repository + +import com.zzang.chongdae.data.mapper.toDomain +import com.zzang.chongdae.data.remote.dto.request.AccessTokenRequest +import com.zzang.chongdae.data.source.AuthRemoteDataSource +import com.zzang.chongdae.domain.model.Member +import com.zzang.chongdae.domain.repository.AuthRepository +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result + +class AuthRepositoryImpl( + private val authRemoteDataSource: AuthRemoteDataSource, +) : AuthRepository { + override suspend fun saveLogin(accessToken: String): Result { + return authRemoteDataSource.saveLogin( + accessTokenRequest = AccessTokenRequest(accessToken), + ).map { it.toDomain() } + } + + override suspend fun saveRefresh(): Result { + return when (val result = authRemoteDataSource.saveRefresh()) { + is Result.Error -> { + when (result.error) { + DataError.Network.UNAUTHORIZED -> { + return Result.Error(result.msg, DataError.Network.FAIL_REFRESH) + } + + else -> { + return Result.Error(result.msg, DataError.Network.UNAUTHORIZED) + } + } + } + + is Result.Success -> result + } + } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/repository/CommentDetailRepositoryImpl.kt b/android/app/src/main/java/com/zzang/chongdae/data/repository/CommentDetailRepositoryImpl.kt index 6d01ae361..bb4f8d5a2 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/repository/CommentDetailRepositoryImpl.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/repository/CommentDetailRepositoryImpl.kt @@ -1,46 +1,40 @@ package com.zzang.chongdae.data.repository -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result +import com.zzang.chongdae.data.mapper.toDomain import com.zzang.chongdae.data.remote.dto.request.CommentRequest -import com.zzang.chongdae.data.remote.mapper.toDomain import com.zzang.chongdae.data.source.comment.CommentRemoteDataSource -import com.zzang.chongdae.di.annotations.CommentDetailDataSourceQualifier import com.zzang.chongdae.domain.model.Comment import com.zzang.chongdae.domain.model.CommentOfferingInfo import com.zzang.chongdae.domain.repository.CommentDetailRepository -import javax.inject.Inject +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result -class CommentDetailRepositoryImpl - @Inject - constructor( - @CommentDetailDataSourceQualifier private val commentRemoteDataSource: CommentRemoteDataSource, - ) : CommentDetailRepository { - override suspend fun saveComment( - offeringId: Long, - comment: String, - ): Result { - return commentRemoteDataSource.saveComment( - CommentRequest(offeringId, comment), - ).map { Unit } - } +class CommentDetailRepositoryImpl( + private val commentRemoteDataSource: CommentRemoteDataSource, +) : CommentDetailRepository { + override suspend fun saveComment( + offeringId: Long, + comment: String, + ): Result { + return commentRemoteDataSource.saveComment( + CommentRequest(offeringId, comment), + ).map { Unit } + } - override suspend fun fetchComments(offeringId: Long): Result, DataError.Network> { - return commentRemoteDataSource.fetchComments(offeringId) - .map { response -> - response.commentsResponse.map { it.toDomain() } - } - } + override suspend fun fetchComments(offeringId: Long): Result, DataError.Network> { + return commentRemoteDataSource.fetchComments(offeringId) + .map { response -> + response.commentsResponse.map { it.toDomain() } + } + } - override suspend fun fetchCommentOfferingInfo(offeringId: Long): Result { - return commentRemoteDataSource.fetchCommentOfferingInfo(offeringId) - .map { response -> - response.toDomain() - } - } + override suspend fun fetchCommentOfferingInfo(offeringId: Long): Result { + return commentRemoteDataSource.fetchCommentOfferingInfo(offeringId) + .map { it.toDomain() } + } - override suspend fun updateOfferingStatus(offeringId: Long): Result { - return commentRemoteDataSource.updateOfferingStatus(offeringId) - .map { Unit } - } + override suspend fun updateOfferingStatus(offeringId: Long): Result { + return commentRemoteDataSource.updateOfferingStatus(offeringId) + .map { Unit } } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/repository/CommentRoomsRepositoryImpl.kt b/android/app/src/main/java/com/zzang/chongdae/data/repository/CommentRoomsRepositoryImpl.kt index d8ef5b867..924cf8544 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/repository/CommentRoomsRepositoryImpl.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/repository/CommentRoomsRepositoryImpl.kt @@ -1,22 +1,18 @@ package com.zzang.chongdae.data.repository -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result -import com.zzang.chongdae.data.remote.mapper.toDomain +import com.zzang.chongdae.data.mapper.toDomain import com.zzang.chongdae.data.source.CommentRoomsDataSource -import com.zzang.chongdae.di.annotations.CommentRoomsDataSourceQualifier import com.zzang.chongdae.domain.model.CommentRoom import com.zzang.chongdae.domain.repository.CommentRoomsRepository -import javax.inject.Inject +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result -class CommentRoomsRepositoryImpl - @Inject - constructor( - @CommentRoomsDataSourceQualifier private val commentRoomsDataSource: CommentRoomsDataSource, - ) : CommentRoomsRepository { - override suspend fun fetchCommentRooms(): Result, DataError.Network> { - return commentRoomsDataSource.fetchCommentRooms().map { - it.commentRoom.map { it.toDomain() } - } +class CommentRoomsRepositoryImpl( + private val commentRoomsDataSource: CommentRoomsDataSource, +) : CommentRoomsRepository { + override suspend fun fetchCommentRooms(): Result, DataError.Network> { + return commentRoomsDataSource.fetchCommentRooms().map { + it.commentRoom.map { it.toDomain() } } } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/repository/OfferingDetailRepositoryImpl.kt b/android/app/src/main/java/com/zzang/chongdae/data/repository/OfferingDetailRepositoryImpl.kt index b1ababab5..b206f444c 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/repository/OfferingDetailRepositoryImpl.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/repository/OfferingDetailRepositoryImpl.kt @@ -1,33 +1,25 @@ package com.zzang.chongdae.data.repository -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result +import com.zzang.chongdae.data.mapper.toDomain import com.zzang.chongdae.data.remote.dto.request.ParticipationRequest -import com.zzang.chongdae.data.remote.mapper.toDomain import com.zzang.chongdae.data.source.OfferingDetailDataSource -import com.zzang.chongdae.di.annotations.OfferingDetailDataSourceQualifier import com.zzang.chongdae.domain.model.OfferingDetail import com.zzang.chongdae.domain.repository.OfferingDetailRepository -import javax.inject.Inject +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result -class OfferingDetailRepositoryImpl - @Inject - constructor( - @OfferingDetailDataSourceQualifier private val offeringDetailDataSource: OfferingDetailDataSource, - ) : OfferingDetailRepository { - override suspend fun fetchOfferingDetail(offeringId: Long): Result = - offeringDetailDataSource.fetchOfferingDetail( - offeringId = offeringId, - ).map { - it.toDomain() - } - - override suspend fun saveParticipation(offeringId: Long): Result = - offeringDetailDataSource.saveParticipation( - participationRequest = ParticipationRequest(offeringId), - ) - - override suspend fun deleteOffering(offeringId: Long): Result { - return offeringDetailDataSource.deleteOffering(offeringId) +class OfferingDetailRepositoryImpl( + private val offeringDetailDataSource: OfferingDetailDataSource, +) : OfferingDetailRepository { + override suspend fun fetchOfferingDetail(offeringId: Long): Result = + offeringDetailDataSource.fetchOfferingDetail( + offeringId = offeringId, + ).map { + it.toDomain() } - } + + override suspend fun saveParticipation(offeringId: Long): Result = + offeringDetailDataSource.saveParticipation( + participationRequest = ParticipationRequest(offeringId), + ) +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/repository/OfferingRepositoryImpl.kt b/android/app/src/main/java/com/zzang/chongdae/data/repository/OfferingRepositoryImpl.kt index bd0f2d3bf..20793d043 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/repository/OfferingRepositoryImpl.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/repository/OfferingRepositoryImpl.kt @@ -1,84 +1,80 @@ package com.zzang.chongdae.data.repository -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result -import com.zzang.chongdae.data.remote.mapper.toDomain -import com.zzang.chongdae.data.remote.mapper.toRequest +import com.zzang.chongdae.data.mapper.toDomain +import com.zzang.chongdae.data.remote.dto.request.OfferingWriteRequest import com.zzang.chongdae.data.source.offering.OfferingLocalDataSource import com.zzang.chongdae.data.source.offering.OfferingRemoteDataSource -import com.zzang.chongdae.di.annotations.OfferingLocalDataSourceQualifier -import com.zzang.chongdae.di.annotations.OfferingRemoteDataSourceQualifier import com.zzang.chongdae.domain.model.Filter import com.zzang.chongdae.domain.model.Meetings import com.zzang.chongdae.domain.model.Offering -import com.zzang.chongdae.domain.model.OfferingModifyDomainRequest -import com.zzang.chongdae.domain.model.OfferingWrite import com.zzang.chongdae.domain.model.ProductUrl import com.zzang.chongdae.domain.repository.OfferingRepository +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result +import com.zzang.chongdae.presentation.view.write.OfferingWriteUiModel import okhttp3.MultipartBody -import javax.inject.Inject -class OfferingRepositoryImpl - @Inject - constructor( - @OfferingLocalDataSourceQualifier private val offeringLocalDataSource: OfferingLocalDataSource, - @OfferingRemoteDataSourceQualifier private val offeringRemoteDataSource: OfferingRemoteDataSource, - ) : OfferingRepository { - override suspend fun fetchOffering(offeringId: Long): Result = - offeringRemoteDataSource.fetchOffering(offeringId = offeringId).map { - it.toDomain() - } - - override suspend fun fetchOfferings( - filter: String?, - search: String?, - lastOfferingId: Long?, - pageSize: Int?, - ): Result, DataError.Network> { - return offeringRemoteDataSource.fetchOfferings(filter, search, lastOfferingId, pageSize) - .map { - it.offerings.map { it.toDomain() } - } - } - - override suspend fun saveOffering(offeringWrite: OfferingWrite): Result { - return offeringRemoteDataSource.saveOffering( - offeringWriteRequest = offeringWrite.toRequest(), - ) +class OfferingRepositoryImpl( + private val offeringLocalDataSource: OfferingLocalDataSource, + private val offeringRemoteDataSource: OfferingRemoteDataSource, +) : OfferingRepository { + override suspend fun fetchOffering(offeringId: Long): Result = + offeringRemoteDataSource.fetchOffering(offeringId = offeringId).map { + it.toDomain() } - override suspend fun saveProductImageOg(productUrl: String): Result { - return offeringRemoteDataSource.saveProductImageOg(productUrl).map { - it.toDomain() + override suspend fun fetchOfferings( + filter: String?, + search: String?, + lastOfferingId: Long?, + pageSize: Int?, + ): Result, DataError.Network> { + return offeringRemoteDataSource.fetchOfferings(filter, search, lastOfferingId, pageSize) + .map { + it.offerings.map { it.toDomain() } } - } + } - override suspend fun saveProductImageS3(image: MultipartBody.Part): Result { - return offeringRemoteDataSource.saveProductImageS3(image).map { - it.toDomain() - } + override suspend fun saveOffering(uiModel: OfferingWriteUiModel): Result { + return offeringRemoteDataSource.saveOffering( + offeringWriteRequest = + OfferingWriteRequest( + title = uiModel.title, + productUrl = uiModel.productUrl, + thumbnailUrl = uiModel.thumbnailUrl, + totalCount = uiModel.totalCount, + totalPrice = uiModel.totalPrice, + originPrice = uiModel.originPrice, + meetingAddress = uiModel.meetingAddress, + meetingAddressDong = uiModel.meetingAddressDong, + meetingAddressDetail = uiModel.meetingAddressDetail, + meetingDate = uiModel.meetingDate, + description = uiModel.description, + ), + ) + } + + override suspend fun saveProductImageOg(productUrl: String): Result { + return offeringRemoteDataSource.saveProductImageOg(productUrl).map { + it.toDomain() } + } - override suspend fun fetchFilters(): Result, DataError.Network> { - return offeringRemoteDataSource.fetchFilters().map { - it.filters.map { it.toDomain() } - } + override suspend fun saveProductImageS3(image: MultipartBody.Part): Result { + return offeringRemoteDataSource.saveProductImageS3(image).map { + it.toDomain() } + } - override suspend fun fetchMeetings(offeringId: Long): Result { - return offeringRemoteDataSource.fetchMeetings(offeringId).map { - it.toDomain() - } + override suspend fun fetchFilters(): Result, DataError.Network> { + return offeringRemoteDataSource.fetchFilters().map { + it.filters.map { it.toDomain() } } + } - override suspend fun patchOffering( - offeringId: Long, - offeringModifyDomainRequest: OfferingModifyDomainRequest, - ): Result = - offeringRemoteDataSource.patchOffering( - offeringId, - offeringModifyDomainRequest.toRequest(), - ).map { - it // .toDomain() - } + override suspend fun fetchMeetings(offeringId: Long): Result { + return offeringRemoteDataSource.fetchMeetings(offeringId).map { + it.toDomain() + } } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/repository/ParticipantRepositoryImpl.kt b/android/app/src/main/java/com/zzang/chongdae/data/repository/ParticipantRepositoryImpl.kt index 7f4d48b57..6abae202b 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/repository/ParticipantRepositoryImpl.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/repository/ParticipantRepositoryImpl.kt @@ -1,26 +1,22 @@ package com.zzang.chongdae.data.repository -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result -import com.zzang.chongdae.data.remote.mapper.participant.toDomain +import com.zzang.chongdae.data.mapper.participant.toDomain import com.zzang.chongdae.data.source.ParticipantRemoteDataSource -import com.zzang.chongdae.di.annotations.ParticipantDataSourceQualifier import com.zzang.chongdae.domain.model.participant.Participants import com.zzang.chongdae.domain.repository.ParticipantRepository -import javax.inject.Inject +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result -class ParticipantRepositoryImpl - @Inject - constructor( - @ParticipantDataSourceQualifier private val participantRemoteDataSource: ParticipantRemoteDataSource, - ) : ParticipantRepository { - override suspend fun fetchParticipants(offeringId: Long): Result = - participantRemoteDataSource.fetchParticipants( - offeringId, - ).map { response -> - response.toDomain() - } +class ParticipantRepositoryImpl( + private val participantRemoteDataSource: ParticipantRemoteDataSource, +) : ParticipantRepository { + override suspend fun fetchParticipants(offeringId: Long): Result = + participantRemoteDataSource.fetchParticipants( + offeringId, + ).map { response -> + response.toDomain() + } - override suspend fun deleteParticipations(offeringId: Long): Result = - participantRemoteDataSource.deleteParticipations(offeringId) - } + override suspend fun deleteParticipations(offeringId: Long): Result = + participantRemoteDataSource.deleteParticipations(offeringId) +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/source/AuthRemoteDataSource.kt b/android/app/src/main/java/com/zzang/chongdae/data/source/AuthRemoteDataSource.kt new file mode 100644 index 000000000..d101edff6 --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/data/source/AuthRemoteDataSource.kt @@ -0,0 +1,12 @@ +package com.zzang.chongdae.data.source + +import com.zzang.chongdae.data.remote.dto.request.AccessTokenRequest +import com.zzang.chongdae.data.remote.dto.response.auth.MemberResponse +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result + +interface AuthRemoteDataSource { + suspend fun saveLogin(accessTokenRequest: AccessTokenRequest): Result + + suspend fun saveRefresh(): Result +} diff --git a/android/app/src/main/java/com/zzang/chongdae/data/source/CommentRoomsDataSource.kt b/android/app/src/main/java/com/zzang/chongdae/data/source/CommentRoomsDataSource.kt index c48283cd7..f133264ed 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/source/CommentRoomsDataSource.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/source/CommentRoomsDataSource.kt @@ -1,8 +1,8 @@ package com.zzang.chongdae.data.source -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result import com.zzang.chongdae.data.remote.dto.response.commentroom.CommentRoomsResponse +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result interface CommentRoomsDataSource { suspend fun fetchCommentRooms(): Result diff --git a/android/app/src/main/java/com/zzang/chongdae/data/source/OfferingDetailDataSource.kt b/android/app/src/main/java/com/zzang/chongdae/data/source/OfferingDetailDataSource.kt index e6cdb92ed..b32806232 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/source/OfferingDetailDataSource.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/source/OfferingDetailDataSource.kt @@ -1,14 +1,12 @@ package com.zzang.chongdae.data.source -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result import com.zzang.chongdae.data.remote.dto.request.ParticipationRequest import com.zzang.chongdae.data.remote.dto.response.offering.OfferingDetailResponse +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result interface OfferingDetailDataSource { suspend fun fetchOfferingDetail(offeringId: Long): Result suspend fun saveParticipation(participationRequest: ParticipationRequest): Result - - suspend fun deleteOffering(offeringId: Long): Result } diff --git a/android/app/src/main/java/com/zzang/chongdae/data/source/ParticipantRemoteDataSource.kt b/android/app/src/main/java/com/zzang/chongdae/data/source/ParticipantRemoteDataSource.kt index 588a4a7c8..b53e3c663 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/source/ParticipantRemoteDataSource.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/source/ParticipantRemoteDataSource.kt @@ -1,8 +1,8 @@ package com.zzang.chongdae.data.source -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result import com.zzang.chongdae.data.remote.dto.response.participants.ParticipantsResponse +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result interface ParticipantRemoteDataSource { suspend fun fetchParticipants(offeringId: Long): Result diff --git a/android/app/src/main/java/com/zzang/chongdae/data/source/comment/CommentRemoteDataSource.kt b/android/app/src/main/java/com/zzang/chongdae/data/source/comment/CommentRemoteDataSource.kt index f3b96a0ae..3ce476747 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/source/comment/CommentRemoteDataSource.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/source/comment/CommentRemoteDataSource.kt @@ -1,11 +1,11 @@ package com.zzang.chongdae.data.source.comment -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result import com.zzang.chongdae.data.remote.dto.request.CommentRequest import com.zzang.chongdae.data.remote.dto.response.comment.CommentOfferingInfoResponse import com.zzang.chongdae.data.remote.dto.response.comment.CommentsResponse import com.zzang.chongdae.data.remote.dto.response.comment.UpdatedStatusResponse +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result interface CommentRemoteDataSource { suspend fun saveComment(commentRequest: CommentRequest): Result diff --git a/android/app/src/main/java/com/zzang/chongdae/data/source/offering/OfferingRemoteDataSource.kt b/android/app/src/main/java/com/zzang/chongdae/data/source/offering/OfferingRemoteDataSource.kt index 7524554c7..4f290c056 100644 --- a/android/app/src/main/java/com/zzang/chongdae/data/source/offering/OfferingRemoteDataSource.kt +++ b/android/app/src/main/java/com/zzang/chongdae/data/source/offering/OfferingRemoteDataSource.kt @@ -1,14 +1,13 @@ package com.zzang.chongdae.data.source.offering -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result -import com.zzang.chongdae.data.remote.dto.request.OfferingModifyRequest import com.zzang.chongdae.data.remote.dto.request.OfferingWriteRequest import com.zzang.chongdae.data.remote.dto.response.offering.FiltersResponse import com.zzang.chongdae.data.remote.dto.response.offering.MeetingsResponse import com.zzang.chongdae.data.remote.dto.response.offering.OfferingsResponse import com.zzang.chongdae.data.remote.dto.response.offering.ProductUrlResponse import com.zzang.chongdae.data.remote.dto.response.offering.RemoteOffering +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result import okhttp3.MultipartBody interface OfferingRemoteDataSource { @@ -30,9 +29,4 @@ interface OfferingRemoteDataSource { suspend fun fetchFilters(): Result suspend fun fetchMeetings(offeringId: Long): Result - - suspend fun patchOffering( - offeringId: Long, - offeringModifyRequest: OfferingModifyRequest, - ): Result } diff --git a/android/app/src/main/java/com/zzang/chongdae/domain/model/Member.kt b/android/app/src/main/java/com/zzang/chongdae/domain/model/Member.kt new file mode 100644 index 000000000..86c0f729c --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/domain/model/Member.kt @@ -0,0 +1,6 @@ +package com.zzang.chongdae.domain.model + +data class Member( + val memberId: Long, + val nickName: String, +) diff --git a/android/app/src/main/java/com/zzang/chongdae/domain/model/OfferingDetail.kt b/android/app/src/main/java/com/zzang/chongdae/domain/model/OfferingDetail.kt index 04b1c5779..262d50601 100644 --- a/android/app/src/main/java/com/zzang/chongdae/domain/model/OfferingDetail.kt +++ b/android/app/src/main/java/com/zzang/chongdae/domain/model/OfferingDetail.kt @@ -11,7 +11,6 @@ data class OfferingDetail( val thumbnailUrl: String?, val dividedPrice: Int, val totalPrice: Int, - val originPrice: Int?, val meetingDate: LocalDateTime, val currentCount: CurrentCount, val totalCount: Int, diff --git a/android/app/src/main/java/com/zzang/chongdae/domain/paging/OfferingPagingSource.kt b/android/app/src/main/java/com/zzang/chongdae/domain/paging/OfferingPagingSource.kt index be354f405..b07e7afd0 100644 --- a/android/app/src/main/java/com/zzang/chongdae/domain/paging/OfferingPagingSource.kt +++ b/android/app/src/main/java/com/zzang/chongdae/domain/paging/OfferingPagingSource.kt @@ -2,11 +2,11 @@ package com.zzang.chongdae.domain.paging import androidx.paging.PagingSource import androidx.paging.PagingState -import com.zzang.chongdae.auth.repository.AuthRepository -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result import com.zzang.chongdae.domain.model.Offering +import com.zzang.chongdae.domain.repository.AuthRepository import com.zzang.chongdae.domain.repository.OfferingRepository +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result class OfferingPagingSource( private val offeringsRepository: OfferingRepository, diff --git a/android/app/src/main/java/com/zzang/chongdae/domain/repository/AuthRepository.kt b/android/app/src/main/java/com/zzang/chongdae/domain/repository/AuthRepository.kt new file mode 100644 index 000000000..30b6d0db6 --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/domain/repository/AuthRepository.kt @@ -0,0 +1,11 @@ +package com.zzang.chongdae.domain.repository + +import com.zzang.chongdae.domain.model.Member +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result + +interface AuthRepository { + suspend fun saveLogin(accessToken: String): Result + + suspend fun saveRefresh(): Result +} diff --git a/android/app/src/main/java/com/zzang/chongdae/domain/repository/CommentDetailRepository.kt b/android/app/src/main/java/com/zzang/chongdae/domain/repository/CommentDetailRepository.kt index 4dc188623..0b0daa896 100644 --- a/android/app/src/main/java/com/zzang/chongdae/domain/repository/CommentDetailRepository.kt +++ b/android/app/src/main/java/com/zzang/chongdae/domain/repository/CommentDetailRepository.kt @@ -1,9 +1,9 @@ package com.zzang.chongdae.domain.repository -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result import com.zzang.chongdae.domain.model.Comment import com.zzang.chongdae.domain.model.CommentOfferingInfo +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result interface CommentDetailRepository { suspend fun saveComment( diff --git a/android/app/src/main/java/com/zzang/chongdae/domain/repository/CommentRoomsRepository.kt b/android/app/src/main/java/com/zzang/chongdae/domain/repository/CommentRoomsRepository.kt index 091c7ae87..e5d883694 100644 --- a/android/app/src/main/java/com/zzang/chongdae/domain/repository/CommentRoomsRepository.kt +++ b/android/app/src/main/java/com/zzang/chongdae/domain/repository/CommentRoomsRepository.kt @@ -1,8 +1,8 @@ package com.zzang.chongdae.domain.repository -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result import com.zzang.chongdae.domain.model.CommentRoom +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result interface CommentRoomsRepository { suspend fun fetchCommentRooms(): Result, DataError.Network> diff --git a/android/app/src/main/java/com/zzang/chongdae/domain/repository/OfferingDetailRepository.kt b/android/app/src/main/java/com/zzang/chongdae/domain/repository/OfferingDetailRepository.kt index d9bb30449..28e682f16 100644 --- a/android/app/src/main/java/com/zzang/chongdae/domain/repository/OfferingDetailRepository.kt +++ b/android/app/src/main/java/com/zzang/chongdae/domain/repository/OfferingDetailRepository.kt @@ -1,13 +1,11 @@ package com.zzang.chongdae.domain.repository -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result import com.zzang.chongdae.domain.model.OfferingDetail +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result interface OfferingDetailRepository { suspend fun fetchOfferingDetail(offeringId: Long): Result suspend fun saveParticipation(offeringId: Long): Result - - suspend fun deleteOffering(offeringId: Long): Result } diff --git a/android/app/src/main/java/com/zzang/chongdae/domain/repository/OfferingRepository.kt b/android/app/src/main/java/com/zzang/chongdae/domain/repository/OfferingRepository.kt index f828fde38..b0ba9e717 100644 --- a/android/app/src/main/java/com/zzang/chongdae/domain/repository/OfferingRepository.kt +++ b/android/app/src/main/java/com/zzang/chongdae/domain/repository/OfferingRepository.kt @@ -1,13 +1,12 @@ package com.zzang.chongdae.domain.repository -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result import com.zzang.chongdae.domain.model.Filter import com.zzang.chongdae.domain.model.Meetings import com.zzang.chongdae.domain.model.Offering -import com.zzang.chongdae.domain.model.OfferingModifyDomainRequest -import com.zzang.chongdae.domain.model.OfferingWrite import com.zzang.chongdae.domain.model.ProductUrl +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result +import com.zzang.chongdae.presentation.view.write.OfferingWriteUiModel import okhttp3.MultipartBody interface OfferingRepository { @@ -20,7 +19,7 @@ interface OfferingRepository { pageSize: Int?, ): Result, DataError.Network> - suspend fun saveOffering(offeringWrite: OfferingWrite): Result + suspend fun saveOffering(uiModel: OfferingWriteUiModel): Result suspend fun saveProductImageOg(productUrl: String): Result @@ -29,9 +28,4 @@ interface OfferingRepository { suspend fun fetchFilters(): Result, DataError.Network> suspend fun fetchMeetings(offeringId: Long): Result - - suspend fun patchOffering( - offeringId: Long, - offeringModifyDomainRequest: OfferingModifyDomainRequest, - ): Result } diff --git a/android/app/src/main/java/com/zzang/chongdae/domain/repository/ParticipantRepository.kt b/android/app/src/main/java/com/zzang/chongdae/domain/repository/ParticipantRepository.kt index 88c086054..07e502fd0 100644 --- a/android/app/src/main/java/com/zzang/chongdae/domain/repository/ParticipantRepository.kt +++ b/android/app/src/main/java/com/zzang/chongdae/domain/repository/ParticipantRepository.kt @@ -1,8 +1,8 @@ package com.zzang.chongdae.domain.repository -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result import com.zzang.chongdae.domain.model.participant.Participants +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result interface ParticipantRepository { suspend fun fetchParticipants(offeringId: Long): Result diff --git a/android/app/src/main/java/com/zzang/chongdae/domain/util/DataError.kt b/android/app/src/main/java/com/zzang/chongdae/domain/util/DataError.kt new file mode 100644 index 000000000..6c3ec920d --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/domain/util/DataError.kt @@ -0,0 +1,16 @@ +package com.zzang.chongdae.domain.util + +sealed interface DataError : Error { + enum class Network : DataError { + UNAUTHORIZED, + UNKNOWN, + NULL, + CONNECTION_ERROR, + FORBIDDEN, + NOT_FOUND, + SERVER_ERROR, + BAD_REQUEST, + CONFLICT, + FAIL_REFRESH, + } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/domain/util/Error.kt b/android/app/src/main/java/com/zzang/chongdae/domain/util/Error.kt new file mode 100644 index 000000000..cb8861cc6 --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/domain/util/Error.kt @@ -0,0 +1,3 @@ +package com.zzang.chongdae.domain.util + +sealed interface Error diff --git a/android/app/src/main/java/com/zzang/chongdae/domain/util/Result.kt b/android/app/src/main/java/com/zzang/chongdae/domain/util/Result.kt new file mode 100644 index 000000000..1d994e79c --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/domain/util/Result.kt @@ -0,0 +1,21 @@ +package com.zzang.chongdae.domain.util + +typealias RootError = Error + +sealed interface Result { + data class Success(val data: D) : Result + + data class Error(val msg: String, val error: E) : Result + + fun map(transform: (D) -> R): Result = + when (this) { + is Success -> Success(transform(data)) + is Error -> this + } + + fun getOrThrow(): D = + when (this) { + is Success -> data + is Error -> throw RuntimeException("Error occurred: $msg, $error") + } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/util/AccessTokenExpirationHandler.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/util/AccessTokenExpirationHandler.kt new file mode 100644 index 000000000..1f2bcbccd --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/util/AccessTokenExpirationHandler.kt @@ -0,0 +1,19 @@ +package com.zzang.chongdae.presentation.util + +import android.util.Log +import com.zzang.chongdae.domain.model.HttpStatusCode +import com.zzang.chongdae.domain.repository.AuthRepository + +suspend fun handleAccessTokenExpiration( + authRepository: AuthRepository, + it: Throwable, + retryFunction: () -> Unit, +) { + when (it.message) { + HttpStatusCode.UNAUTHORIZED_401.code -> { + Log.e("error", "Access Token 만료") + authRepository.saveRefresh() + retryFunction() + } + } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/util/BindingAdapters.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/util/BindingAdapters.kt index 4bf4eec18..35fcd013a 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/util/BindingAdapters.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/util/BindingAdapters.kt @@ -336,14 +336,3 @@ private fun TextView.setDiscountRate( fun EditText.setOriginPriceHint(originPrice: Int) { this.hint = context.getString(R.string.write_current_split_price).format(originPrice) } - -@BindingAdapter(value = ["debouncedOnClick", "debounceTime"], requireAll = false) -fun View.setDebouncedOnClick( - clickListener: View.OnClickListener?, - debounceTime: Long?, -) { - val safeDebounceTime = debounceTime ?: DEFAULT_DEBOUNCE_TIME - setDebouncedOnClickListener(safeDebounceTime) { - clickListener?.onClick(this) - } -} diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/util/FirebaseAnalyticsManager.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/util/FirebaseAnalyticsManager.kt new file mode 100644 index 000000000..11334b627 --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/util/FirebaseAnalyticsManager.kt @@ -0,0 +1,32 @@ +package com.zzang.chongdae.presentation.util + +import android.os.Bundle +import com.google.firebase.analytics.FirebaseAnalytics + +class FirebaseAnalyticsManager(private val firebaseAnalytics: FirebaseAnalytics) { + fun logSelectContentEvent( + id: String, + name: String, + contentType: String, + ) { + val bundle = + Bundle().apply { + putString(FirebaseAnalytics.Param.ITEM_ID, id) + putString(FirebaseAnalytics.Param.ITEM_NAME, name) + putString(FirebaseAnalytics.Param.CONTENT_TYPE, contentType) + } + firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle) + } + + fun logScreenView( + screenName: String, + screenClass: String, + ) { + val bundle = + Bundle().apply { + putString(FirebaseAnalytics.Param.SCREEN_NAME, screenName) + putString(FirebaseAnalytics.Param.SCREEN_CLASS, screenClass) + } + firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW, bundle) + } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/MainActivity.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/MainActivity.kt index 04eba11eb..9be6d959d 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/MainActivity.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/MainActivity.kt @@ -2,24 +2,18 @@ package com.zzang.chongdae.presentation.view import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Bundle import android.view.MotionEvent import android.view.View import android.view.inputmethod.InputMethodManager -import android.widget.Toast import androidx.appcompat.app.AppCompatActivity -import androidx.core.os.bundleOf import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.findNavController import androidx.navigation.ui.setupWithNavController import com.zzang.chongdae.R import com.zzang.chongdae.databinding.ActivityMainBinding -import com.zzang.chongdae.presentation.view.offeringdetail.OfferingDetailFragment -import dagger.hilt.android.AndroidEntryPoint -@AndroidEntryPoint class MainActivity : AppCompatActivity() { private var _binding: ActivityMainBinding? = null private val binding get() = _binding!! @@ -31,7 +25,6 @@ class MainActivity : AppCompatActivity() { initBinding() initNavController() setupBottomNavigation() - handleDeepLink(intent) } private fun initBinding() { @@ -64,45 +57,12 @@ class MainActivity : AppCompatActivity() { return super.dispatchTouchEvent(motionEvent) } - override fun onNewIntent(intent: Intent) { - super.onNewIntent(intent) - handleDeepLink(intent) - } - - private fun handleDeepLink(intent: Intent) { - val data: Uri? = intent.data - data?.let { uri -> - if (uri.scheme == SCHEME && uri.host == HOST) { - val offeringIdStr = uri.lastPathSegment - - val offeringId = offeringIdStr?.toLongOrNull() - if (offeringId != null) { - openOfferingDetailFragment(offeringId) - } else { - Toast.makeText(this, "공모 ID가 올바르지 않습니다.", Toast.LENGTH_SHORT).show() - } - } else { - Toast.makeText(this, "Deeplink가 올바르지 않습니다.", Toast.LENGTH_SHORT).show() - } - } - } - - private fun openOfferingDetailFragment(offeringId: Long) { - val navController = navHostFragment.navController - val bundle = bundleOf(OfferingDetailFragment.OFFERING_ID_KEY to offeringId) - - navController.navigate(R.id.action_home_fragment_to_offering_detail_fragment, bundle) - } - override fun onDestroy() { super.onDestroy() _binding = null } companion object { - private const val SCHEME = "chongdaeapp" - private const val HOST = "offerings" - fun startActivity(context: Context) = Intent(context, MainActivity::class.java).run { context.startActivity(this) diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/comment/CommentRoomsFragment.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/comment/CommentRoomsFragment.kt index 8d5b35bf2..b804f5a6c 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/comment/CommentRoomsFragment.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/comment/CommentRoomsFragment.kt @@ -8,14 +8,13 @@ import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import com.google.firebase.analytics.FirebaseAnalytics -import com.zzang.chongdae.common.firebase.FirebaseAnalyticsManager +import com.zzang.chongdae.ChongdaeApp import com.zzang.chongdae.databinding.FragmentCommentRoomsBinding +import com.zzang.chongdae.presentation.util.FirebaseAnalyticsManager import com.zzang.chongdae.presentation.view.comment.adapter.CommentRoomsAdapter import com.zzang.chongdae.presentation.view.comment.adapter.OnCommentRoomClickListener import com.zzang.chongdae.presentation.view.commentdetail.CommentDetailActivity -import dagger.hilt.android.AndroidEntryPoint -@AndroidEntryPoint class CommentRoomsFragment : Fragment(), OnCommentRoomClickListener { private var _binding: FragmentCommentRoomsBinding? = null private val binding get() = _binding!! @@ -31,7 +30,12 @@ class CommentRoomsFragment : Fragment(), OnCommentRoomClickListener { CommentRoomsAdapter(this) } - private val viewModel by viewModels() + private val viewModel by viewModels { + CommentRoomsViewModel.getFactory( + authRepository = (requireActivity().application as ChongdaeApp).authRepository, + commentRoomsRepository = (requireActivity().application as ChongdaeApp).commentRoomsRepository, + ) + } override fun onCreateView( inflater: LayoutInflater, diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/comment/CommentRoomsViewModel.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/comment/CommentRoomsViewModel.kt index e67720024..ebaf03186 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/comment/CommentRoomsViewModel.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/comment/CommentRoomsViewModel.kt @@ -4,52 +4,56 @@ import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.map import androidx.lifecycle.viewModelScope -import com.zzang.chongdae.auth.repository.AuthRepository -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result -import com.zzang.chongdae.di.annotations.AuthRepositoryQualifier -import com.zzang.chongdae.di.annotations.CommentRoomsRepositoryQualifier import com.zzang.chongdae.domain.model.CommentRoom +import com.zzang.chongdae.domain.repository.AuthRepository import com.zzang.chongdae.domain.repository.CommentRoomsRepository -import dagger.hilt.android.lifecycle.HiltViewModel +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result import kotlinx.coroutines.launch -import javax.inject.Inject -@HiltViewModel -class CommentRoomsViewModel - @Inject - constructor( - @AuthRepositoryQualifier private val authRepository: AuthRepository, - @CommentRoomsRepositoryQualifier private val commentRoomsRepository: CommentRoomsRepository, - ) : ViewModel() { - private val _commentRooms: MutableLiveData> = MutableLiveData() - val commentRooms: LiveData> get() = _commentRooms +class CommentRoomsViewModel( + private val authRepository: AuthRepository, + private val commentRoomsRepository: CommentRoomsRepository, +) : ViewModel() { + private val _commentRooms: MutableLiveData> = MutableLiveData() + val commentRooms: LiveData> get() = _commentRooms - val isCommentRoomsEmpty: LiveData - get() = - commentRooms.map { - it.isEmpty() - } + val isCommentRoomsEmpty: LiveData + get() = + commentRooms.map { + it.isEmpty() + } - fun updateCommentRooms() { - viewModelScope.launch { - when (val result = commentRoomsRepository.fetchCommentRooms()) { - is Result.Error -> { - Log.e("error", "updateCommentRooms: ${result.error}") - when (result.error) { - DataError.Network.UNAUTHORIZED -> { - when (authRepository.saveRefresh()) { - is Result.Success -> updateCommentRooms() - is Result.Error -> return@launch - } - } - else -> {} + fun updateCommentRooms() { + viewModelScope.launch { + when (val result = commentRoomsRepository.fetchCommentRooms()) { + is Result.Error -> { + Log.e("error", "${result.error}") + when (result.error) { + DataError.Network.UNAUTHORIZED -> { + authRepository.saveRefresh() + updateCommentRooms() } + else -> {} } - is Result.Success -> _commentRooms.value = result.data } + is Result.Success -> _commentRooms.value = result.data + } + } + } + + companion object { + @Suppress("UNCHECKED_CAST") + fun getFactory( + authRepository: AuthRepository, + commentRoomsRepository: CommentRoomsRepository, + ) = object : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return CommentRoomsViewModel(authRepository, commentRoomsRepository) as T } } } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/commentdetail/CommentDetailActivity.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/commentdetail/CommentDetailActivity.kt index f3ab4ed57..33d45c565 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/commentdetail/CommentDetailActivity.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/commentdetail/CommentDetailActivity.kt @@ -7,7 +7,6 @@ import android.net.Uri import android.os.Bundle import android.view.MotionEvent import android.view.inputmethod.InputMethodManager -import android.widget.EditText import android.widget.Toast import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity @@ -16,18 +15,14 @@ import androidx.core.view.doOnPreDraw import androidx.databinding.DataBindingUtil import androidx.recyclerview.widget.LinearLayoutManager import com.google.firebase.analytics.FirebaseAnalytics +import com.zzang.chongdae.ChongdaeApp import com.zzang.chongdae.R -import com.zzang.chongdae.common.firebase.FirebaseAnalyticsManager import com.zzang.chongdae.databinding.ActivityCommentDetailBinding -import com.zzang.chongdae.databinding.DialogAlertBinding import com.zzang.chongdae.databinding.DialogUpdateStatusBinding -import com.zzang.chongdae.presentation.util.setDebouncedOnClickListener +import com.zzang.chongdae.presentation.util.FirebaseAnalyticsManager import com.zzang.chongdae.presentation.view.commentdetail.adapter.comment.CommentAdapter import com.zzang.chongdae.presentation.view.commentdetail.adapter.participant.ParticipantAdapter -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject -@AndroidEntryPoint class CommentDetailActivity : AppCompatActivity(), OnUpdateStatusClickListener { private var _binding: ActivityCommentDetailBinding? = null private val binding get() = _binding!! @@ -36,13 +31,13 @@ class CommentDetailActivity : AppCompatActivity(), OnUpdateStatusClickListener { private val participantAdapter: ParticipantAdapter by lazy { ParticipantAdapter() } private val dialog: Dialog by lazy { Dialog(this) } - @Inject - lateinit var commentDetailAssistedFactory: CommentDetailViewModel.CommentDetailAssistedFactory - private val viewModel: CommentDetailViewModel by viewModels { CommentDetailViewModel.getFactory( - assistedFactory = commentDetailAssistedFactory, offeringId = offeringId, + authRepository = (application as ChongdaeApp).authRepository, + offeringRepository = (application as ChongdaeApp).offeringRepository, + participantRepository = (application as ChongdaeApp).participantRepository, + commentDetailRepository = (application as ChongdaeApp).commentDetailRepository, ) } @@ -75,10 +70,10 @@ class CommentDetailActivity : AppCompatActivity(), OnUpdateStatusClickListener { } private fun setupDrawerToggle() { - binding.ivMoreOptions.setDebouncedOnClickListener { + binding.ivMoreOptions.setOnClickListener { if (binding.drawerLayout.isDrawerOpen(GravityCompat.END)) { binding.drawerLayout.closeDrawer(GravityCompat.END) - return@setDebouncedOnClickListener + return@setOnClickListener } binding.drawerLayout.openDrawer(GravityCompat.END) firebaseAnalyticsManager.logSelectContentEvent( @@ -156,18 +151,6 @@ class CommentDetailActivity : AppCompatActivity(), OnUpdateStatusClickListener { contentType = "button", ) finish() - dialog.dismiss() - } - viewModel.showAlertEvent.observe(this) { - val alertBinding = DialogAlertBinding.inflate(layoutInflater, null, false) - alertBinding.tvDialogMessage.text = getString(R.string.comment_detail_exit_alert) - alertBinding.listener = viewModel - - dialog.setContentView(alertBinding.root) - dialog.show() - } - viewModel.alertCancelEvent.observe(this) { - dialog.dismiss() } } @@ -231,19 +214,8 @@ class CommentDetailActivity : AppCompatActivity(), OnUpdateStatusClickListener { } override fun dispatchTouchEvent(motionEvent: MotionEvent): Boolean { - val view = currentFocus - if (view != null && (view is EditText || view.id == R.id.iv_send_comment)) { - val screenCoords = IntArray(2) - view.getLocationOnScreen(screenCoords) - val x = motionEvent.rawX + view.left - screenCoords[0] - val y = motionEvent.rawY + view.top - screenCoords[1] - - if (motionEvent.action == MotionEvent.ACTION_UP && - (x < view.left || x >= view.right || y < view.top || y > view.bottom) - ) { - val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(view.windowToken, 0) - } + (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager).apply { + this.hideSoftInputFromWindow(currentFocus?.windowToken, 0) } return super.dispatchTouchEvent(motionEvent) } diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/commentdetail/CommentDetailViewModel.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/commentdetail/CommentDetailViewModel.kt index d67785fb6..7902f70be 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/commentdetail/CommentDetailViewModel.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/commentdetail/CommentDetailViewModel.kt @@ -5,18 +5,15 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras import com.zzang.chongdae.R -import com.zzang.chongdae.auth.repository.AuthRepository -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result -import com.zzang.chongdae.di.annotations.AuthRepositoryQualifier -import com.zzang.chongdae.di.annotations.CommentDetailRepositoryQualifier -import com.zzang.chongdae.di.annotations.OfferingRepositoryQualifier -import com.zzang.chongdae.di.annotations.ParticipantRepositoryQualifier import com.zzang.chongdae.domain.model.Comment +import com.zzang.chongdae.domain.repository.AuthRepository import com.zzang.chongdae.domain.repository.CommentDetailRepository import com.zzang.chongdae.domain.repository.OfferingRepository import com.zzang.chongdae.domain.repository.ParticipantRepository +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result import com.zzang.chongdae.presentation.util.MutableSingleLiveData import com.zzang.chongdae.presentation.util.SingleLiveData import com.zzang.chongdae.presentation.view.commentdetail.model.information.CommentOfferingInfoUiModel @@ -25,308 +22,269 @@ import com.zzang.chongdae.presentation.view.commentdetail.model.meeting.Meetings import com.zzang.chongdae.presentation.view.commentdetail.model.meeting.MeetingsUiModel.Companion.toUiModel import com.zzang.chongdae.presentation.view.commentdetail.model.participants.ParticipantsUiModel import com.zzang.chongdae.presentation.view.commentdetail.model.participants.ParticipantsUiModel.Companion.toUiModel -import com.zzang.chongdae.presentation.view.common.OnAlertClickListener -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -class CommentDetailViewModel - @AssistedInject - constructor( - @Assisted private val offeringId: Long, - @AuthRepositoryQualifier private val authRepository: AuthRepository, - @OfferingRepositoryQualifier private val offeringRepository: OfferingRepository, - @ParticipantRepositoryQualifier private val participantRepository: ParticipantRepository, - @CommentDetailRepositoryQualifier private val commentDetailRepository: CommentDetailRepository, - ) : ViewModel(), - OnAlertClickListener { - @AssistedFactory - interface CommentDetailAssistedFactory { - fun create(offeringId: Long): CommentDetailViewModel - } - - private var cachedComments: List = emptyList() - private var pollJob: Job? = null - val commentContent = MutableLiveData("") - - private val _comments: MutableLiveData> = MutableLiveData() - val comments: LiveData> get() = _comments - - private val _commentOfferingInfo = MutableLiveData() - val commentOfferingInfo: LiveData get() = _commentOfferingInfo - - private val _meetings = MutableLiveData() - val meetings: LiveData get() = _meetings +class CommentDetailViewModel( + private val offeringId: Long, + private val authRepository: AuthRepository, + private val offeringRepository: OfferingRepository, + private val participantRepository: ParticipantRepository, + private val commentDetailRepository: CommentDetailRepository, +) : ViewModel() { + private var cachedComments: List = emptyList() + private var pollJob: Job? = null + val commentContent = MutableLiveData("") - private val _isCollapsibleViewVisible = MutableLiveData(false) - val isCollapsibleViewVisible: LiveData get() = _isCollapsibleViewVisible + private val _comments: MutableLiveData> = MutableLiveData() + val comments: LiveData> get() = _comments - private val _participants = MutableLiveData() - val participants: LiveData get() = _participants + private val _commentOfferingInfo = MutableLiveData() + val commentOfferingInfo: LiveData get() = _commentOfferingInfo - private val _showStatusDialogEvent = MutableLiveData() - val showStatusDialogEvent: LiveData get() = _showStatusDialogEvent + private val _meetings = MutableLiveData() + val meetings: LiveData get() = _meetings - private val _reportEvent: MutableSingleLiveData = MutableSingleLiveData() - val reportEvent: SingleLiveData get() = _reportEvent + private val _isCollapsibleViewVisible = MutableLiveData(false) + val isCollapsibleViewVisible: LiveData get() = _isCollapsibleViewVisible - private val _onExitOfferingEvent = MutableSingleLiveData() - val onExitOfferingEvent: SingleLiveData get() = _onExitOfferingEvent + private val _participants = MutableLiveData() + val participants: LiveData get() = _participants - private val _onBackPressedEvent = MutableSingleLiveData() - val onBackPressedEvent: SingleLiveData get() = _onBackPressedEvent + private val _showStatusDialogEvent = MutableLiveData() + val showStatusDialogEvent: LiveData get() = _showStatusDialogEvent - private val _errorEvent = MutableLiveData() - val errorEvent: MutableLiveData get() = _errorEvent + private val _reportEvent: MutableSingleLiveData = MutableSingleLiveData() + val reportEvent: SingleLiveData get() = _reportEvent - private val _showAlertEvent = MutableSingleLiveData() - val showAlertEvent: SingleLiveData get() = _showAlertEvent + private val _onExitOfferingEvent = MutableSingleLiveData() + val onExitOfferingEvent: SingleLiveData get() = _onExitOfferingEvent - private val _alertCancelEvent = MutableSingleLiveData() - val alertCancelEvent: SingleLiveData get() = _alertCancelEvent + private val _onBackPressedEvent = MutableSingleLiveData() + val onBackPressedEvent: SingleLiveData get() = _onBackPressedEvent - init { - startPolling() - updateCommentInfo() - loadMeetings() - loadParticipants() - } + private val _errorEvent = MutableLiveData() + val errorEvent: MutableLiveData get() = _errorEvent - private fun startPolling() { - pollJob?.cancel() - pollJob = - viewModelScope.launch { - while (this.isActive) { - loadComments() - delay(1000) - } - } - } + init { + startPolling() + updateCommentInfo() + loadMeetings() + loadParticipants() + } - private fun updateCommentInfo() { + private fun startPolling() { + pollJob?.cancel() + pollJob = viewModelScope.launch { - when (val result = commentDetailRepository.fetchCommentOfferingInfo(offeringId)) { - is Result.Success -> _commentOfferingInfo.value = result.data.toUiModel() - is Result.Error -> - when (result.error) { - DataError.Network.UNAUTHORIZED -> { - when (authRepository.saveRefresh()) { - is Result.Success -> updateCommentInfo() - is Result.Error -> return@launch - } - } - - else -> { - errorEvent.value = result.error.name - } - } + while (this.isActive) { + loadComments() + delay(1000) } } - } - - fun updateOfferingEvent() { - _showStatusDialogEvent.value = Unit - } + } - fun updateOfferingStatus() { - viewModelScope.launch { - when (val result = commentDetailRepository.updateOfferingStatus(offeringId)) { - is Result.Success -> updateCommentInfo() - is Result.Error -> - when (result.error) { - DataError.Network.UNAUTHORIZED -> { - when (authRepository.saveRefresh()) { - is Result.Success -> updateOfferingStatus() - is Result.Error -> return@launch - } - } - - else -> { - errorEvent.value = result.error.name - } + private fun updateCommentInfo() { + viewModelScope.launch { + when (val result = commentDetailRepository.fetchCommentOfferingInfo(offeringId)) { + is Result.Success -> _commentOfferingInfo.value = result.data.toUiModel() + is Result.Error -> + when (result.error) { + DataError.Network.UNAUTHORIZED -> { + authRepository.saveRefresh() + updateCommentInfo() } - } + else -> { + errorEvent.value = result.error.name + } + } } } + } - fun loadComments() { - viewModelScope.launch { - when (val result = commentDetailRepository.fetchComments(offeringId)) { - is Result.Success -> { - val newComments = result.data - if (cachedComments != newComments) { - _comments.value = newComments - cachedComments = newComments + fun updateOfferingEvent() { + _showStatusDialogEvent.value = Unit + } + + fun updateOfferingStatus() { + viewModelScope.launch { + when (val result = commentDetailRepository.updateOfferingStatus(offeringId)) { + is Result.Success -> updateCommentInfo() + is Result.Error -> + when (result.error) { + DataError.Network.UNAUTHORIZED -> { + authRepository.saveRefresh() + updateOfferingStatus() } - } - is Result.Error -> - when (result.error) { - DataError.Network.UNAUTHORIZED -> { - when (authRepository.saveRefresh()) { - is Result.Success -> loadComments() - is Result.Error -> return@launch - } - } - - else -> { - pollJob?.cancel() - errorEvent.value = result.error.name - } + else -> { + errorEvent.value = result.error.name } - } + } } } + } - fun postComment() { - val content = commentContent.value?.trim() - if (content.isNullOrEmpty()) { - return - } - viewModelScope.launch { - when (val result = commentDetailRepository.saveComment(offeringId, content)) { - is Result.Success -> { - commentContent.value = "" + fun loadComments() { + viewModelScope.launch { + when (val result = commentDetailRepository.fetchComments(offeringId)) { + is Result.Success -> { + val newComments = result.data + if (cachedComments != newComments) { + _comments.value = newComments + cachedComments = newComments } + } - is Result.Error -> - when (result.error) { - DataError.Network.UNAUTHORIZED -> { - when (authRepository.saveRefresh()) { - is Result.Success -> postComment() - is Result.Error -> return@launch - } - } - - else -> { - errorEvent.value = result.error.name - } + is Result.Error -> + when (result.error) { + DataError.Network.UNAUTHORIZED -> { + authRepository.saveRefresh() + loadComments() } - } + else -> { + pollJob?.cancel() + errorEvent.value = result.error.name + } + } } } + } - fun toggleCollapsibleView() { - _isCollapsibleViewVisible.value = _isCollapsibleViewVisible.value?.not() - if (_isCollapsibleViewVisible.value == true) { - loadMeetings() - } + fun postComment() { + val content = commentContent.value?.trim() + if (content.isNullOrEmpty()) { + return } - private fun loadParticipants() { - viewModelScope.launch { - when (val result = participantRepository.fetchParticipants(offeringId)) { - is Result.Success -> _participants.value = result.data.toUiModel() - is Result.Error -> - when (result.error) { - DataError.Network.UNAUTHORIZED -> { - when (authRepository.saveRefresh()) { - is Result.Success -> loadParticipants() - is Result.Error -> return@launch - } - } - - else -> { - errorEvent.value = result.error.name - } - } + viewModelScope.launch { + when (val result = commentDetailRepository.saveComment(offeringId, content)) { + is Result.Success -> { + commentContent.value = "" } - } - } - private fun loadMeetings() { - viewModelScope.launch { - when (val result = offeringRepository.fetchMeetings(offeringId)) { - is Result.Success -> _meetings.value = result.data.toUiModel() - is Result.Error -> - when (result.error) { - DataError.Network.UNAUTHORIZED -> { - when (authRepository.saveRefresh()) { - is Result.Success -> loadMeetings() - is Result.Error -> return@launch - } - } - - else -> { - errorEvent.value = result.error.name - } + is Result.Error -> + when (result.error) { + DataError.Network.UNAUTHORIZED -> { + authRepository.saveRefresh() + postComment() } - } + else -> { + errorEvent.value = result.error.name + } + } } } + } - fun onClickReport() { - _reportEvent.setValue(R.string.report_url) + fun toggleCollapsibleView() { + _isCollapsibleViewVisible.value = _isCollapsibleViewVisible.value?.not() + if (_isCollapsibleViewVisible.value == true) { + loadMeetings() } + } - fun exitOffering() { - viewModelScope.launch { - when (val result = participantRepository.deleteParticipations(offeringId)) { - is Result.Success -> { - _onExitOfferingEvent.setValue(Unit) - pollJob?.cancel() - } - - is Result.Error -> - when (result.error) { - DataError.Network.NULL -> { - _onExitOfferingEvent.setValue(Unit) - pollJob?.cancel() - } - - DataError.Network.UNAUTHORIZED -> { - when (authRepository.saveRefresh()) { - is Result.Success -> exitOffering() - is Result.Error -> return@launch - } - } - - else -> { - _errorEvent.value = result.error.name - } + private fun loadParticipants() { + viewModelScope.launch { + when (val result = participantRepository.fetchParticipants(offeringId)) { + is Result.Success -> _participants.value = result.data.toUiModel() + is Result.Error -> + when (result.error) { + DataError.Network.UNAUTHORIZED -> { + authRepository.saveRefresh() + loadParticipants() } - } + else -> { + errorEvent.value = result.error.name + } + } } } + } - fun onBackClick() { - _onBackPressedEvent.setValue(Unit) - } - - override fun onCleared() { - super.onCleared() - stopPolling() + private fun loadMeetings() { + viewModelScope.launch { + when (val result = offeringRepository.fetchMeetings(offeringId)) { + is Result.Success -> _meetings.value = result.data.toUiModel() + is Result.Error -> + when (result.error) { + DataError.Network.UNAUTHORIZED -> { + authRepository.saveRefresh() + loadMeetings() + } + else -> { + errorEvent.value = result.error.name + } + } + } } + } - private fun stopPolling() { - pollJob?.cancel() - } + fun onClickReport() { + _reportEvent.setValue(R.string.report_url) + } - companion object { - @Suppress("UNCHECKED_CAST") - fun getFactory( - assistedFactory: CommentDetailAssistedFactory, - offeringId: Long, - ) = object : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - return assistedFactory.create(offeringId) as T + fun exitOffering() { + viewModelScope.launch { + when (val result = participantRepository.deleteParticipations(offeringId)) { + is Result.Success -> { + _onExitOfferingEvent.setValue(Unit) + pollJob?.cancel() } + is Result.Error -> + when (result.error) { + DataError.Network.NULL -> { + _onExitOfferingEvent.setValue(Unit) + pollJob?.cancel() + } + DataError.Network.UNAUTHORIZED -> { + authRepository.saveRefresh() + exitOffering() + } + else -> { + _errorEvent.value = result.error.name + } + } } } + } - fun onExitClick() { - _showAlertEvent.setValue(Unit) - } + fun onBackClick() { + _onBackPressedEvent.setValue(Unit) + } - override fun onClickConfirm() { - exitOffering() - } + override fun onCleared() { + super.onCleared() + stopPolling() + } + + private fun stopPolling() { + pollJob?.cancel() + } - override fun onClickCancel() { - _alertCancelEvent.setValue(Unit) + companion object { + @Suppress("UNCHECKED_CAST") + fun getFactory( + offeringId: Long, + authRepository: AuthRepository, + offeringRepository: OfferingRepository, + participantRepository: ParticipantRepository, + commentDetailRepository: CommentDetailRepository, + ) = object : ViewModelProvider.Factory { + override fun create( + modelClass: Class, + extras: CreationExtras, + ): T { + return CommentDetailViewModel( + offeringId = offeringId, + authRepository = authRepository, + offeringRepository = offeringRepository, + participantRepository = participantRepository, + commentDetailRepository = commentDetailRepository, + ) as T + } } } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/home/HomeFragment.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/home/HomeFragment.kt index ae52cdfd0..81fcd2dd5 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/home/HomeFragment.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/home/HomeFragment.kt @@ -22,27 +22,33 @@ import androidx.paging.PagingData import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import com.google.firebase.analytics.FirebaseAnalytics +import com.zzang.chongdae.ChongdaeApp +import com.zzang.chongdae.ChongdaeApp.Companion.dataStore import com.zzang.chongdae.R -import com.zzang.chongdae.common.firebase.FirebaseAnalyticsManager +import com.zzang.chongdae.data.local.source.UserPreferencesDataStore import com.zzang.chongdae.databinding.FragmentHomeBinding import com.zzang.chongdae.domain.model.FilterName -import com.zzang.chongdae.presentation.util.setDebouncedOnClickListener +import com.zzang.chongdae.presentation.util.FirebaseAnalyticsManager import com.zzang.chongdae.presentation.view.MainActivity import com.zzang.chongdae.presentation.view.home.adapter.OfferingAdapter import com.zzang.chongdae.presentation.view.login.LoginActivity import com.zzang.chongdae.presentation.view.offeringdetail.OfferingDetailFragment import com.zzang.chongdae.presentation.view.write.OfferingWriteOptionalFragment -import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch -@AndroidEntryPoint class HomeFragment : Fragment(), OnOfferingClickListener { private var _binding: FragmentHomeBinding? = null private val binding get() = _binding!! private var toast: Toast? = null private lateinit var offeringAdapter: OfferingAdapter - private val viewModel: OfferingViewModel by viewModels() + private val viewModel: OfferingViewModel by viewModels { + OfferingViewModel.getFactory( + offeringRepository = (requireActivity().application as ChongdaeApp).offeringRepository, + authRepository = (requireActivity().applicationContext as ChongdaeApp).authRepository, + userPreferencesDataStore = UserPreferencesDataStore(requireActivity().applicationContext.dataStore), + ) + } private val firebaseAnalytics: FirebaseAnalytics by lazy { FirebaseAnalytics.getInstance(requireContext()) @@ -71,14 +77,6 @@ class HomeFragment : Fragment(), OnOfferingClickListener { navigateToOfferingWriteFragment() initFragmentResultListener() setOnCheckboxListener() - setOnSwipeRefreshListener() - } - - private fun setOnSwipeRefreshListener() { - binding.swipeLayout.setOnRefreshListener { - binding.swipeLayout.isRefreshing = false - viewModel.swipeRefresh() - } } private fun setOnCheckboxListener() { @@ -121,12 +119,8 @@ class HomeFragment : Fragment(), OnOfferingClickListener { viewModel.fetchUpdatedOffering(bundle.getLong(OfferingDetailFragment.UPDATED_OFFERING_ID_KEY)) } - setFragmentResultListener(OfferingDetailFragment.OFFERING_DETAIL_BUNDLE_KEY) { _, bundle -> - viewModel.refreshOfferings(bundle.getBoolean(OfferingDetailFragment.DELETED_OFFERING_ID_KEY)) - } - setFragmentResultListener(OfferingWriteOptionalFragment.OFFERING_WRITE_BUNDLE_KEY) { _, bundle -> - viewModel.refreshOfferings( + viewModel.refreshOfferingsByOfferingWriteEvent( bundle.getBoolean( OfferingWriteOptionalFragment.NEW_OFFERING_EVENT_KEY, ), @@ -170,13 +164,6 @@ class HomeFragment : Fragment(), OnOfferingClickListener { private fun initAdapter() { offeringAdapter = OfferingAdapter(this) - offeringAdapter.addLoadStateListener { - if (it.append.endOfPaginationReached) { - binding.tvEmptyItem.isVisible = isItemEmpty() - } else { - binding.tvEmptyItem.isVisible = false - } - } viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { offeringAdapter.loadStateFlow.collect { loadState -> @@ -193,8 +180,6 @@ class HomeFragment : Fragment(), OnOfferingClickListener { ) } - private fun isItemEmpty() = offeringAdapter.itemCount == 0 - private fun setUpOfferingsObserve() { viewModel.offeringsRefreshEvent.observe(viewLifecycleOwner) { offeringAdapter.submitData(viewLifecycleOwner.lifecycle, PagingData.empty()) @@ -244,7 +229,7 @@ class HomeFragment : Fragment(), OnOfferingClickListener { } private fun navigateToOfferingWriteFragment() { - binding.fabCreateOffering.setDebouncedOnClickListener { + binding.fabCreateOffering.setOnClickListener { findNavController().navigate(R.id.action_home_fragment_to_offering_write_fragment) } } diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/home/OfferingViewModel.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/home/OfferingViewModel.kt index d8ec6d13d..c593cd553 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/home/OfferingViewModel.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/home/OfferingViewModel.kt @@ -4,234 +4,239 @@ import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.map import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData import androidx.paging.cachedIn import androidx.paging.map import com.zzang.chongdae.R -import com.zzang.chongdae.auth.repository.AuthRepository -import com.zzang.chongdae.common.datastore.UserPreferencesDataStore -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result -import com.zzang.chongdae.di.annotations.AuthRepositoryQualifier -import com.zzang.chongdae.di.annotations.OfferingRepositoryQualifier +import com.zzang.chongdae.data.local.source.UserPreferencesDataStore import com.zzang.chongdae.domain.model.Filter import com.zzang.chongdae.domain.model.FilterName import com.zzang.chongdae.domain.model.Offering import com.zzang.chongdae.domain.paging.OfferingPagingSource +import com.zzang.chongdae.domain.repository.AuthRepository import com.zzang.chongdae.domain.repository.OfferingRepository +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result import com.zzang.chongdae.presentation.util.MutableSingleLiveData import com.zzang.chongdae.presentation.util.SingleLiveData -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class OfferingViewModel - @Inject - constructor( - @OfferingRepositoryQualifier private val offeringRepository: OfferingRepository, - @AuthRepositoryQualifier private val authRepository: AuthRepository, - private val userPreferencesDataStore: UserPreferencesDataStore, - ) : ViewModel(), OnFilterClickListener, OnSearchClickListener { - private val _offerings = MutableLiveData>() - val offerings: LiveData> get() = _offerings - - val search: MutableLiveData = MutableLiveData(null) - - private val _filters: MutableLiveData> = MutableLiveData() - val filters: LiveData> get() = _filters - - val joinableFilter: LiveData = - _filters.map { - it.first { it.name == FilterName.JOINABLE } - } - val imminentFilter: LiveData = - _filters.map { - it.first { it.name == FilterName.IMMINENT } - } +class OfferingViewModel( + private val offeringRepository: OfferingRepository, + private val authRepository: AuthRepository, + private val userPreferencesDataStore: UserPreferencesDataStore, +) : ViewModel(), OnFilterClickListener, OnSearchClickListener { + private val _offerings = MutableLiveData>() + val offerings: LiveData> get() = _offerings - val highDiscountFilter: LiveData = - _filters.map { - it.first { it.name == FilterName.HIGH_DISCOUNT } - } + val search: MutableLiveData = MutableLiveData(null) - private val _selectedFilter: MutableLiveData = MutableLiveData() - val selectedFilter: LiveData get() = _selectedFilter + private val _filters: MutableLiveData> = MutableLiveData() + val filters: LiveData> get() = _filters - private val _searchEvent: MutableSingleLiveData = MutableSingleLiveData(null) - val searchEvent: SingleLiveData get() = _searchEvent + val joinableFilter: LiveData = + _filters.map { + it.first { it.name == FilterName.JOINABLE } + } - private val _filterOfferingsEvent: MutableSingleLiveData = MutableSingleLiveData() - val filterOfferingsEvent: SingleLiveData get() = _filterOfferingsEvent + val imminentFilter: LiveData = + _filters.map { + it.first { it.name == FilterName.IMMINENT } + } - private val _updatedOffering: MutableSingleLiveData> = - MutableSingleLiveData(mutableListOf()) - val updatedOffering: SingleLiveData> get() = _updatedOffering + val highDiscountFilter: LiveData = + _filters.map { + it.first { it.name == FilterName.HIGH_DISCOUNT } + } - private val _offeringsRefreshEvent: MutableSingleLiveData = MutableSingleLiveData() - val offeringsRefreshEvent: SingleLiveData get() = _offeringsRefreshEvent + private val _selectedFilter: MutableLiveData = MutableLiveData() + val selectedFilter: LiveData get() = _selectedFilter - private val _error: MutableSingleLiveData = MutableSingleLiveData() - val error: SingleLiveData get() = _error + private val _searchEvent: MutableSingleLiveData = MutableSingleLiveData(null) + val searchEvent: SingleLiveData get() = _searchEvent - private val _refreshTokenExpiredEvent: MutableSingleLiveData = MutableSingleLiveData() - val refreshTokenExpiredEvent: SingleLiveData get() = _refreshTokenExpiredEvent + private val _filterOfferingsEvent: MutableSingleLiveData = MutableSingleLiveData() + val filterOfferingsEvent: SingleLiveData get() = _filterOfferingsEvent - init { - fetchFilters() - fetchOfferings() - } + private val _updatedOffering: MutableSingleLiveData> = + MutableSingleLiveData(mutableListOf()) + val updatedOffering: SingleLiveData> get() = _updatedOffering + + private val _offeringsRefreshEvent: MutableSingleLiveData = MutableSingleLiveData() + val offeringsRefreshEvent: SingleLiveData get() = _offeringsRefreshEvent + + private val _error: MutableSingleLiveData = MutableSingleLiveData() + val error: SingleLiveData get() = _error + + private val _refreshTokenExpiredEvent: MutableSingleLiveData = MutableSingleLiveData() + val refreshTokenExpiredEvent: SingleLiveData get() = _refreshTokenExpiredEvent + + init { + fetchFilters() + fetchOfferings() + } - private fun fetchOfferings() { - viewModelScope.launch { - Pager( - config = PagingConfig(pageSize = PAGE_SIZE, enablePlaceholders = false), - pagingSourceFactory = { - OfferingPagingSource( - offeringRepository, - authRepository, - search.value, - _selectedFilter.value, - ) { fetchOfferings() } - }, - ).flow.cachedIn(viewModelScope).collectLatest { pagingData -> - _offerings.value = - pagingData.map { - if (isSearchKeywordExist() && isTitleContainSearchKeyword(it)) { - return@map it.copy( - title = - highlightSearchKeyword( - it.title, - search.value!!, - ), - ) - } - it.copy(title = removeAsterisks(it.title)) + private fun fetchOfferings() { + viewModelScope.launch { + Pager( + config = PagingConfig(pageSize = PAGE_SIZE, enablePlaceholders = false), + pagingSourceFactory = { + OfferingPagingSource( + offeringRepository, + authRepository, + search.value, + _selectedFilter.value, + ) { fetchOfferings() } + }, + ).flow.cachedIn(viewModelScope).collectLatest { pagingData -> + _offerings.value = + pagingData.map { + if (isSearchKeywordExist() && isTitleContainSearchKeyword(it)) { + return@map it.copy( + title = + highlightSearchKeyword( + it.title, + search.value!!, + ), + ) } - } + it.copy(title = removeAsterisks(it.title)) + } } } + } - private fun removeAsterisks(title: String): String { - return title.replace("*", "") - } + private fun removeAsterisks(title: String): String { + return title.replace("*", "") + } - private fun highlightSearchKeyword( - title: String, - keyword: String, - ): String { - return title.replace(keyword, "*$keyword*") - } + private fun highlightSearchKeyword( + title: String, + keyword: String, + ): String { + return title.replace(keyword, "*$keyword*") + } + + private fun isTitleContainSearchKeyword(it: Offering) = (search.value as String) in it.title + + private fun isSearchKeywordExist() = (search.value != null) && (search.value != "") - private fun isTitleContainSearchKeyword(it: Offering) = (search.value as String) in it.title - - private fun isSearchKeywordExist() = (search.value != null) && (search.value != "") - - private fun fetchFilters() { - viewModelScope.launch { - when (val result = offeringRepository.fetchFilters()) { - is Result.Error -> { - Log.d("error", "fetchFilters: ${result.error}") - when (result.error) { - DataError.Network.UNAUTHORIZED -> { - when (authRepository.saveRefresh()) { - is Result.Success -> fetchFilters() - is Result.Error -> { - userPreferencesDataStore.removeAllData() - _refreshTokenExpiredEvent.setValue(Unit) - return@launch - } - } - } - - DataError.Network.BAD_REQUEST -> { - _error.setValue(R.string.home_filter_error_message) - } - - else -> { - Log.e("error", "fetchFilters Error: ${result.error.name}") - } + private fun fetchFilters() { + viewModelScope.launch { + when (val result = offeringRepository.fetchFilters()) { + is Result.Error -> { + Log.d("error", "fetchFilters: ${result.error}") + when (result.error) { + DataError.Network.UNAUTHORIZED -> { + authRepository.saveRefresh() + fetchFilters() + } + + DataError.Network.FORBIDDEN -> { + userPreferencesDataStore.removeAllData() + _refreshTokenExpiredEvent.setValue(Unit) } - } - is Result.Success -> { - _filters.value = result.data + DataError.Network.BAD_REQUEST -> { + _error.setValue(R.string.home_filter_error_message) + } + + else -> { + Log.e("error", "fetchFilters Error: ${result.error.name}") + } } } - } - } - override fun onClickFilter( - filterName: FilterName, - isChecked: Boolean, - ) { - if (isChecked) { - _selectedFilter.value = filterName.toString() - } else { - _selectedFilter.value = null + is Result.Success -> { + _filters.value = result.data + } } - - _filterOfferingsEvent.setValue(Unit) - fetchOfferings() } + } - override fun onClickSearchButton() { - _searchEvent.setValue(search.value) - fetchOfferings() + override fun onClickFilter( + filterName: FilterName, + isChecked: Boolean, + ) { + if (isChecked) { + _selectedFilter.value = filterName.toString() + } else { + _selectedFilter.value = null } - fun fetchUpdatedOffering(offeringId: Long) { - viewModelScope.launch { - when (val result = offeringRepository.fetchOffering(offeringId)) { - is Result.Error -> { - when (result.error) { - DataError.Network.UNAUTHORIZED -> { - when (authRepository.saveRefresh()) { - is Result.Success -> fetchUpdatedOffering(offeringId) - is Result.Error -> return@launch - } - } - - DataError.Network.BAD_REQUEST -> { - _error.setValue(R.string.home_updated_offering_error_mesasge) - } - - else -> { - Log.e("error", "fetchUpdatedOffering Error: ${result.error.name}") - } + _filterOfferingsEvent.setValue(Unit) + fetchOfferings() + } + + override fun onClickSearchButton() { + _searchEvent.setValue(search.value) + fetchOfferings() + } + + fun fetchUpdatedOffering(offeringId: Long) { + viewModelScope.launch { + when (val result = offeringRepository.fetchOffering(offeringId)) { + is Result.Error -> { + when (result.error) { + DataError.Network.UNAUTHORIZED -> { + authRepository.saveRefresh() + fetchUpdatedOffering(offeringId) } - } - is Result.Success -> { - val updatedOfferings = _updatedOffering.getValue() ?: mutableListOf() - updatedOfferings.add(result.data) - _updatedOffering.setValue(updatedOfferings) + DataError.Network.BAD_REQUEST -> { + _error.setValue(R.string.home_updated_offering_error_mesasge) + } + + else -> { + Log.e("error", "fetchUpdatedOffering Error: ${result.error.name}") + } } } - } - } - fun refreshOfferings(isSuccess: Boolean) { - if (isSuccess) { - search.value = null - _selectedFilter.value = null - _offeringsRefreshEvent.setValue(Unit) - fetchOfferings() + is Result.Success -> { + val updatedOfferings = _updatedOffering.getValue() ?: mutableListOf() + updatedOfferings.add(result.data) + _updatedOffering.setValue(updatedOfferings) + } } } + } - fun swipeRefresh() { + fun refreshOfferingsByOfferingWriteEvent(isSuccess: Boolean) { + if (isSuccess) { + search.value = null + _selectedFilter.value = null _offeringsRefreshEvent.setValue(Unit) fetchOfferings() } + } - companion object { - private const val PAGE_SIZE = 10 + companion object { + private const val PAGE_SIZE = 10 + + @Suppress("UNCHECKED_CAST") + fun getFactory( + offeringRepository: OfferingRepository, + authRepository: AuthRepository, + userPreferencesDataStore: UserPreferencesDataStore, + ) = object : ViewModelProvider.Factory { + override fun create( + modelClass: Class, + extras: CreationExtras, + ): T { + return OfferingViewModel( + offeringRepository, + authRepository, + userPreferencesDataStore, + ) as T + } } } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/login/LoginActivity.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/login/LoginActivity.kt index a4e33ec8f..450fff06e 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/login/LoginActivity.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/login/LoginActivity.kt @@ -11,17 +11,23 @@ import com.kakao.sdk.auth.model.OAuthToken import com.kakao.sdk.common.model.ClientError import com.kakao.sdk.common.model.ClientErrorCause import com.kakao.sdk.user.UserApiClient -import com.zzang.chongdae.common.firebase.FirebaseAnalyticsManager +import com.zzang.chongdae.ChongdaeApp +import com.zzang.chongdae.ChongdaeApp.Companion.dataStore +import com.zzang.chongdae.data.local.source.UserPreferencesDataStore import com.zzang.chongdae.databinding.ActivityLoginBinding +import com.zzang.chongdae.presentation.util.FirebaseAnalyticsManager import com.zzang.chongdae.presentation.view.MainActivity -import dagger.hilt.android.AndroidEntryPoint -@AndroidEntryPoint class LoginActivity : AppCompatActivity(), OnAuthClickListener { private var _binding: ActivityLoginBinding? = null private val binding get() = _binding!! - private val viewModel: LoginViewModel by viewModels() + private val viewModel: LoginViewModel by viewModels { + LoginViewModel.getFactory( + authRepository = (application as ChongdaeApp).authRepository, + userPreferencesDataStore = UserPreferencesDataStore(applicationContext.dataStore), + ) + } private val firebaseAnalytics: FirebaseAnalytics by lazy { FirebaseAnalytics.getInstance(this) diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/login/LoginViewModel.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/login/LoginViewModel.kt index 00815f981..c3726d2c3 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/login/LoginViewModel.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/login/LoginViewModel.kt @@ -2,56 +2,67 @@ package com.zzang.chongdae.presentation.view.login import android.util.Log import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope -import com.zzang.chongdae.auth.repository.AuthRepository -import com.zzang.chongdae.common.datastore.UserPreferencesDataStore -import com.zzang.chongdae.common.handler.Result -import com.zzang.chongdae.di.annotations.AuthRepositoryQualifier +import androidx.lifecycle.viewmodel.CreationExtras +import com.zzang.chongdae.data.local.source.UserPreferencesDataStore +import com.zzang.chongdae.domain.repository.AuthRepository +import com.zzang.chongdae.domain.util.Result import com.zzang.chongdae.presentation.util.MutableSingleLiveData import com.zzang.chongdae.presentation.util.SingleLiveData -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class LoginViewModel - @Inject - constructor( - @AuthRepositoryQualifier private val authRepository: AuthRepository, - private val userPreferencesDataStore: UserPreferencesDataStore, - ) : ViewModel() { - private val _loginSuccessEvent: MutableSingleLiveData = MutableSingleLiveData() - val loginSuccessEvent: SingleLiveData get() = _loginSuccessEvent - - private val _alreadyLoggedInEvent: MutableSingleLiveData = MutableSingleLiveData() - val alreadyLoggedInEvent: SingleLiveData get() = _alreadyLoggedInEvent - - init { - makeAlreadyLoggedInEvent() + +class LoginViewModel( + private val authRepository: AuthRepository, + private val userPreferencesDataStore: UserPreferencesDataStore, +) : ViewModel() { + private val _loginSuccessEvent: MutableSingleLiveData = MutableSingleLiveData() + val loginSuccessEvent: SingleLiveData get() = _loginSuccessEvent + + private val _alreadyLoggedInEvent: MutableSingleLiveData = MutableSingleLiveData() + val alreadyLoggedInEvent: SingleLiveData get() = _alreadyLoggedInEvent + + init { + makeAlreadyLoggedInEvent() + } + + private fun makeAlreadyLoggedInEvent() { + viewModelScope.launch { + val accessToken = userPreferencesDataStore.accessTokenFlow.first() + if (accessToken != null) { + _alreadyLoggedInEvent.setValue(Unit) + } } + } + + fun postLogin(accessToken: String) { + viewModelScope.launch { + when (val result = authRepository.saveLogin(accessToken = accessToken)) { + is Result.Success -> { + userPreferencesDataStore.saveMember(result.data.memberId, result.data.nickName) + _loginSuccessEvent.setValue(Unit) + } - private fun makeAlreadyLoggedInEvent() { - viewModelScope.launch { - val accessToken = userPreferencesDataStore.accessTokenFlow.first() - if (accessToken != null) { - _alreadyLoggedInEvent.setValue(Unit) + is Result.Error -> { + Log.e("error", "postLogin: ${result.error}") } } } + } - fun postLogin(accessToken: String) { - viewModelScope.launch { - when (val result = authRepository.saveLogin(accessToken = accessToken)) { - is Result.Success -> { - userPreferencesDataStore.saveMember(result.data.memberId, result.data.nickName) - _loginSuccessEvent.setValue(Unit) - } - - is Result.Error -> { - Log.e("error", "postLogin: ${result.error}") - } - } + companion object { + @Suppress("UNCHECKED_CAST") + fun getFactory( + authRepository: AuthRepository, + userPreferencesDataStore: UserPreferencesDataStore, + ) = object : ViewModelProvider.Factory { + override fun create( + modelClass: Class, + extras: CreationExtras, + ): T { + return LoginViewModel(authRepository, userPreferencesDataStore) as T } } } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/mypage/MyPageFragment.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/mypage/MyPageFragment.kt index fd6a3ac10..c00226a20 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/mypage/MyPageFragment.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/mypage/MyPageFragment.kt @@ -1,6 +1,5 @@ package com.zzang.chongdae.presentation.view.mypage -import android.app.Dialog import android.content.Intent import android.net.Uri import android.os.Bundle @@ -10,24 +9,19 @@ import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import com.google.firebase.analytics.FirebaseAnalytics -import com.zzang.chongdae.R -import com.zzang.chongdae.common.firebase.FirebaseAnalyticsManager -import com.zzang.chongdae.databinding.DialogAlertBinding +import com.zzang.chongdae.ChongdaeApp.Companion.dataStore +import com.zzang.chongdae.data.local.source.UserPreferencesDataStore import com.zzang.chongdae.databinding.FragmentMyPageBinding +import com.zzang.chongdae.presentation.util.FirebaseAnalyticsManager import com.zzang.chongdae.presentation.view.login.LoginActivity -import dagger.hilt.android.AndroidEntryPoint -@AndroidEntryPoint class MyPageFragment : Fragment() { private var _binding: FragmentMyPageBinding? = null private val binding get() = _binding!! - private var _alertBinding: DialogAlertBinding? = null - private val alertBinding get() = _alertBinding!! - - private val alert: Dialog by lazy { Dialog(requireContext()) } - - private val viewModel: MyPageViewModel by viewModels() + private val viewModel: MyPageViewModel by viewModels { + MyPageViewModel.getFactory(UserPreferencesDataStore(requireContext().dataStore)) + } private val firebaseAnalytics: FirebaseAnalytics by lazy { FirebaseAnalytics.getInstance(requireContext()) @@ -52,10 +46,6 @@ class MyPageFragment : Fragment() { _binding = FragmentMyPageBinding.inflate(inflater, container, false) binding.vm = viewModel binding.lifecycleOwner = viewLifecycleOwner - - _alertBinding = DialogAlertBinding.inflate(inflater, container, false) - alertBinding.listener = viewModel - alertBinding.tvDialogMessage.text = getString(R.string.my_page_logout_dialog_description) } override fun onViewCreated( @@ -70,13 +60,6 @@ class MyPageFragment : Fragment() { viewModel.openUrlInBrowserEvent.observe(viewLifecycleOwner) { openUrlInBrowser(it) } - viewModel.showAlertEvent.observe(viewLifecycleOwner) { - alert.setContentView(alertBinding.root) - alert.show() - } - viewModel.alertCancelEvent.observe(viewLifecycleOwner) { - alert.dismiss() - } viewModel.logoutEvent.observe(viewLifecycleOwner) { clearDataAndLogout() } diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/mypage/MyPageViewModel.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/mypage/MyPageViewModel.kt index a00cb7664..8a996a133 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/mypage/MyPageViewModel.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/mypage/MyPageViewModel.kt @@ -2,67 +2,59 @@ package com.zzang.chongdae.presentation.view.mypage import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope -import com.zzang.chongdae.common.datastore.UserPreferencesDataStore +import androidx.lifecycle.viewmodel.CreationExtras +import com.zzang.chongdae.data.local.source.UserPreferencesDataStore import com.zzang.chongdae.presentation.util.MutableSingleLiveData import com.zzang.chongdae.presentation.util.SingleLiveData -import com.zzang.chongdae.presentation.view.common.OnAlertClickListener -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch -import javax.inject.Inject -@HiltViewModel -class MyPageViewModel - @Inject - constructor( - private val userPreferencesDataStore: UserPreferencesDataStore, - ) : ViewModel(), - OnAlertClickListener { - val nickName: LiveData = userPreferencesDataStore.nickNameFlow.asLiveData() +class MyPageViewModel(private val userPreferencesDataStore: UserPreferencesDataStore) : ViewModel() { + val nickName: LiveData = userPreferencesDataStore.nickNameFlow.asLiveData() - private val _openUrlInBrowserEvent = MutableSingleLiveData() - val openUrlInBrowserEvent: SingleLiveData get() = _openUrlInBrowserEvent + private val _openUrlInBrowserEvent = MutableSingleLiveData() + val openUrlInBrowserEvent: SingleLiveData get() = _openUrlInBrowserEvent - private val _logoutEvent = MutableSingleLiveData() - val logoutEvent: SingleLiveData get() = _logoutEvent + private val _logoutEvent = MutableSingleLiveData() + val logoutEvent: SingleLiveData get() = _logoutEvent - private val _showAlertEvent = MutableSingleLiveData() - val showAlertEvent: SingleLiveData get() = _showAlertEvent + private val termsOfUseUrl = + "https://silent-apparatus-578.notion.site/f1f5cd1609d4469dba3ab7d0f95c183c?pvs=4" + private val privacyUrl = + "https://silent-apparatus-578.notion.site/f1f5cd1609d4469dba3ab7d0f95c183c?pvs=4" + private val withdrawalUrl = "https://forms.gle/z5MUzVTUoyunfqEu8" - private val _alertCancelEvent = MutableSingleLiveData() - val alertCancelEvent: SingleLiveData get() = _alertCancelEvent - - private val termsOfUseUrl = - "https://silent-apparatus-578.notion.site/f1f5cd1609d4469dba3ab7d0f95c183c?pvs=4" - private val privacyUrl = - "https://silent-apparatus-578.notion.site/f1f5cd1609d4469dba3ab7d0f95c183c?pvs=4" - private val withdrawalUrl = "https://forms.gle/z5MUzVTUoyunfqEu8" - - fun onClickTermsOfUse() { - _openUrlInBrowserEvent.setValue(termsOfUseUrl) - } + fun onClickTermsOfUse() { + _openUrlInBrowserEvent.setValue(termsOfUseUrl) + } - fun onClickPrivacy() { - _openUrlInBrowserEvent.setValue(privacyUrl) - } + fun onClickPrivacy() { + _openUrlInBrowserEvent.setValue(privacyUrl) + } - fun onClickLogout() { - _showAlertEvent.setValue(Unit) + fun onClickLogout() { + viewModelScope.launch { + userPreferencesDataStore.removeAllData() } + _logoutEvent.setValue(Unit) + } - fun onClickWithdrawal() { - _openUrlInBrowserEvent.setValue(withdrawalUrl) - } + fun onClickWithdrawal() { + _openUrlInBrowserEvent.setValue(withdrawalUrl) + } - override fun onClickConfirm() { - viewModelScope.launch { - userPreferencesDataStore.removeAllData() + companion object { + @Suppress("UNCHECKED_CAST") + fun getFactory(userPreferencesDataStore: UserPreferencesDataStore) = + object : ViewModelProvider.Factory { + override fun create( + modelClass: Class, + extras: CreationExtras, + ): T { + return MyPageViewModel(userPreferencesDataStore) as T + } } - _logoutEvent.setValue(Unit) - } - - override fun onClickCancel() { - _alertCancelEvent.setValue(Unit) - } } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/offeringdetail/OfferingDetailFragment.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/offeringdetail/OfferingDetailFragment.kt index 30d0cbaaf..1801288be 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/offeringdetail/OfferingDetailFragment.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/offeringdetail/OfferingDetailFragment.kt @@ -1,6 +1,5 @@ package com.zzang.chongdae.presentation.view.offeringdetail -import android.app.Dialog import android.content.Intent import android.net.Uri import android.os.Bundle @@ -15,35 +14,26 @@ import androidx.fragment.app.setFragmentResult import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.google.firebase.analytics.FirebaseAnalytics -import com.zzang.chongdae.R -import com.zzang.chongdae.common.firebase.FirebaseAnalyticsManager -import com.zzang.chongdae.databinding.DialogAlertBinding -import com.zzang.chongdae.databinding.DialogDeleteOfferingBinding +import com.zzang.chongdae.ChongdaeApp import com.zzang.chongdae.databinding.FragmentOfferingDetailBinding +import com.zzang.chongdae.presentation.util.FirebaseAnalyticsManager import com.zzang.chongdae.presentation.view.MainActivity import com.zzang.chongdae.presentation.view.commentdetail.CommentDetailActivity import com.zzang.chongdae.presentation.view.home.HomeFragment -import com.zzang.chongdae.presentation.view.login.LoginActivity -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject -@AndroidEntryPoint -class OfferingDetailFragment : Fragment(), OnOfferingDeleteAlertClickListener { +class OfferingDetailFragment : Fragment() { private var _binding: FragmentOfferingDetailBinding? = null private val binding get() = _binding!! private var toast: Toast? = null private val offeringId by lazy { - arguments?.getLong(HomeFragment.OFFERING_ID) ?: throw IllegalArgumentException() + arguments?.getLong(HomeFragment.OFFERING_ID) + ?: throw IllegalArgumentException() } - private val dialog: Dialog by lazy { Dialog(requireContext()) } - - @Inject - lateinit var offeringDetailAssistedFactory: OfferingDetailViewModel.OfferingDetailAssistedFactory - private val viewModel: OfferingDetailViewModel by viewModels { OfferingDetailViewModel.getFactory( - assistedFactory = offeringDetailAssistedFactory, offeringId = offeringId, + offeringDetailRepository = (requireActivity().application as ChongdaeApp).offeringDetailRepository, + authRepository = (requireActivity().applicationContext as ChongdaeApp).authRepository, ) } @@ -74,11 +64,6 @@ class OfferingDetailFragment : Fragment(), OnOfferingDeleteAlertClickListener { setUpObserve() } - override fun onResume() { - super.onResume() - viewModel.loadOffering() - } - private fun setUpObserve() { viewModel.updatedOfferingId.observe(viewLifecycleOwner) { setFragmentResult(OFFERING_DETAIL_BUNDLE_KEY, bundleOf(UPDATED_OFFERING_ID_KEY to it)) @@ -95,64 +80,6 @@ class OfferingDetailFragment : Fragment(), OnOfferingDeleteAlertClickListener { viewModel.productLinkRedirectEvent.observe(viewLifecycleOwner) { productURL -> openUrlInBrowser(productURL) } - - viewModel.modifyOfferingEvent.observe(viewLifecycleOwner) { - findNavController().navigate( - R.id.action_offering_detail_fragment_to_offering_modify_essential_fragment, - bundleOf(HomeFragment.OFFERING_ID to offeringId), - ) - } - - viewModel.deleteOfferingEvent.observe(viewLifecycleOwner) { - showUpdateStatusDialog() - } - - viewModel.deleteOfferingSuccessEvent.observe(viewLifecycleOwner) { - dialog.dismiss() - findNavController().popBackStack() - setFragmentResult(OFFERING_DETAIL_BUNDLE_KEY, bundleOf(DELETED_OFFERING_ID_KEY to true)) - showToast(R.string.offering_detail_delete_complete_message) - } - - viewModel.showAlertEvent.observe(viewLifecycleOwner) { - val alertBinding = DialogAlertBinding.inflate(layoutInflater, null, false) - alertBinding.tvDialogMessage.text = getString(R.string.offering_detail_participate_alert) - alertBinding.listener = viewModel - - dialog.setContentView(alertBinding.root) - dialog.show() - } - - viewModel.alertCancelEvent.observe(viewLifecycleOwner) { - dialog.dismiss() - } - } - - override fun onClickConfirm() { - viewModel.deleteOffering(offeringId) - firebaseAnalyticsManager.logSelectContentEvent( - id = "delete_offering_event", - name = "delete_offering_event", - contentType = "button", - ) - } - - override fun onClickCancel() { - firebaseAnalyticsManager.logSelectContentEvent( - id = "cancel_delete_offering_event", - name = "cancel_delete_offering_event", - contentType = "button", - ) - dialog.dismiss() - } - - private fun showUpdateStatusDialog() { - val dialogBinding = DialogDeleteOfferingBinding.inflate(layoutInflater, null, false) - - dialogBinding.listener = this - - dialog.setContentView(dialogBinding.root) - dialog.show() } private fun openUrlInBrowser(url: String) { @@ -178,7 +105,7 @@ class OfferingDetailFragment : Fragment(), OnOfferingDeleteAlertClickListener { } private fun setUpMoveCommentDetailEventObserve() { - viewModel.commentDetailEvent.observe(viewLifecycleOwner) { + viewModel.commentDetailEvent.observe(this) { firebaseAnalyticsManager.logSelectContentEvent( id = "Offering_Item_ID: $offeringId", name = "participate_offering_event", @@ -186,11 +113,6 @@ class OfferingDetailFragment : Fragment(), OnOfferingDeleteAlertClickListener { ) findNavController().popBackStack() CommentDetailActivity.startActivity(requireContext(), offeringId) - dialog.dismiss() - } - - viewModel.refreshTokenExpiredEvent.observe(viewLifecycleOwner) { - LoginActivity.startActivity(requireContext()) } } @@ -210,7 +132,5 @@ class OfferingDetailFragment : Fragment(), OnOfferingDeleteAlertClickListener { companion object { const val OFFERING_DETAIL_BUNDLE_KEY = "offering_detail_bundle_key" const val UPDATED_OFFERING_ID_KEY = "updated_offering_id" - const val DELETED_OFFERING_ID_KEY = "deleted_offering_id" - const val OFFERING_ID_KEY = "offering_id" } } diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/offeringdetail/OfferingDetailViewModel.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/offeringdetail/OfferingDetailViewModel.kt index 21455ec1e..cf65a7a1a 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/offeringdetail/OfferingDetailViewModel.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/offeringdetail/OfferingDetailViewModel.kt @@ -6,255 +6,161 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras import com.zzang.chongdae.R -import com.zzang.chongdae.auth.repository.AuthRepository -import com.zzang.chongdae.common.datastore.UserPreferencesDataStore -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result -import com.zzang.chongdae.di.annotations.AuthRepositoryQualifier -import com.zzang.chongdae.di.annotations.OfferingDetailRepositoryQualifier import com.zzang.chongdae.domain.model.OfferingCondition import com.zzang.chongdae.domain.model.OfferingCondition.Companion.isAvailable import com.zzang.chongdae.domain.model.OfferingDetail +import com.zzang.chongdae.domain.repository.AuthRepository import com.zzang.chongdae.domain.repository.OfferingDetailRepository +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result import com.zzang.chongdae.presentation.util.MutableSingleLiveData import com.zzang.chongdae.presentation.util.SingleLiveData -import com.zzang.chongdae.presentation.view.common.OnAlertClickListener -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject import kotlinx.coroutines.launch -class OfferingDetailViewModel - @AssistedInject - constructor( - @Assisted private val offeringId: Long, - @OfferingDetailRepositoryQualifier val offeringDetailRepository: OfferingDetailRepository, - @AuthRepositoryQualifier private val authRepository: AuthRepository, - private val userPreferencesDataStore: UserPreferencesDataStore, - ) : ViewModel(), - OnParticipationClickListener, - OnOfferingReportClickListener, - OnMoveCommentDetailClickListener, - OnProductLinkClickListener, - OnOfferingModifyClickListener, - OnAlertClickListener { - @AssistedFactory - interface OfferingDetailAssistedFactory { - fun create(offeringId: Long): OfferingDetailViewModel - } - - private val _offeringDetail: MutableLiveData = MutableLiveData() - val offeringDetail: LiveData get() = _offeringDetail - - private val _currentCount: MutableLiveData = MutableLiveData() - val currentCount: LiveData get() = _currentCount - - private val _offeringCondition: MutableLiveData = MutableLiveData() - val offeringCondition: LiveData get() = _offeringCondition - - private val _isParticipated: MutableLiveData = MutableLiveData(false) - val isParticipated: LiveData get() = _isParticipated - - private val _isParticipationAvailable: MutableLiveData = MutableLiveData(true) - val isParticipationAvailable: LiveData get() = _isParticipationAvailable - - private val _isRepresentative: MutableLiveData = MutableLiveData(true) - val isRepresentative: LiveData get() = _isRepresentative +class OfferingDetailViewModel( + private val offeringId: Long, + private val offeringDetailRepository: OfferingDetailRepository, + private val authRepository: AuthRepository, +) : ViewModel(), + OnParticipationClickListener, + OnOfferingReportClickListener, + OnMoveCommentDetailClickListener, + OnProductLinkClickListener { + private val _offeringDetail: MutableLiveData = MutableLiveData() + val offeringDetail: LiveData get() = _offeringDetail - private val _commentDetailEvent: MutableSingleLiveData = MutableSingleLiveData() - val commentDetailEvent: SingleLiveData get() = _commentDetailEvent + private val _currentCount: MutableLiveData = MutableLiveData() + val currentCount: LiveData get() = _currentCount - private val _updatedOfferingId: MutableLiveData = MutableLiveData() - val updatedOfferingId: LiveData get() = _updatedOfferingId + private val _offeringCondition: MutableLiveData = MutableLiveData() + val offeringCondition: LiveData get() = _offeringCondition - private val _reportEvent: MutableSingleLiveData = MutableSingleLiveData() - val reportEvent: SingleLiveData get() = _reportEvent + private val _isParticipated: MutableLiveData = MutableLiveData(false) + val isParticipated: LiveData get() = _isParticipated - private val _productLinkRedirectEvent: MutableSingleLiveData = MutableSingleLiveData() - val productLinkRedirectEvent: SingleLiveData get() = _productLinkRedirectEvent + private val _isParticipationAvailable: MutableLiveData = MutableLiveData(true) + val isParticipationAvailable: LiveData get() = _isParticipationAvailable - private val _error: MutableSingleLiveData = MutableSingleLiveData() - val error: SingleLiveData get() = _error + private val _isRepresentative: MutableLiveData = MutableLiveData(true) + val isRepresentative: LiveData get() = _isRepresentative - private val _modifyOfferingEvent: MutableSingleLiveData = MutableSingleLiveData() - val modifyOfferingEvent: SingleLiveData get() = _modifyOfferingEvent + private val _commentDetailEvent: MutableSingleLiveData = MutableSingleLiveData() + val commentDetailEvent: SingleLiveData get() = _commentDetailEvent - private val _deleteOfferingEvent: MutableSingleLiveData = MutableSingleLiveData() - val deleteOfferingEvent: SingleLiveData get() = _deleteOfferingEvent + private val _updatedOfferingId: MutableLiveData = MutableLiveData() + val updatedOfferingId: LiveData get() = _updatedOfferingId - private val _deleteOfferingSuccessEvent: MutableSingleLiveData = MutableSingleLiveData() - val deleteOfferingSuccessEvent: SingleLiveData get() = _deleteOfferingSuccessEvent + private val _reportEvent: MutableSingleLiveData = MutableSingleLiveData() + val reportEvent: SingleLiveData get() = _reportEvent - private val _refreshTokenExpiredEvent: MutableSingleLiveData = MutableSingleLiveData() - val refreshTokenExpiredEvent: SingleLiveData get() = _refreshTokenExpiredEvent + private val _productLinkRedirectEvent: MutableSingleLiveData = MutableSingleLiveData() + val productLinkRedirectEvent: SingleLiveData get() = _productLinkRedirectEvent - private val _showAlertEvent = MutableSingleLiveData() - val showAlertEvent: SingleLiveData get() = _showAlertEvent + private val _error: MutableSingleLiveData = MutableSingleLiveData() + val error: SingleLiveData get() = _error - private val _alertCancelEvent = MutableSingleLiveData() - val alertCancelEvent: SingleLiveData get() = _alertCancelEvent - - init { - loadOffering() - } - - fun loadOffering() { - viewModelScope.launch { - when (val result = offeringDetailRepository.fetchOfferingDetail(offeringId)) { - is Result.Error -> - when (result.error) { - DataError.Network.UNAUTHORIZED -> { - when (authRepository.saveRefresh()) { - is Result.Success -> loadOffering() - is Result.Error -> { - userPreferencesDataStore.removeAllData() - _refreshTokenExpiredEvent.setValue(Unit) - return@launch - } - } - } - - DataError.Network.BAD_REQUEST -> { - _error.setValue(R.string.offering_detail_load_error_mesage) - } + init { + loadOffering() + } - else -> { - Log.e("error", "loadOffering Error: ${result.error.name}") - } + private fun loadOffering() { + viewModelScope.launch { + when (val result = offeringDetailRepository.fetchOfferingDetail(offeringId)) { + is Result.Error -> + when (result.error) { + DataError.Network.UNAUTHORIZED -> { + authRepository.saveRefresh() + loadOffering() } - is Result.Success -> { - _offeringDetail.value = result.data - _currentCount.value = result.data.currentCount.value - _offeringCondition.value = result.data.condition - _isParticipated.value = result.data.isParticipated - _isParticipationAvailable.value = - isParticipationEnabled(result.data.condition, result.data.isParticipated) - _isRepresentative.value = result.data.isProposer - } - } - } - } - - override fun participate() { - viewModelScope.launch { - when (val result = offeringDetailRepository.saveParticipation(offeringId)) { - is Result.Error -> - when (result.error) { - DataError.Network.UNAUTHORIZED -> { - when (authRepository.saveRefresh()) { - is Result.Success -> participate() - is Result.Error -> { - userPreferencesDataStore.removeAllData() - _refreshTokenExpiredEvent.setValue(Unit) - return@launch - } - } - } - - DataError.Network.BAD_REQUEST -> { - _error.setValue(R.string.offering_detail_participation_error) - } - - else -> { - Log.e("error", "onClickParticipation Error: ${result.error.name}") - } + DataError.Network.BAD_REQUEST -> { + _error.setValue(R.string.offering_detail_load_error_mesage) } - is Result.Success -> { - _isParticipated.value = true - _commentDetailEvent.setValue(offeringDetail.value?.title ?: DEFAULT_TITLE) - _updatedOfferingId.value = offeringId + else -> { + Log.e("error", "loadOffering Error: ${result.error.name}") + } } - } - } - } - - override fun onClickMoveCommentDetail() { - _commentDetailEvent.setValue(offeringDetail.value?.title ?: DEFAULT_TITLE) - } - override fun onClickReport() { - _reportEvent.setValue(R.string.report_url) - } - - override fun onClickProductRedirectText(productUrl: String) { - _productLinkRedirectEvent.setValue(productUrl) - } - - override fun onClickOfferingModify() { - if (_offeringCondition.value == OfferingCondition.CONFIRMED) { - _error.setValue(R.string.error_modify_invalid) - return + is Result.Success -> { + _offeringDetail.value = result.data + _currentCount.value = result.data.currentCount.value + _offeringCondition.value = result.data.condition + _isParticipated.value = result.data.isParticipated + _isParticipationAvailable.value = + isParticipationEnabled(result.data.condition, result.data.isParticipated) + _isRepresentative.value = result.data.isProposer + } } - _modifyOfferingEvent.setValue(offeringId) - } - - fun onClickDeleteButton() { - _deleteOfferingEvent.setValue(Unit) } + } - fun deleteOffering(offeringId: Long) { - viewModelScope.launch { - when (val result = offeringDetailRepository.deleteOffering(offeringId)) { - is Result.Error -> - when (result.error) { - DataError.Network.UNAUTHORIZED -> { - when (authRepository.saveRefresh()) { - is Result.Success -> deleteOffering(offeringId) - is Result.Error -> return@launch - } - } - - DataError.Network.NULL -> { - _deleteOfferingSuccessEvent.setValue(Unit) - } - - DataError.Network.BAD_REQUEST -> { - _error.setValue(R.string.offering_detail_delete_error) - } + override fun onClickParticipation() { + viewModelScope.launch { + when (val result = offeringDetailRepository.saveParticipation(offeringId)) { + is Result.Error -> + when (result.error) { + DataError.Network.UNAUTHORIZED -> { + authRepository.saveRefresh() + onClickParticipation() + } - else -> { - Log.e("error", "onClickOfferingDelete Error: ${result.error.name}") - } + DataError.Network.BAD_REQUEST -> { + _error.setValue(R.string.offering_detail_participation_error) } - is Result.Success -> { - _deleteOfferingSuccessEvent.setValue(Unit) + else -> { + Log.e("error", "onClickParticipation Error: ${result.error.name}") + } } - } - } - } - - private fun isParticipationEnabled( - offeringCondition: OfferingCondition, - isParticipated: Boolean, - ) = !isParticipated && offeringCondition.isAvailable() - companion object { - private const val DEFAULT_TITLE = "" - - @Suppress("UNCHECKED_CAST") - fun getFactory( - assistedFactory: OfferingDetailAssistedFactory, - offeringId: Long, - ) = object : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - return assistedFactory.create(offeringId) as T + is Result.Success -> { + _isParticipated.value = true + _commentDetailEvent.setValue(offeringDetail.value?.title ?: DEFAULT_TITLE) + _updatedOfferingId.value = offeringId } } } + } - fun onParticipateClick() { - _showAlertEvent.setValue(Unit) - } + override fun onClickMoveCommentDetail() { + _commentDetailEvent.setValue(offeringDetail.value?.title ?: DEFAULT_TITLE) + } - override fun onClickConfirm() { - participate() - } + override fun onClickReport() { + _reportEvent.setValue(R.string.report_url) + } - override fun onClickCancel() { - _alertCancelEvent.setValue(Unit) + override fun onClickProductRedirectText(productUrl: String) { + _productLinkRedirectEvent.setValue(productUrl) + } + + private fun isParticipationEnabled( + offeringCondition: OfferingCondition, + isParticipated: Boolean, + ) = !isParticipated && offeringCondition.isAvailable() + + companion object { + private const val DEFAULT_TITLE = "" + + @Suppress("UNCHECKED_CAST") + fun getFactory( + offeringId: Long, + offeringDetailRepository: OfferingDetailRepository, + authRepository: AuthRepository, + ) = object : ViewModelProvider.Factory { + override fun create( + modelClass: Class, + extras: CreationExtras, + ): T { + return OfferingDetailViewModel( + offeringId, + offeringDetailRepository, + authRepository, + ) as T + } } } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/offeringdetail/OnParticipationClickListener.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/offeringdetail/OnParticipationClickListener.kt index 26302cd49..9858c47fa 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/offeringdetail/OnParticipationClickListener.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/offeringdetail/OnParticipationClickListener.kt @@ -1,5 +1,5 @@ package com.zzang.chongdae.presentation.view.offeringdetail interface OnParticipationClickListener { - fun participate() + fun onClickParticipation() } diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteEssentialFragment.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteEssentialFragment.kt index 803fa9603..cf5f17c68 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteEssentialFragment.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteEssentialFragment.kt @@ -13,18 +13,16 @@ import androidx.fragment.app.activityViewModels import androidx.fragment.app.setFragmentResultListener import androidx.navigation.fragment.findNavController import com.google.firebase.analytics.FirebaseAnalytics +import com.zzang.chongdae.ChongdaeApp import com.zzang.chongdae.R -import com.zzang.chongdae.common.firebase.FirebaseAnalyticsManager import com.zzang.chongdae.databinding.DialogDatePickerBinding import com.zzang.chongdae.databinding.FragmentOfferingWriteEssentialBinding -import com.zzang.chongdae.presentation.util.setDebouncedOnClickListener +import com.zzang.chongdae.presentation.util.FirebaseAnalyticsManager import com.zzang.chongdae.presentation.view.MainActivity import com.zzang.chongdae.presentation.view.address.AddressFinderDialog -import dagger.hilt.android.AndroidEntryPoint import java.util.Calendar -@AndroidEntryPoint -class OfferingWriteEssentialFragment : Fragment(), OnDateTimeButtonsClickListener { +class OfferingWriteEssentialFragment : Fragment(), OnOfferingWriteClickListener { private var _fragmentBinding: FragmentOfferingWriteEssentialBinding? = null private val fragmentBinding get() = _fragmentBinding!! @@ -34,7 +32,12 @@ class OfferingWriteEssentialFragment : Fragment(), OnDateTimeButtonsClickListene private var toast: Toast? = null private val dialog: Dialog by lazy { Dialog(requireActivity()) } - private val viewModel: OfferingWriteViewModel by activityViewModels() + private val viewModel: OfferingWriteViewModel by activityViewModels { + OfferingWriteViewModel.getFactory( + offeringRepository = (requireActivity().application as ChongdaeApp).offeringRepository, + authRepository = (requireActivity().application as ChongdaeApp).authRepository, + ) + } private val firebaseAnalytics: FirebaseAnalytics by lazy { FirebaseAnalytics.getInstance(requireContext()) @@ -87,7 +90,7 @@ class OfferingWriteEssentialFragment : Fragment(), OnDateTimeButtonsClickListene } private fun searchPlace() { - fragmentBinding.tvPlaceValue.setDebouncedOnClickListener(800L) { + fragmentBinding.tvPlaceValue.setOnClickListener { AddressFinderDialog().show(parentFragmentManager, this.tag) } setFragmentResultListener(AddressFinderDialog.ADDRESS_KEY) { _, bundle -> @@ -117,7 +120,6 @@ class OfferingWriteEssentialFragment : Fragment(), OnDateTimeButtonsClickListene val month = calendar.get(Calendar.MONTH) val day = calendar.get(Calendar.DAY_OF_MONTH) updateDateTextView(dateTimeBinding.tvDate, year, month, day) - dateTimeBinding.pickerDate.minDate = System.currentTimeMillis() dateTimeBinding.pickerDate.setOnDateChangedListener { _, year, monthOfYear, dayOfMonth -> updateDateTextView(dateTimeBinding.tvDate, year, monthOfYear, dayOfMonth) } @@ -167,6 +169,7 @@ class OfferingWriteEssentialFragment : Fragment(), OnDateTimeButtonsClickListene fragmentBinding.lifecycleOwner = viewLifecycleOwner _dateTimePickerBinding = DialogDatePickerBinding.inflate(inflater, container, false) + dateTimePickerBinding.vm = viewModel dateTimePickerBinding.onClickListener = this } @@ -205,6 +208,5 @@ class OfferingWriteEssentialFragment : Fragment(), OnDateTimeButtonsClickListene override fun onDestroy() { super.onDestroy() _fragmentBinding = null - viewModel.initOfferingWriteInputs() } } diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteOptionalFragment.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteOptionalFragment.kt index 241bcac57..a9b3a67dc 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteOptionalFragment.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteOptionalFragment.kt @@ -16,14 +16,13 @@ import androidx.fragment.app.activityViewModels import androidx.fragment.app.setFragmentResult import androidx.navigation.fragment.findNavController import com.google.firebase.analytics.FirebaseAnalytics +import com.zzang.chongdae.ChongdaeApp import com.zzang.chongdae.R -import com.zzang.chongdae.common.firebase.FirebaseAnalyticsManager import com.zzang.chongdae.databinding.FragmentOfferingWriteOptionalBinding import com.zzang.chongdae.presentation.util.FileUtils +import com.zzang.chongdae.presentation.util.FirebaseAnalyticsManager import com.zzang.chongdae.presentation.util.PermissionManager -import dagger.hilt.android.AndroidEntryPoint -@AndroidEntryPoint class OfferingWriteOptionalFragment : Fragment() { private var _fragmentBinding: FragmentOfferingWriteOptionalBinding? = null private val fragmentBinding get() = _fragmentBinding!! @@ -33,7 +32,12 @@ class OfferingWriteOptionalFragment : Fragment() { private lateinit var permissionManager: PermissionManager private lateinit var pickMediaLauncher: ActivityResultLauncher - private val viewModel: OfferingWriteViewModel by activityViewModels() + private val viewModel: OfferingWriteViewModel by activityViewModels { + OfferingWriteViewModel.getFactory( + offeringRepository = (requireActivity().application as ChongdaeApp).offeringRepository, + authRepository = (requireActivity().application as ChongdaeApp).authRepository, + ) + } private val firebaseAnalytics: FirebaseAnalytics by lazy { FirebaseAnalytics.getInstance(requireContext()) @@ -83,7 +87,9 @@ class OfferingWriteOptionalFragment : Fragment() { contentType = "button", ) showToast(R.string.write_success_writing) - findNavController().popBackStack(R.id.offering_write_essential_fragment, true) + findNavController().popBackStack(R.id.offering_write_fragment_essential, true) + viewModel.initOfferingWriteInputs() + setFragmentResult( OFFERING_WRITE_BUNDLE_KEY, bundleOf(NEW_OFFERING_EVENT_KEY to true), diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteUiModel.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteUiModel.kt new file mode 100644 index 000000000..808096314 --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteUiModel.kt @@ -0,0 +1,15 @@ +package com.zzang.chongdae.presentation.view.write + +data class OfferingWriteUiModel( + val title: String, + val productUrl: String?, + val thumbnailUrl: String?, + val totalCount: Int, + val totalPrice: Int, + val originPrice: Int?, + val meetingAddress: String, + val meetingAddressDong: String?, + val meetingAddressDetail: String, + val meetingDate: String, + val description: String, +) diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteViewModel.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteViewModel.kt index c8b337b7b..304f2f8d9 100644 --- a/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteViewModel.kt +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OfferingWriteViewModel.kt @@ -5,418 +5,402 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.map import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras import com.zzang.chongdae.R -import com.zzang.chongdae.auth.repository.AuthRepository -import com.zzang.chongdae.common.handler.DataError -import com.zzang.chongdae.common.handler.Result -import com.zzang.chongdae.di.annotations.AuthRepositoryQualifier -import com.zzang.chongdae.di.annotations.OfferingRepositoryQualifier import com.zzang.chongdae.domain.model.Count import com.zzang.chongdae.domain.model.DiscountPrice -import com.zzang.chongdae.domain.model.OfferingWrite import com.zzang.chongdae.domain.model.Price +import com.zzang.chongdae.domain.repository.AuthRepository import com.zzang.chongdae.domain.repository.OfferingRepository +import com.zzang.chongdae.domain.util.DataError +import com.zzang.chongdae.domain.util.Result import com.zzang.chongdae.presentation.util.MutableSingleLiveData import com.zzang.chongdae.presentation.util.SingleLiveData -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import okhttp3.MultipartBody import java.text.SimpleDateFormat import java.util.Locale -import javax.inject.Inject -@HiltViewModel -class OfferingWriteViewModel - @Inject - constructor( - @OfferingRepositoryQualifier private val offeringRepository: OfferingRepository, - @AuthRepositoryQualifier private val authRepository: AuthRepository, - ) : ViewModel() { - val title: MutableLiveData = MutableLiveData("") +class OfferingWriteViewModel( + private val offeringRepository: OfferingRepository, + private val authRepository: AuthRepository, +) : ViewModel() { + val title: MutableLiveData = MutableLiveData("") - val productUrl: MutableLiveData = MutableLiveData(null) + val productUrl: MutableLiveData = MutableLiveData(null) - val thumbnailUrl: MutableLiveData = MutableLiveData("") + val thumbnailUrl: MutableLiveData = MutableLiveData("") - val deleteImageVisible: LiveData = thumbnailUrl.map { !it.isNullOrBlank() } + val deleteImageVisible: LiveData = thumbnailUrl.map { !it.isNullOrBlank() } - val totalCount: MutableLiveData = MutableLiveData("$MINIMUM_TOTAL_COUNT") + val totalCount: MutableLiveData = MutableLiveData("$MINIMUM_TOTAL_COUNT") - val totalPrice: MutableLiveData = MutableLiveData("") + val totalPrice: MutableLiveData = MutableLiveData("") - val originPrice: MutableLiveData = MutableLiveData("") + val originPrice: MutableLiveData = MutableLiveData("") - val meetingAddress: MutableLiveData = MutableLiveData("") + val meetingAddress: MutableLiveData = MutableLiveData("") - val meetingAddressDetail: MutableLiveData = MutableLiveData("") + val meetingAddressDetail: MutableLiveData = MutableLiveData("") - val meetingDate: MutableLiveData = MutableLiveData("") + val meetingDate: MutableLiveData = MutableLiveData("") - private val meetingDateValue: MutableLiveData = MutableLiveData("") + private val meetingDateValue: MutableLiveData = MutableLiveData("") - val description: MutableLiveData = MutableLiveData("") + val description: MutableLiveData = MutableLiveData("") - val descriptionLength: LiveData - get() = description.map { it.length } + val descriptionLength: LiveData + get() = description.map { it.length } - private val _essentialSubmitButtonEnabled: MediatorLiveData = MediatorLiveData(false) - val essentialSubmitButtonEnabled: LiveData get() = _essentialSubmitButtonEnabled + private val _essentialSubmitButtonEnabled: MediatorLiveData = MediatorLiveData(false) + val essentialSubmitButtonEnabled: LiveData get() = _essentialSubmitButtonEnabled - private val _extractButtonEnabled: MediatorLiveData = MediatorLiveData(false) - val extractButtonEnabled: LiveData get() = _extractButtonEnabled + private val _extractButtonEnabled: MediatorLiveData = MediatorLiveData(false) + val extractButtonEnabled: LiveData get() = _extractButtonEnabled - private val _splitPrice: MediatorLiveData = MediatorLiveData(ERROR_INTEGER_FORMAT) - val splitPrice: LiveData get() = _splitPrice + private val _splitPrice: MediatorLiveData = MediatorLiveData(ERROR_INTEGER_FORMAT) + val splitPrice: LiveData get() = _splitPrice - private val _discountRate: MediatorLiveData = MediatorLiveData(ERROR_FLOAT_FORMAT) - val splitPriceValidity: LiveData - get() = _splitPrice.map { it >= 0 } + private val _discountRate: MediatorLiveData = MediatorLiveData(ERROR_FLOAT_FORMAT) + val splitPriceValidity: LiveData + get() = _splitPrice.map { it >= 0 } - val discountRateValidity: LiveData - get() = _discountRate.map { it >= 0 } + val discountRateValidity: LiveData + get() = _discountRate.map { it >= 0 } - val discountRate: LiveData get() = _discountRate + val discountRate: LiveData get() = _discountRate - private val _meetingDateChoiceEvent: MutableSingleLiveData = MutableSingleLiveData() - val meetingDateChoiceEvent: SingleLiveData get() = _meetingDateChoiceEvent + private val _meetingDateChoiceEvent: MutableSingleLiveData = MutableSingleLiveData() + val meetingDateChoiceEvent: SingleLiveData get() = _meetingDateChoiceEvent - private val _navigateToOptionalEvent: MutableSingleLiveData = MutableSingleLiveData() - val navigateToOptionalEvent: SingleLiveData get() = _navigateToOptionalEvent + private val _navigateToOptionalEvent: MutableSingleLiveData = MutableSingleLiveData() + val navigateToOptionalEvent: SingleLiveData get() = _navigateToOptionalEvent - private val _submitOfferingEvent: MutableSingleLiveData = MutableSingleLiveData() - val submitOfferingEvent: SingleLiveData get() = _submitOfferingEvent + private val _submitOfferingEvent: MutableSingleLiveData = MutableSingleLiveData() + val submitOfferingEvent: SingleLiveData get() = _submitOfferingEvent - private val _imageUploadEvent = MutableLiveData() - val imageUploadEvent: LiveData get() = _imageUploadEvent + private val _imageUploadEvent = MutableLiveData() + val imageUploadEvent: LiveData get() = _imageUploadEvent - private val _writeUIState = MutableLiveData(WriteUIState.Initial) - val writeUIState: LiveData get() = _writeUIState + private val _writeUIState = MutableLiveData(WriteUIState.Initial) + val writeUIState: LiveData get() = _writeUIState - val isLoading: LiveData = _writeUIState.map { it is WriteUIState.Loading } + val isLoading: LiveData = _writeUIState.map { it is WriteUIState.Loading } - init { - _essentialSubmitButtonEnabled.apply { - addSource(title) { updateSubmitButtonEnabled() } - addSource(totalCount) { updateSubmitButtonEnabled() } - addSource(totalPrice) { updateSubmitButtonEnabled() } - addSource(meetingAddress) { updateSubmitButtonEnabled() } - addSource(meetingDate) { updateSubmitButtonEnabled() } - } - - _splitPrice.apply { - addSource(totalCount) { safeUpdateSplitPrice() } - addSource(totalPrice) { safeUpdateSplitPrice() } - } - - _discountRate.apply { - addSource(_splitPrice) { safeUpdateDiscountRate() } - addSource(originPrice) { safeUpdateDiscountRate() } - } + init { + _essentialSubmitButtonEnabled.apply { + addSource(title) { updateSubmitButtonEnabled() } + addSource(totalCount) { updateSubmitButtonEnabled() } + addSource(totalPrice) { updateSubmitButtonEnabled() } + addSource(meetingAddress) { updateSubmitButtonEnabled() } + addSource(meetingDate) { updateSubmitButtonEnabled() } + } - _extractButtonEnabled.apply { - addSource(productUrl) { value = !productUrl.value.isNullOrBlank() } - } + _splitPrice.apply { + addSource(totalCount) { safeUpdateSplitPrice() } + addSource(totalPrice) { safeUpdateSplitPrice() } } - private fun safeUpdateSplitPrice() { - runCatching { - updateSplitPrice() - }.onFailure { - _splitPrice.value = ERROR_INTEGER_FORMAT - } + _discountRate.apply { + addSource(_splitPrice) { safeUpdateDiscountRate() } + addSource(originPrice) { safeUpdateDiscountRate() } } - fun clearProductUrl() { - productUrl.value = null + _extractButtonEnabled.apply { + addSource(productUrl) { value = !productUrl.value.isNullOrBlank() } } + } - fun onUploadPhotoClick() { - _imageUploadEvent.value = Unit + private fun safeUpdateSplitPrice() { + runCatching { + updateSplitPrice() + }.onFailure { + _splitPrice.value = ERROR_INTEGER_FORMAT } + } - fun uploadImageFile(multipartBody: MultipartBody.Part) { - viewModelScope.launch { - _writeUIState.value = WriteUIState.Loading - when (val result = offeringRepository.saveProductImageS3(multipartBody)) { - is Result.Success -> { - _writeUIState.value = WriteUIState.Success(result.data.imageUrl) - thumbnailUrl.value = result.data.imageUrl - } + fun clearProductUrl() { + productUrl.value = null + } + + fun onUploadPhotoClick() { + _imageUploadEvent.value = Unit + } + + fun uploadImageFile(multipartBody: MultipartBody.Part) { + viewModelScope.launch { + when (val result = offeringRepository.saveProductImageS3(multipartBody)) { + is Result.Success -> { + _writeUIState.value = WriteUIState.Success(result.data.imageUrl) + thumbnailUrl.value = result.data.imageUrl + } - is Result.Error -> { - Log.e("error", "uploadImageFile: ${result.error}") - when (result.error) { - DataError.Network.UNAUTHORIZED -> { - when (authRepository.saveRefresh()) { - is Result.Success -> uploadImageFile(multipartBody) - is Result.Error -> return@launch - } - } - - else -> { - _writeUIState.value = - WriteUIState.Error( - R.string.all_error_image_upload, - "${result.error}", - ) - } + is Result.Error -> { + Log.e("error", "uploadImageFile: ${result.error}") + when (result.error) { + DataError.Network.UNAUTHORIZED -> { + authRepository.saveRefresh() + uploadImageFile(multipartBody) } + else -> {} } } } } + } - fun postProductImageOg() { - viewModelScope.launch { - _writeUIState.value = WriteUIState.Loading - when (val result = offeringRepository.saveProductImageOg(productUrl.value ?: "")) { - is Result.Success -> { - if (result.data.imageUrl.isBlank()) { - _writeUIState.value = WriteUIState.Empty(R.string.error_empty_product_url) - return@launch - } - _writeUIState.value = WriteUIState.Success(result.data.imageUrl) - thumbnailUrl.value = HTTPS + result.data.imageUrl + fun postProductImageOg() { + viewModelScope.launch { + when (val result = offeringRepository.saveProductImageOg(productUrl.value ?: "")) { + is Result.Success -> { + if (result.data.imageUrl.isBlank()) { + _writeUIState.value = WriteUIState.Empty(R.string.error_empty_product_url) + return@launch } + thumbnailUrl.value = HTTPS + result.data.imageUrl + } + + is Result.Error -> { + Log.e("error", "postProductImageOg: ${result.error}") + when (result.error) { + DataError.Network.UNAUTHORIZED -> { + authRepository.saveRefresh() + postProductImageOg() + } - is Result.Error -> { - Log.e("error", "postProductImageOg: ${result.error}") - when (result.error) { - DataError.Network.UNAUTHORIZED -> { - when (authRepository.saveRefresh()) { - is Result.Success -> postProductImageOg() - is Result.Error -> return@launch - } - } - - else -> { - _writeUIState.value = - WriteUIState.Error( - R.string.error_invalid_product_url, - "${result.error}", - ) - } + else -> { + _writeUIState.value = + WriteUIState.Error(R.string.error_invalid_product_url, "${result.error}") } } } } } + } - fun clearProductImage() { - thumbnailUrl.value = null - } + fun clearProductImage() { + thumbnailUrl.value = null + } - private fun safeUpdateDiscountRate() { - runCatching { - updateDiscountRate() - }.onFailure { - _discountRate.value = ERROR_FLOAT_FORMAT - } + private fun safeUpdateDiscountRate() { + runCatching { + updateDiscountRate() + }.onFailure { + _discountRate.value = ERROR_FLOAT_FORMAT } + } - private fun updateSubmitButtonEnabled() { - _essentialSubmitButtonEnabled.value = !title.value.isNullOrBlank() && - !totalCount.value.isNullOrBlank() && - !totalPrice.value.isNullOrBlank() && - !meetingAddress.value.isNullOrBlank() && - !meetingDate.value.isNullOrBlank() - } + private fun updateSubmitButtonEnabled() { + _essentialSubmitButtonEnabled.value = !title.value.isNullOrBlank() && + !totalCount.value.isNullOrBlank() && + !totalPrice.value.isNullOrBlank() && + !meetingAddress.value.isNullOrBlank() && + !meetingDate.value.isNullOrBlank() + } - private fun updateSplitPrice() { - val totalPrice = Price.fromString(totalPrice.value) - val totalCount = Count.fromString(totalCount.value) - _splitPrice.value = totalPrice.amount / totalCount.number - } + private fun updateSplitPrice() { + val totalPrice = Price.fromString(totalPrice.value) + val totalCount = Count.fromString(totalCount.value) + _splitPrice.value = totalPrice.amount / totalCount.number + } - private fun updateDiscountRate() { - val originPrice = Price.fromString(originPrice.value) - val splitPrice = Price.fromInteger(_splitPrice.value) - val discountPriceValue = originPrice.amount - splitPrice.amount - val discountPrice = DiscountPrice.fromFloat(discountPriceValue.toFloat()) - _discountRate.value = (discountPrice.amount / originPrice.amount) * 100 - } + private fun updateDiscountRate() { + val originPrice = Price.fromString(originPrice.value) + val splitPrice = Price.fromInteger(_splitPrice.value) + val discountPriceValue = originPrice.amount - splitPrice.amount + val discountPrice = DiscountPrice.fromFloat(discountPriceValue.toFloat()) + _discountRate.value = (discountPrice.amount / originPrice.amount) * 100 + } - fun increaseTotalCount() { - val totalCount = Count.fromString(totalCount.value).increase() - this.totalCount.value = totalCount.number.toString() - } + fun increaseTotalCount() { + val totalCount = Count.fromString(totalCount.value).increase() + this.totalCount.value = totalCount.number.toString() + } - fun decreaseTotalCount() { - if (Count.fromString(totalCount.value).number < 0) { - this.totalCount.value = MINIMUM_TOTAL_COUNT.toString() - return - } - val totalCount = Count.fromString(totalCount.value).decrease() - this.totalCount.value = totalCount.number.toString() + fun decreaseTotalCount() { + if (Count.fromString(totalCount.value).number < 0) { + this.totalCount.value = MINIMUM_TOTAL_COUNT.toString() + return } + val totalCount = Count.fromString(totalCount.value).decrease() + this.totalCount.value = totalCount.number.toString() + } - fun makeMeetingDateChoiceEvent() { - _meetingDateChoiceEvent.setValue(true) - } + fun makeMeetingDateChoiceEvent() { + _meetingDateChoiceEvent.setValue(true) + } - fun updateMeetingDate(date: String) { - val dateTime = "$date" - val inputFormat = SimpleDateFormat(INPUT_DATE_FORMAT, Locale.KOREAN) - val outputFormat = SimpleDateFormat(OUTPUT_DATE_TIME_FORMAT, Locale.getDefault()) + fun updateMeetingDate(date: String) { + val dateTime = "$date" + val inputFormat = SimpleDateFormat(INPUT_DATE_FORMAT, Locale.KOREAN) + val outputFormat = SimpleDateFormat(OUTPUT_DATE_TIME_FORMAT, Locale.getDefault()) - val parsedDateTime = inputFormat.parse(dateTime) - meetingDateValue.value = parsedDateTime?.let { outputFormat.format(it) } - meetingDate.value = dateTime - } + val parsedDateTime = inputFormat.parse(dateTime) + meetingDateValue.value = parsedDateTime?.let { outputFormat.format(it) } + meetingDate.value = dateTime + } - fun postOffering() { - val title = title.value ?: return - val totalCount = totalCount.value ?: return - val totalPrice = totalPrice.value ?: return - val meetingAddress = meetingAddress.value ?: return - val meetingAddressDetail = meetingAddressDetail.value ?: return - val meetingDate = meetingDateValue.value ?: return - val description = description.value ?: return - - val totalCountConverted = makeTotalCountInvalidEvent(totalCount) ?: return - val totalPriceConverted = makeTotalPriceInvalidEvent(totalPrice) ?: return - val meetingAddressDong = extractDong(meetingAddress) - - var originPriceNotBlank: Int? = 0 - runCatching { - originPriceNotBlank = originPriceToPositiveIntOrNull(originPrice.value) - }.onFailure { - makeOriginPriceInvalidEvent() - return - } - if (isOriginPriceCheaperThanSplitPriceEvent()) return - - viewModelScope.launch { - when ( - val result = - offeringRepository.saveOffering( - offeringWrite = - OfferingWrite( - title = title, - productUrl = productUrlOrNull(), - thumbnailUrl = thumbnailUrl.value, - totalCount = totalCountConverted, - totalPrice = totalPriceConverted, - originPrice = originPriceNotBlank, - meetingAddress = meetingAddress, - meetingAddressDong = meetingAddressDong, - meetingAddressDetail = meetingAddressDetail, - meetingDate = meetingDate, - description = description, - ), - ) - ) { - is Result.Success -> makeSubmitOfferingEvent() - - is Result.Error -> { - Log.e("error", "postOffering: ${result.error}") - when (result.error) { - DataError.Network.UNAUTHORIZED -> { - when (authRepository.saveRefresh()) { - is Result.Success -> postOffering() - is Result.Error -> return@launch - } - } - - else -> { - _writeUIState.value = - WriteUIState.Error(R.string.write_error_writing, "${result.error}") - } + fun postOffering() { + val title = title.value ?: return + val totalCount = totalCount.value ?: return + val totalPrice = totalPrice.value ?: return + val meetingAddress = meetingAddress.value ?: return + val meetingAddressDetail = meetingAddressDetail.value ?: return + val meetingDate = meetingDateValue.value ?: return + val description = description.value ?: return + + val totalCountConverted = makeTotalCountInvalidEvent(totalCount) ?: return + val totalPriceConverted = makeTotalPriceInvalidEvent(totalPrice) ?: return + val meetingAddressDong = extractDong(meetingAddress) + + var originPriceNotBlank: Int? = 0 + runCatching { + originPriceNotBlank = originPriceToPositiveIntOrNull(originPrice.value) + }.onFailure { + makeOriginPriceInvalidEvent() + return + } + if (isOriginPriceCheaperThanSplitPriceEvent()) return + + viewModelScope.launch { + when ( + val result = + offeringRepository.saveOffering( + uiModel = + OfferingWriteUiModel( + title = title, + productUrl = productUrl.value, + thumbnailUrl = thumbnailUrl.value, + totalCount = totalCountConverted, + totalPrice = totalPriceConverted, + originPrice = originPriceNotBlank, + meetingAddress = meetingAddress, + meetingAddressDong = meetingAddressDong, + meetingAddressDetail = meetingAddressDetail, + meetingDate = meetingDate, + description = description, + ), + ) + ) { + is Result.Success -> makeSubmitOfferingEvent() + + is Result.Error -> { + Log.e("error", "postOffering: ${result.error}") + when (result.error) { + DataError.Network.UNAUTHORIZED -> { + authRepository.saveRefresh() + postOffering() + } + else -> { + _writeUIState.value = + WriteUIState.Error(R.string.write_error_writing, "${result.error}") } } } } } + } - private fun productUrlOrNull(): String? { - val productUrl = productUrl.value - if (productUrl == "") return null - return productUrl + private fun originPriceToPositiveIntOrNull(input: String?): Int? { + val originPriceInputTrim = input?.trim() + if (originPriceInputTrim.isNullOrBlank()) { + return null } - - private fun originPriceToPositiveIntOrNull(input: String?): Int? { - val originPriceInputTrim = input?.trim() - if (originPriceInputTrim.isNullOrBlank()) { - return null - } - if (originPriceInputTrim.toInt() < 0) { - throw NumberFormatException() - } - return originPriceInputTrim.toInt() + if (originPriceInputTrim.toInt() < 0) { + throw NumberFormatException() } + return originPriceInputTrim.toInt() + } - private fun extractDong(address: String): String? { - val regex = """\((.*?)\)""".toRegex() - val matchResult = regex.find(address) - val content = matchResult?.groups?.get(1)?.value - return content?.split(",")?.get(0)?.trim() - } + private fun extractDong(address: String): String? { + val regex = """\((.*?)\)""".toRegex() + val matchResult = regex.find(address) + val content = matchResult?.groups?.get(1)?.value + return content?.split(",")?.get(0)?.trim() + } - private fun makeTotalCountInvalidEvent(totalCount: String): Int? { - val totalCountValue = totalCount.trim().toIntOrNull() ?: ERROR_INTEGER_FORMAT - if (totalCountValue < MINIMUM_TOTAL_COUNT || totalCountValue > MAXIMUM_TOTAL_COUNT) { - _writeUIState.value = WriteUIState.InvalidInput(R.string.write_invalid_total_count) - return null - } - return totalCountValue + private fun makeTotalCountInvalidEvent(totalCount: String): Int? { + val totalCountValue = totalCount.trim().toIntOrNull() ?: ERROR_INTEGER_FORMAT + if (totalCountValue < MINIMUM_TOTAL_COUNT || totalCountValue > MAXIMUM_TOTAL_COUNT) { + _writeUIState.value = WriteUIState.InvalidInput(R.string.write_invalid_total_count) + return null } + return totalCountValue + } - private fun makeTotalPriceInvalidEvent(totalPrice: String): Int? { - val totalPriceConverted = totalPrice.trim().toIntOrNull() ?: ERROR_INTEGER_FORMAT - if (totalPriceConverted < 0) { - _writeUIState.value = WriteUIState.InvalidInput(R.string.write_invalid_total_price) - return null - } - return totalPriceConverted + private fun makeTotalPriceInvalidEvent(totalPrice: String): Int? { + val totalPriceConverted = totalPrice.trim().toIntOrNull() ?: ERROR_INTEGER_FORMAT + if (totalPriceConverted < 0) { + _writeUIState.value = WriteUIState.InvalidInput(R.string.write_invalid_total_price) + return null } + return totalPriceConverted + } - private fun makeOriginPriceInvalidEvent() { - _writeUIState.value = WriteUIState.InvalidInput(R.string.write_invalid_origin_price) - } + private fun makeOriginPriceInvalidEvent() { + _writeUIState.value = WriteUIState.InvalidInput(R.string.write_invalid_origin_price) + } - private fun isOriginPriceCheaperThanSplitPriceEvent(): Boolean { - if (originPrice.value.isNullOrBlank()) return false - val discountRateValue = discountRate.value ?: ERROR_FLOAT_FORMAT - if (discountRateValue <= 0f) { - _writeUIState.value = - WriteUIState.InvalidInput(R.string.write_origin_price_cheaper_than_total_price) - return true - } - return false + private fun isOriginPriceCheaperThanSplitPriceEvent(): Boolean { + if (originPrice.value.isNullOrBlank()) return false + val discountRateValue = discountRate.value ?: ERROR_FLOAT_FORMAT + if (discountRateValue <= 0f) { + _writeUIState.value = + WriteUIState.InvalidInput(R.string.write_origin_price_cheaper_than_total_price) + return true } + return false + } - fun makeNavigateToOptionalEvent() { - _navigateToOptionalEvent.setValue(true) - } + fun makeNavigateToOptionalEvent() { + _navigateToOptionalEvent.setValue(true) + } - private fun makeSubmitOfferingEvent() { - _submitOfferingEvent.setValue(Unit) - } + private fun makeSubmitOfferingEvent() { + _submitOfferingEvent.setValue(Unit) + } - fun initOfferingWriteInputs() { - title.value = "" - productUrl.value = "" - thumbnailUrl.value = "" - totalCount.value = "$MINIMUM_TOTAL_COUNT" - totalPrice.value = "" - originPrice.value = "" - meetingAddress.value = "" - meetingAddressDetail.value = "" - meetingDate.value = "" - meetingDateValue.value = "" - description.value = "" - } + fun initOfferingWriteInputs() { + title.value = "" + productUrl.value = "" + thumbnailUrl.value = "" + totalCount.value = "$MINIMUM_TOTAL_COUNT" + totalPrice.value = "" + originPrice.value = "" + meetingAddress.value = "" + meetingAddressDetail.value = "" + meetingDate.value = "" + meetingDateValue.value = "" + description.value = "" + } - companion object { - private const val ERROR_INTEGER_FORMAT = -1 - private const val ERROR_FLOAT_FORMAT = -1f - private const val MINIMUM_TOTAL_COUNT = 2 - private const val MAXIMUM_TOTAL_COUNT = 10_000 - private const val INPUT_DATE_TIME_FORMAT = "yyyy년 M월 d일 a h시 m분" - private const val INPUT_DATE_FORMAT = "yyyy년 M월 d일" - private const val OUTPUT_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss" - const val HTTPS = "https:" + companion object { + private const val ERROR_INTEGER_FORMAT = -1 + private const val ERROR_FLOAT_FORMAT = -1f + private const val MINIMUM_TOTAL_COUNT = 2 + private const val MAXIMUM_TOTAL_COUNT = 10_000 + private const val INPUT_DATE_TIME_FORMAT = "yyyy년 M월 d일 a h시 m분" + private const val INPUT_DATE_FORMAT = "yyyy년 M월 d일" + private const val OUTPUT_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss" + const val HTTPS = "https:" + + @Suppress("UNCHECKED_CAST") + fun getFactory( + offeringRepository: OfferingRepository, + authRepository: AuthRepository, + ) = object : ViewModelProvider.Factory { + override fun create( + modelClass: Class, + extras: CreationExtras, + ): T { + return OfferingWriteViewModel( + offeringRepository, + authRepository, + ) as T + } } } +} diff --git a/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OnOfferingWriteClickListener.kt b/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OnOfferingWriteClickListener.kt new file mode 100644 index 000000000..56fe0e55b --- /dev/null +++ b/android/app/src/main/java/com/zzang/chongdae/presentation/view/write/OnOfferingWriteClickListener.kt @@ -0,0 +1,7 @@ +package com.zzang.chongdae.presentation.view.write + +interface OnOfferingWriteClickListener { + fun onDateTimeSubmitButtonClick() + + fun onDateTimeCancelButtonClick() +} diff --git a/android/app/src/main/res/layout/activity_comment_detail.xml b/android/app/src/main/res/layout/activity_comment_detail.xml index f39ec8188..7cc1621fd 100644 --- a/android/app/src/main/res/layout/activity_comment_detail.xml +++ b/android/app/src/main/res/layout/activity_comment_detail.xml @@ -38,7 +38,7 @@ android:layout_marginStart="@dimen/margin_10" android:layout_marginTop="@dimen/margin_20" android:contentDescription="@string/comment_detail" - app:debouncedOnClick="@{() -> vm.onBackClick()}" + android:onClick="@{() -> vm.onBackClick()}" android:padding="@dimen/margin_10" android:src="@drawable/btn_left_vector" app:layout_constraintStart_toStartOf="parent" @@ -112,7 +112,7 @@ android:elevation="5dp" android:fontFamily="@font/suit_semibold" android:gravity="center" - app:debouncedOnClick="@{() -> vm.updateOfferingEvent()}" + android:onClick="@{() -> vm.updateOfferingEvent()}" android:text="@{vm.commentOfferingInfo.buttonText}" android:textColor="@color/white" android:textSize="@dimen/size_15" @@ -128,7 +128,7 @@ android:layout_width="match_parent" android:layout_height="@dimen/size_36" android:background="@color/gray_100" - app:debouncedOnClick="@{() -> vm.toggleCollapsibleView()}" + android:onClick="@{() -> vm.toggleCollapsibleView()}" android:translationZ="1dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -259,7 +259,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/margin_20" - android:layout_marginEnd="@dimen/size_23" + android:layout_marginEnd="@dimen/margin_20" android:layout_marginBottom="@dimen/margin_30" android:background="@drawable/bg_gray100_radius_16dp" android:fontFamily="@font/suit_medium" @@ -279,9 +279,9 @@ @@ -391,7 +391,7 @@ android:layout_width="@dimen/icon_size_24" android:layout_height="@dimen/icon_size_24" android:layout_marginStart="@dimen/margin_20" - app:debouncedOnClick="@{() -> vm.onExitClick()}" + android:onClick="@{() -> vm.exitOffering()}" android:src="@drawable/btn_exit" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/android/app/src/main/res/layout/activity_login.xml b/android/app/src/main/res/layout/activity_login.xml index eb23270e4..2023b3e1c 100644 --- a/android/app/src/main/res/layout/activity_login.xml +++ b/android/app/src/main/res/layout/activity_login.xml @@ -55,7 +55,7 @@ android:layout_marginEnd="@dimen/margin_30" android:layout_marginBottom="@dimen/size_120" android:background="@drawable/bg_yellow_radius_12dp" - app:debouncedOnClick="@{() -> onAuthClickListener.onLoginButtonClick()}" + android:onClick="@{() -> onAuthClickListener.onLoginButtonClick()}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> diff --git a/android/app/src/main/res/layout/dialog_date_picker.xml b/android/app/src/main/res/layout/dialog_date_picker.xml index cadfeb816..78d4e866c 100644 --- a/android/app/src/main/res/layout/dialog_date_picker.xml +++ b/android/app/src/main/res/layout/dialog_date_picker.xml @@ -2,9 +2,14 @@ + + + + type="com.zzang.chongdae.presentation.view.write.OnOfferingWriteClickListener" /> - + app:layout_constraintTop_toBottomOf="@id/tv_date" /> + + app:layout_constraintTop_toBottomOf="@id/view_divide_line" /> - + tools:layout_editor_absoluteX="10dp" />