├── .firebaserc ├── .github ├── ISSUE_TEMPLATE │ ├── ✨-기능-추가.md │ └── 🐞-버그-신고.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ucmcCI.yml ├── .gitignore ├── README.md ├── app ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── gta │ │ └── ucmc │ │ ├── LoggerInitializer.kt │ │ └── UCMCApplication.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── mipmap │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── xml │ ├── backup_rules.xml │ └── data_extraction_rules.xml ├── build.gradle.kts ├── buildSrc ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── java │ └── com │ └── gta │ └── buildsrc │ ├── Configuration.kt │ └── Dependencies.kt ├── data ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── gta │ │ └── data │ │ ├── di │ │ ├── FirebaseModule.kt │ │ ├── LicenseModule.kt │ │ ├── LoginModule.kt │ │ ├── MessageTokenModule.kt │ │ ├── MyPageModule.kt │ │ ├── NetworkModule.kt │ │ ├── NicknameModule.kt │ │ ├── NotificationModule.kt │ │ ├── PinkSlipModule.kt │ │ ├── Qualifiers.kt │ │ ├── RepositoryModule.kt │ │ ├── ReservationModule.kt │ │ └── ReviewModule.kt │ │ ├── model │ │ ├── AddressResult.kt │ │ ├── Car.kt │ │ ├── MessageResult.kt │ │ ├── NotificationMessage.kt │ │ ├── SearchResult.kt │ │ └── UserInfo.kt │ │ ├── repository │ │ ├── CarRepositoryImpl.kt │ │ ├── LicenseRepositoryImpl.kt │ │ ├── LoginRepositoryImpl.kt │ │ ├── MapRepositoryImpl.kt │ │ ├── MessageTokenRepositoryImpl.kt │ │ ├── MyPageRepositoryImpl.kt │ │ ├── NicknameRepositoryImpl.kt │ │ ├── NotificationRepositoryImpl.kt │ │ ├── PinkSlipRepositoryImpl.kt │ │ ├── ReportRepositoryImpl.kt │ │ ├── ReservationRepositoryImpl.kt │ │ ├── ReviewRepositoryImpl.kt │ │ ├── TransactionRepositoryImpl.kt │ │ └── UserRepositoryImpl.kt │ │ ├── service │ │ ├── AddressSearchService.kt │ │ └── CloudMessageService.kt │ │ └── source │ │ ├── CarDataSource.kt │ │ ├── LicenseDataSource.kt │ │ ├── LoginDataSource.kt │ │ ├── MapDataSource.kt │ │ ├── MessageTokenDataSource.kt │ │ ├── MyPageDataSource.kt │ │ ├── NicknameDataSource.kt │ │ ├── NotificationDataSource.kt │ │ ├── NotificationPagingSource.kt │ │ ├── PinkSlipDataSource.kt │ │ ├── ReservationDataSource.kt │ │ ├── ReviewDataSource.kt │ │ ├── StorageDataSource.kt │ │ ├── TransactionDataSource.kt │ │ ├── TransactionPagingSource.kt │ │ └── UserDataSource.kt │ └── test │ └── java │ └── com │ └── gta │ └── data │ ├── CarRepositoryUnitTest.kt │ ├── LicenseRepositoryUnitTest.kt │ ├── LoginRepositoryUnitTest.kt │ ├── MapFragmentUnitTest.kt │ ├── MessageTokenRepositoryUnitTest.kt │ ├── MyPageRepositoryUnitTest.kt │ ├── NicknameRepositoryUnitTest.kt │ ├── NotificationRepositoryUnitTest.kt │ ├── PinkSlipRepositoryUnitTest.kt │ ├── ReportRepositoryUnitTest.kt │ ├── ReservationRepositoryUnitTest.kt │ ├── ReviewRepositoryUnitTest.kt │ ├── TestUtil.kt │ └── UserRepositoryUnitTest.kt ├── domain ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── java │ └── com │ └── gta │ └── domain │ ├── model │ ├── AvailableDate.kt │ ├── CarDetail.kt │ ├── CarRentInfo.kt │ ├── Coordinate.kt │ ├── DrivingLicense.kt │ ├── InsuranceOption.kt │ ├── LocationInfo.kt │ ├── NicknameState.kt │ ├── Notification.kt │ ├── NotificationInfo.kt │ ├── PinkSlip.kt │ ├── Reservation.kt │ ├── ReviewDTO.kt │ ├── ReviewType.kt │ ├── SimpleCar.kt │ ├── SimpleReservation.kt │ ├── Transaction.kt │ ├── UCMCException.kt │ ├── UCMCResult.kt │ ├── UpdateCar.kt │ ├── UserProfile.kt │ └── UserReview.kt │ ├── repository │ ├── CarRepository.kt │ ├── LicenseRepository.kt │ ├── LoginRepository.kt │ ├── MapRepository.kt │ ├── MessageTokenRepository.kt │ ├── MyPageRepository.kt │ ├── NicknameRepository.kt │ ├── NotificationRepository.kt │ ├── PinkSlipRepository.kt │ ├── ReportRepository.kt │ ├── ReservationRepository.kt │ ├── ReviewRepository.kt │ ├── TransactionRepository.kt │ └── UserRepository.kt │ └── usecase │ ├── SendNotificationUseCase.kt │ ├── car │ └── GetSimpleCarUseCase.kt │ ├── cardetail │ ├── GetCarDetailDataUseCase.kt │ ├── GetNowRentCarUseCase.kt │ ├── GetOwnerCarsUseCase.kt │ ├── GetOwnerInfoUseCase.kt │ ├── GetUseStateAboutCarUseCase.kt │ ├── RemoveCarUseCase.kt │ └── edit │ │ ├── DeleteImagesUseCase.kt │ │ ├── GetCoordinateLocationUseCase.kt │ │ ├── SetCarImagesUseCase.kt │ │ ├── UpdateCarDetailDataUseCase.kt │ │ └── UploadCarImagesUseCase.kt │ ├── license │ ├── GetLicenseFromDatabaseUseCase.kt │ ├── GetLicenseFromImageUseCase.kt │ └── SetLicenseUseCase.kt │ ├── login │ ├── CheckCurrentUserUseCase.kt │ ├── SignUpUseCase.kt │ └── UpdateUserMessageTokenUseCase.kt │ ├── map │ ├── GetAllCarsUseCase.kt │ ├── GetNearCarsUseCase.kt │ └── GetSearchAddressUseCase.kt │ ├── mypage │ └── SetThumbnailUseCase.kt │ ├── nickname │ ├── CheckNicknameStateUseCase.kt │ └── UpdateNicknameUseCase.kt │ ├── notification │ ├── GetNotificationsInfoUseCase.kt │ └── SetMessageTokenUseCase.kt │ ├── pinkslip │ ├── GetPinkSlipUseCase.kt │ └── SetPinkSlipUseCase.kt │ ├── reservation │ ├── CreateReservationUseCase.kt │ ├── FinishReservationUseCase.kt │ ├── GetCarRentInfoUseCase.kt │ └── GetReservationUseCase.kt │ ├── returncar │ └── ReturnCarUseCase.kt │ ├── review │ ├── AddReviewUseCase.kt │ └── GetReviewDTOUseCase.kt │ ├── transaction │ └── GetTransactionsUseCase.kt │ └── user │ ├── GetUserProfileUseCase.kt │ └── ReportUserUseCase.kt ├── firebase.json ├── functions ├── .eslintrc.js ├── .gitignore ├── index.js ├── package-lock.json └── package.json ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── presentation ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── gta │ │ └── presentation │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── terms_of_privacy.txt │ │ └── terms_of_service.txt │ ├── java │ │ └── com │ │ │ └── gta │ │ │ └── presentation │ │ │ ├── NotificationService.kt │ │ │ ├── di │ │ │ ├── FirebaseAuthModule.kt │ │ │ ├── FirebaseSigninModule.kt │ │ │ ├── StreamModule.kt │ │ │ └── UtilModule.kt │ │ │ ├── model │ │ │ ├── DateType.kt │ │ │ ├── TransactionState.kt │ │ │ └── TransactionUserState.kt │ │ │ ├── ui │ │ │ ├── BindingAdapter.kt │ │ │ ├── GlideModule.kt │ │ │ ├── MainActivity.kt │ │ │ ├── MainViewModel.kt │ │ │ ├── base │ │ │ │ ├── BaseActivity.kt │ │ │ │ ├── BaseFragment.kt │ │ │ │ └── CameraGuideFragment.kt │ │ │ ├── cardetail │ │ │ │ ├── CarDetailFragment.kt │ │ │ │ ├── CarDetailViewModel.kt │ │ │ │ ├── CarImagePagerAdapter.kt │ │ │ │ ├── CarListAdapter.kt │ │ │ │ ├── OwnerProfileFragment.kt │ │ │ │ ├── OwnerProfileViewModel.kt │ │ │ │ └── edit │ │ │ │ │ ├── CarEditFragment.kt │ │ │ │ │ ├── CarEditImagesAdapter.kt │ │ │ │ │ ├── CarEditMapFragment.kt │ │ │ │ │ └── CarEditViewModel.kt │ │ │ ├── chatting │ │ │ │ ├── chat │ │ │ │ │ ├── ChattingFragment.kt │ │ │ │ │ └── ChattingViewModel.kt │ │ │ │ └── list │ │ │ │ │ ├── ChattingListFragment.kt │ │ │ │ │ └── ChattingListViewHolder.kt │ │ │ ├── license │ │ │ │ ├── guide │ │ │ │ │ └── LicenseGuideFragment.kt │ │ │ │ └── registration │ │ │ │ │ ├── LicenseRegistrationFragment.kt │ │ │ │ │ └── LicenseRegistrationViewModel.kt │ │ │ ├── login │ │ │ │ ├── LoginActivity.kt │ │ │ │ └── LoginViewModel.kt │ │ │ ├── map │ │ │ │ ├── AutoCompleteAdapter.kt │ │ │ │ ├── MapFragment.kt │ │ │ │ └── MapViewModel.kt │ │ │ ├── mypage │ │ │ │ ├── MyPageFragment.kt │ │ │ │ ├── MyPageTermsFragment.kt │ │ │ │ ├── MyPageViewModel.kt │ │ │ │ ├── license │ │ │ │ │ ├── MyPageLicenseFragment.kt │ │ │ │ │ └── MyPageLicenseViewModel.kt │ │ │ │ └── mycars │ │ │ │ │ ├── MyCarsFragment.kt │ │ │ │ │ ├── MyCarsListAdapter.kt │ │ │ │ │ ├── MyCarsViewModel.kt │ │ │ │ │ └── OnItemClickListener.kt │ │ │ ├── nickname │ │ │ │ ├── NicknameFragment.kt │ │ │ │ └── NicknameViewModel.kt │ │ │ ├── notification │ │ │ │ ├── NotificationListAdapter.kt │ │ │ │ ├── NotificationListFragment.kt │ │ │ │ └── NotificationListViewModel.kt │ │ │ ├── pinkslip │ │ │ │ ├── guide │ │ │ │ │ └── PinkSlipGuideFragment.kt │ │ │ │ └── registration │ │ │ │ │ ├── PinkSlipRegistrationFragment.kt │ │ │ │ │ └── PinkSlipRegistrationViewModel.kt │ │ │ ├── reservation │ │ │ │ ├── ReservationFragment.kt │ │ │ │ ├── ReservationViewModel.kt │ │ │ │ └── check │ │ │ │ │ ├── ReservationCheckFragment.kt │ │ │ │ │ └── ReservationCheckViewModel.kt │ │ │ ├── returncar │ │ │ │ ├── ReturnCarFragment.kt │ │ │ │ └── ReturnCarViewModel.kt │ │ │ ├── review │ │ │ │ ├── ReviewFragment.kt │ │ │ │ └── ReviewViewModel.kt │ │ │ └── transaction │ │ │ │ ├── TransactionFragment.kt │ │ │ │ ├── TransactionListAdapter.kt │ │ │ │ ├── TransactionListFragment.kt │ │ │ │ ├── TransactionListViewModel.kt │ │ │ │ └── TransactionPagerAdapter.kt │ │ │ └── util │ │ │ ├── DateUtil.kt │ │ │ ├── DateValidator.kt │ │ │ ├── EventFlow.kt │ │ │ ├── FirebaseUtil.kt │ │ │ ├── ImageUtil.kt │ │ │ └── RepeatOnStarted.kt │ └── res │ │ ├── drawable │ │ ├── bg_bottom_sheet.xml │ │ ├── bg_reservation_tag.xml │ │ ├── bg_textview_round.xml │ │ ├── bg_transaction_state_tag.xml │ │ ├── edittext_style.xml │ │ ├── ic_add.xml │ │ ├── ic_arrow_right_on_background.xml │ │ ├── ic_bottom_nav_car.xml │ │ ├── ic_bottom_nav_chatting.xml │ │ ├── ic_bottom_nav_my.xml │ │ ├── ic_broken_image.xml │ │ ├── ic_camera.xml │ │ ├── ic_check.xml │ │ ├── ic_close.xml │ │ ├── ic_delete.xml │ │ ├── ic_logo.xml │ │ ├── ic_mypage_car.xml │ │ ├── ic_mypage_deal.xml │ │ ├── ic_mypage_deal_inverse.xml │ │ ├── ic_mypage_edit.xml │ │ ├── ic_mypage_license.xml │ │ ├── ic_mypage_thumb.xml │ │ ├── ic_notification.xml │ │ ├── ic_right_arrow.xml │ │ ├── ic_search.xml │ │ ├── ic_splash.xml │ │ ├── img_driving_license.png │ │ ├── img_pink_slip.jpg │ │ └── selector_background.xml │ │ ├── layout │ │ ├── activity_login.xml │ │ ├── activity_main.xml │ │ ├── fragment_camera_guide.xml │ │ ├── fragment_car_detail.xml │ │ ├── fragment_car_edit.xml │ │ ├── fragment_car_edit_map.xml │ │ ├── fragment_chatting.xml │ │ ├── fragment_chatting_list.xml │ │ ├── fragment_license_registration.xml │ │ ├── fragment_map.xml │ │ ├── fragment_my_cars.xml │ │ ├── fragment_mypage.xml │ │ ├── fragment_mypage_license.xml │ │ ├── fragment_mypage_terms.xml │ │ ├── fragment_nickname.xml │ │ ├── fragment_notification.xml │ │ ├── fragment_notification_list.xml │ │ ├── fragment_owner_profile.xml │ │ ├── fragment_pink_slip_registration.xml │ │ ├── fragment_reservation.xml │ │ ├── fragment_reservation_check.xml │ │ ├── fragment_return_car.xml │ │ ├── fragment_review.xml │ │ ├── fragment_transaction.xml │ │ ├── fragment_transaction_list.xml │ │ ├── include_car_summary.xml │ │ ├── include_owner_profile.xml │ │ ├── include_wait_loading.xml │ │ ├── item_car_detail_image.xml │ │ ├── item_car_edit_image.xml │ │ ├── item_chatting_list.xml │ │ ├── item_map_search.xml │ │ ├── item_mypage_carlist.xml │ │ ├── item_notification_list.xml │ │ ├── item_owner_car.xml │ │ └── item_transaction.xml │ │ ├── menu │ │ ├── menu_bottom_nav.xml │ │ ├── menu_main_activity.xml │ │ └── menu_mycars_item_click.xml │ │ ├── navigation │ │ └── nav_main.xml │ │ ├── raw │ │ └── lottie_car.json │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ │ └── xml │ │ └── file_paths.xml │ └── test │ └── java │ └── com │ └── gta │ └── presentation │ └── ExampleUnitTest.kt └── settings.gradle.kts /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "boostcamp-ucmc" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/✨-기능-추가.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "✨ 기능 추가" 3 | about: 기능 추가 작업 사항을 입력해주세요. 4 | title: '' 5 | labels: "✨ Feature" 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Feature : [기능 이름] 11 | 12 | ## 설명 13 | - 설명을 작성해주세요. 14 | 15 | ## 해야할 일 16 | - [ ] todo 17 | 18 | ## 기타 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/🐞-버그-신고.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41E 버그 신고" 3 | about: 버그 내용을 입력해주세요. 4 | title: '' 5 | labels: "\U0001F41E Fix" 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Bug : [기능 이름] 11 | 12 | ## 설명 13 | - 버그에 대한 설명을 작성해주세요. 14 | 15 | ## 버그가 일어나는 상황 16 | 1. 버그가 일어나는 사용자 동작을 설명해주세요. 17 | 2. 버그가 일어나는 사용자 동작을 설명해주세요. 18 | 3. 버그가 일어나는 사용자 동작을 설명해주세요. 19 | 20 | ## 예상 동작 21 | - 어떻게 앱이 동작해야하는지 설명해주세요. 22 | 23 | ## 버그 화면 24 | 25 | ## 기타 26 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## 개요 2 | 내용을 적어주세요. 3 | 4 | ## 작업사항 5 | - 내용을 적어주세요. 6 | 7 | ## 변경된 부분 8 | - 내용을 적어주세요. 9 | 10 | ## 실행 화면 11 | 12 | 13 | ## 특이사항 14 | - 내용을 적어주세요. 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/tasks.xml 43 | .idea/gradle.xml 44 | .idea/assetWizardSettings.xml 45 | .idea/dictionaries 46 | .idea/libraries 47 | # Android Studio 3 in .gitignore file. 48 | .idea/caches 49 | .idea/modules.xml 50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 51 | .idea/navEditor.xml 52 | 53 | # Keystore files 54 | # Uncomment the following lines if you do not want to check your keystore files in. 55 | #*.jks 56 | #*.keystore 57 | 58 | # External native build folder generated in Android Studio 2.2 and later 59 | .externalNativeBuild 60 | .cxx/ 61 | 62 | # Google Services (e.g. APIs or Firebase) 63 | google-services.json 64 | 65 | # Freeline 66 | freeline.py 67 | freeline/ 68 | freeline_project_description.json 69 | 70 | # fastlane 71 | fastlane/report.xml 72 | fastlane/Preview.html 73 | fastlane/screenshots 74 | fastlane/test_output 75 | fastlane/readme.md 76 | 77 | # Version control 78 | vcs.xml 79 | 80 | # lint 81 | lint/intermediates/ 82 | lint/generated/ 83 | lint/outputs/ 84 | lint/tmp/ 85 | # lint/reports/ 86 | /.idea/ 87 | /app/release/output-metadata.json 88 | 89 | # Mac 90 | *.DS_Store 91 | /presentation/src/main/java/com/gta/presentation/secret/Secrets.kt 92 | /data/src/main/java/com/gta/data/secret/ 93 | 94 | /functions/node_modules -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.gta.buildsrc.Configuration 2 | 3 | plugins { 4 | id("com.android.application") 5 | kotlin("android") 6 | kotlin("kapt") 7 | id("dagger.hilt.android.plugin") 8 | id("com.google.firebase.crashlytics") 9 | } 10 | 11 | android { 12 | namespace = "com.gta.ucmc" 13 | compileSdk = Configuration.compileSdk 14 | 15 | defaultConfig { 16 | applicationId = "com.gta.ucmc" 17 | minSdk = Configuration.minSdk 18 | targetSdk = Configuration.targetSdk 19 | versionCode = Configuration.versionCode 20 | versionName = Configuration.versionName 21 | 22 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 23 | } 24 | 25 | buildTypes { 26 | release { 27 | isMinifyEnabled = false 28 | setProguardFiles( 29 | listOf( 30 | getDefaultProguardFile("proguard-android-optimize.txt"), 31 | "proguard-rules.pro" 32 | ) 33 | ) 34 | } 35 | } 36 | 37 | compileOptions { 38 | sourceCompatibility = JavaVersion.VERSION_1_8 39 | targetCompatibility = JavaVersion.VERSION_1_8 40 | } 41 | kotlinOptions { 42 | jvmTarget = JavaVersion.VERSION_1_8.toString() 43 | } 44 | buildFeatures { 45 | viewBinding = true 46 | dataBinding = true 47 | } 48 | } 49 | 50 | dependencies { 51 | implementation(project(":data")) 52 | implementation(project(":domain")) 53 | implementation(project(":presentation")) 54 | debugImplementation("com.squareup.leakcanary:leakcanary-android:2.10") 55 | 56 | implementation(Dependencies.Libraries.appLibraries) 57 | kapt(Dependencies.Libraries.appKaptLibraries) 58 | } 59 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 17 | 18 | 21 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/gta/ucmc/LoggerInitializer.kt: -------------------------------------------------------------------------------- 1 | package com.gta.ucmc 2 | 3 | import android.content.Context 4 | import androidx.startup.Initializer 5 | import timber.log.Timber 6 | 7 | class LoggerInitializer : Initializer { 8 | override fun create(context: Context) { 9 | if (BuildConfig.DEBUG) { 10 | Timber.plant(Timber.DebugTree()) 11 | } 12 | } 13 | 14 | override fun dependencies(): List>> = emptyList() 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/gta/ucmc/UCMCApplication.kt: -------------------------------------------------------------------------------- 1 | package com.gta.ucmc 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class UCMCApplication : Application() 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android01-UCMC/d326bfea3c8f199fae80f046cf6f9d2b03ca46ca/app/src/main/res/mipmap/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android01-UCMC/d326bfea3c8f199fae80f046cf6f9d2b03ca46ca/app/src/main/res/mipmap/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath(Dependencies.Classpaths.GOOGLE_SERVICES) 10 | classpath(Dependencies.Classpaths.NAVIGATION) 11 | classpath(Dependencies.Classpaths.HILT) 12 | classpath(Dependencies.Classpaths.CRASHLYTICS) 13 | classpath(Dependencies.Classpaths.JUNIT5) 14 | classpath(Dependencies.Classpaths.JACOCO) 15 | } 16 | } 17 | 18 | plugins { 19 | id("com.android.application") version Dependencies.Versions.ANDROID apply false 20 | id("com.android.library") version Dependencies.Versions.ANDROID apply false 21 | id("org.jetbrains.kotlin.android") version Dependencies.Versions.KOTLIN apply false 22 | id("org.jetbrains.kotlin.jvm") version Dependencies.Versions.KOTLIN apply false 23 | } 24 | 25 | subprojects { 26 | val ktlint by configurations.creating 27 | 28 | dependencies { 29 | ktlint("com.pinterest:ktlint:0.47.1") 30 | } 31 | 32 | tasks.register("ktlint") { 33 | group = "verification" 34 | description = "Check Kotlin code style." 35 | mainClass.set("com.pinterest.ktlint.Main") 36 | classpath = ktlint 37 | args("--android", "src/**/*.kt") 38 | } 39 | 40 | tasks.register("ktlintFormat") { 41 | group = "formatting" 42 | description = "Fix Kotlin code style deviations." 43 | mainClass.set("com.pinterest.ktlint.Main") 44 | classpath = ktlint 45 | args("--android", "-F", "src/**/*.kt") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /buildSrc/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | google() 7 | mavenCentral() 8 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/com/gta/buildsrc/Configuration.kt: -------------------------------------------------------------------------------- 1 | package com.gta.buildsrc 2 | 3 | object Configuration { 4 | const val compileSdk = 33 5 | const val minSdk = 23 6 | const val targetSdk = 33 7 | const val versionCode = 1 8 | const val versionName = "0.2.1" 9 | } 10 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | src/main/java/com/gta/data/secret/Secrets.kt -------------------------------------------------------------------------------- /data/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android01-UCMC/d326bfea3c8f199fae80f046cf6f9d2b03ca46ca/data/consumer-rules.pro -------------------------------------------------------------------------------- /data/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/di/FirebaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.di 2 | 3 | import com.google.firebase.firestore.FirebaseFirestore 4 | import com.google.firebase.firestore.ktx.firestore 5 | import com.google.firebase.ktx.Firebase 6 | import com.google.firebase.storage.StorageReference 7 | import com.google.firebase.storage.ktx.storage 8 | import dagger.Module 9 | import dagger.Provides 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | object FirebaseModule { 17 | 18 | @Singleton 19 | @Provides 20 | fun provideFireStore(): FirebaseFirestore = Firebase.firestore 21 | 22 | @Singleton 23 | @Provides 24 | fun provideStorageReference(): StorageReference = Firebase.storage.reference 25 | } 26 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/di/LicenseModule.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.di 2 | 3 | import com.google.firebase.firestore.FirebaseFirestore 4 | import com.gta.data.repository.LicenseRepositoryImpl 5 | import com.gta.data.source.LicenseDataSource 6 | import com.gta.data.source.StorageDataSource 7 | import com.gta.data.source.UserDataSource 8 | import com.gta.domain.repository.LicenseRepository 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import javax.inject.Singleton 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | object LicenseModule { 18 | @Singleton 19 | @Provides 20 | fun provideLicenseDataSource(fireStore: FirebaseFirestore): LicenseDataSource = 21 | LicenseDataSource(fireStore) 22 | 23 | @Singleton 24 | @Provides 25 | fun provideLicenseRepository( 26 | userDataSource: UserDataSource, 27 | licenseDataSource: LicenseDataSource, 28 | storageDataSource: StorageDataSource 29 | ): LicenseRepository = LicenseRepositoryImpl(userDataSource, licenseDataSource, storageDataSource) 30 | } 31 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/di/LoginModule.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.di 2 | 3 | import com.google.firebase.firestore.FirebaseFirestore 4 | import com.gta.data.repository.LoginRepositoryImpl 5 | import com.gta.data.source.LoginDataSource 6 | import com.gta.data.source.MessageTokenDataSource 7 | import com.gta.data.source.UserDataSource 8 | import com.gta.domain.repository.LoginRepository 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import javax.inject.Singleton 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | object LoginModule { 18 | 19 | @Singleton 20 | @Provides 21 | fun provideLoginDataSource(fireStore: FirebaseFirestore): LoginDataSource = 22 | LoginDataSource(fireStore) 23 | 24 | @Singleton 25 | @Provides 26 | fun provideLoginRepository( 27 | userDataSource: UserDataSource, 28 | loginDataSource: LoginDataSource, 29 | messageTokenDataSource: MessageTokenDataSource 30 | ): LoginRepository = LoginRepositoryImpl(userDataSource, loginDataSource, messageTokenDataSource) 31 | } 32 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/di/MessageTokenModule.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.di 2 | 3 | import com.gta.data.repository.MessageTokenRepositoryImpl 4 | import com.gta.data.source.MessageTokenDataSource 5 | import com.gta.domain.repository.MessageTokenRepository 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | import javax.inject.Singleton 11 | 12 | @Module 13 | @InstallIn(SingletonComponent::class) 14 | class MessageTokenModule { 15 | @Singleton 16 | @Provides 17 | fun provideMessageTokenRepository(messageTokenDataSource: MessageTokenDataSource): MessageTokenRepository = 18 | MessageTokenRepositoryImpl(messageTokenDataSource) 19 | } 20 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/di/MyPageModule.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.di 2 | 3 | import com.google.firebase.firestore.FirebaseFirestore 4 | import com.gta.data.repository.MyPageRepositoryImpl 5 | import com.gta.data.source.MyPageDataSource 6 | import com.gta.data.source.StorageDataSource 7 | import com.gta.domain.repository.MyPageRepository 8 | import dagger.Module 9 | import dagger.Provides 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | object MyPageModule { 17 | 18 | @Singleton 19 | @Provides 20 | fun provideMyPageRepository( 21 | myPageDataSource: MyPageDataSource, 22 | storageDataSource: StorageDataSource 23 | ): MyPageRepository = MyPageRepositoryImpl(myPageDataSource, storageDataSource) 24 | 25 | @Singleton 26 | @Provides 27 | fun provideMyPageDataSource( 28 | fireStore: FirebaseFirestore 29 | ): MyPageDataSource = MyPageDataSource(fireStore) 30 | } 31 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/di/NicknameModule.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.di 2 | 3 | import com.gta.data.repository.NicknameRepositoryImpl 4 | import com.gta.data.source.NicknameDataSource 5 | import com.gta.domain.repository.NicknameRepository 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | import javax.inject.Singleton 11 | 12 | @Module 13 | @InstallIn(SingletonComponent::class) 14 | object NicknameModule { 15 | 16 | @Singleton 17 | @Provides 18 | fun provideNicknameRepository(dataSource: NicknameDataSource): NicknameRepository = 19 | NicknameRepositoryImpl(dataSource) 20 | } 21 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/di/NotificationModule.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.di 2 | 3 | import com.gta.data.repository.NotificationRepositoryImpl 4 | import com.gta.data.source.CarDataSource 5 | import com.gta.data.source.NotificationDataSource 6 | import com.gta.data.source.ReservationDataSource 7 | import com.gta.data.source.UserDataSource 8 | import com.gta.domain.repository.NotificationRepository 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import javax.inject.Singleton 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | object NotificationModule { 18 | @Singleton 19 | @Provides 20 | fun provideNotificationRepository( 21 | notificationDataSource: NotificationDataSource, 22 | reservationDataSource: ReservationDataSource, 23 | userDataSource: UserDataSource, 24 | carDataSource: CarDataSource 25 | ): NotificationRepository = 26 | NotificationRepositoryImpl( 27 | notificationDataSource, 28 | reservationDataSource, 29 | userDataSource, 30 | carDataSource 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/di/PinkSlipModule.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.di 2 | 3 | import com.google.firebase.firestore.FirebaseFirestore 4 | import com.gta.data.repository.PinkSlipRepositoryImpl 5 | import com.gta.data.source.CarDataSource 6 | import com.gta.data.source.PinkSlipDataSource 7 | import com.gta.data.source.UserDataSource 8 | import com.gta.domain.repository.PinkSlipRepository 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import javax.inject.Singleton 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | object PinkSlipModule { 18 | @Singleton 19 | @Provides 20 | fun providePinkSlipRepository( 21 | carDataSource: CarDataSource, 22 | userDataSource: UserDataSource, 23 | pinkSlipDataSource: PinkSlipDataSource 24 | ): PinkSlipRepository = PinkSlipRepositoryImpl(carDataSource, userDataSource, pinkSlipDataSource) 25 | 26 | @Singleton 27 | @Provides 28 | fun providePinkSlipDataSource(fireStore: FirebaseFirestore): PinkSlipDataSource = 29 | PinkSlipDataSource(fireStore) 30 | } 31 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/di/Qualifiers.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.di 2 | 3 | import javax.inject.Qualifier 4 | 5 | object Qualifiers { 6 | @Qualifier 7 | @Retention(AnnotationRetention.BINARY) 8 | annotation class SearchRetrofit 9 | 10 | @Qualifier 11 | @Retention(AnnotationRetention.BINARY) 12 | annotation class SearchOkHttpClient 13 | 14 | @Qualifier 15 | @Retention(AnnotationRetention.BINARY) 16 | annotation class SearchInterceptor 17 | 18 | @Qualifier 19 | @Retention(AnnotationRetention.BINARY) 20 | annotation class CloudMessageRetrofit 21 | 22 | @Qualifier 23 | @Retention(AnnotationRetention.BINARY) 24 | annotation class CloudMessageOkHttpClient 25 | 26 | @Qualifier 27 | @Retention(AnnotationRetention.BINARY) 28 | annotation class CloudMessageInterceptor 29 | } 30 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/di/ReservationModule.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.di 2 | 3 | import com.gta.data.repository.ReservationRepositoryImpl 4 | import com.gta.data.source.CarDataSource 5 | import com.gta.data.source.ReservationDataSource 6 | import com.gta.domain.repository.ReservationRepository 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.components.SingletonComponent 11 | import javax.inject.Singleton 12 | 13 | @Module 14 | @InstallIn(SingletonComponent::class) 15 | object ReservationModule { 16 | @Provides 17 | @Singleton 18 | fun providesReservationRepository(reservationDataSource: ReservationDataSource, carDataSource: CarDataSource): ReservationRepository { 19 | return ReservationRepositoryImpl(reservationDataSource, carDataSource) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/di/ReviewModule.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.di 2 | 3 | import com.gta.data.repository.ReviewRepositoryImpl 4 | import com.gta.data.source.CarDataSource 5 | import com.gta.data.source.ReservationDataSource 6 | import com.gta.data.source.ReviewDataSource 7 | import com.gta.data.source.UserDataSource 8 | import com.gta.domain.repository.ReviewRepository 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import javax.inject.Singleton 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | object ReviewModule { 18 | 19 | @Singleton 20 | @Provides 21 | fun provideReviewRepository( 22 | reviewDataSource: ReviewDataSource, 23 | carDataSource: CarDataSource, 24 | reservationDataSource: ReservationDataSource, 25 | userDataSource: UserDataSource 26 | ): ReviewRepository = ReviewRepositoryImpl( 27 | reviewDataSource, 28 | carDataSource, 29 | reservationDataSource, 30 | userDataSource 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/model/AddressResult.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class AddressResult( 6 | var meta: Meta2, 7 | var documents: List 8 | ) 9 | 10 | data class Meta2( 11 | @SerializedName("total_count") var totalCount: Int 12 | ) 13 | 14 | data class Document( 15 | @SerializedName("read_address") var readAddress: RoadAddress, 16 | @SerializedName("address") var addressName: Address 17 | ) 18 | 19 | data class Address( 20 | @SerializedName("address_name") var addressName: String, 21 | @SerializedName("region_1depth_name") var region1DepthName: String, 22 | @SerializedName("region_2depth_name") var region2DepthName: String, 23 | @SerializedName("region_3depth_name") var region3DepthName: String, 24 | @SerializedName("mountain_yn") var mountainYn: String, 25 | @SerializedName("main_building_no") var mainBuildingNo: String, 26 | @SerializedName("sub_building_no") var subBuildingNo: String 27 | 28 | ) 29 | 30 | data class RoadAddress( 31 | @SerializedName("address_name") var addressName: String, 32 | @SerializedName("region_1depth_name") var region1DepthName: String, 33 | @SerializedName("region_2depth_name") var region2DepthName: String, 34 | @SerializedName("region_3depth_name") var region3DepthName: String, 35 | @SerializedName("road_name") var RoadName: String, 36 | @SerializedName("underground_yn") var undergroundYn: String, 37 | @SerializedName("main_building_no") var mainBuildingNo: String, 38 | @SerializedName("sub_building_no") var subBuildingNo: String, 39 | @SerializedName("building_name") var buildingName: String?, 40 | @SerializedName("zone_no") var zoneNo: String? 41 | ) 42 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/model/MessageResult.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class MessageResult( 6 | val success: Int, 7 | val failure: Int, 8 | val results: List 9 | ) 10 | 11 | data class MessageIdList(@SerializedName("message_id") val messageId: String) 12 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/model/NotificationMessage.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.gta.domain.model.Notification 5 | 6 | data class NotificationMessage( 7 | @SerializedName("to") val to: String, 8 | @SerializedName("priority") val priority: String = "high", 9 | @SerializedName("data") val data: Notification 10 | ) 11 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/model/SearchResult.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.gta.domain.model.LocationInfo 5 | 6 | data class SearchResult( 7 | var meta: Meta, 8 | var documents: List 9 | ) 10 | 11 | data class Meta( 12 | @SerializedName("total_count") var totalCount: Int, 13 | @SerializedName("pageable_count") var pageableCount: Int, 14 | @SerializedName("is_end") var isEnd: Boolean 15 | ) 16 | 17 | data class Place( 18 | @SerializedName("address_name") var addressName: String, 19 | @SerializedName("place_name") var placeName: String?, 20 | @SerializedName("y") var latitude: String, 21 | @SerializedName("x") var longitude: String 22 | ) 23 | 24 | fun Place.toLocationInfo(): LocationInfo { 25 | return LocationInfo(this.addressName, this.placeName, this.latitude.toDouble(), this.longitude.toDouble()) 26 | } 27 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/model/UserInfo.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.model 2 | 3 | import com.gta.domain.model.DrivingLicense 4 | import com.gta.domain.model.UserProfile 5 | 6 | data class UserInfo( 7 | val nickname: String = "이동훈", 8 | val icon: String = "", 9 | val temperature: Float = 36.5f, 10 | val license: DrivingLicense? = null, 11 | val myCars: List = emptyList(), 12 | val reportCount: Int = 0, 13 | val messageToken: String = "" 14 | ) 15 | 16 | fun UserInfo.toProfile(id: String): UserProfile = UserProfile( 17 | id = id, 18 | name = nickname, 19 | temp = temperature, 20 | image = icon 21 | ) 22 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/repository/LoginRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.repository 2 | 3 | import android.content.res.Resources.NotFoundException 4 | import com.gta.data.model.toProfile 5 | import com.gta.data.source.LoginDataSource 6 | import com.gta.data.source.MessageTokenDataSource 7 | import com.gta.data.source.UserDataSource 8 | import com.gta.domain.model.FirestoreException 9 | import com.gta.domain.model.UCMCResult 10 | import com.gta.domain.model.UserProfile 11 | import com.gta.domain.repository.LoginRepository 12 | import kotlinx.coroutines.flow.first 13 | import javax.inject.Inject 14 | 15 | class LoginRepositoryImpl @Inject constructor( 16 | private val userDataSource: UserDataSource, 17 | private val loginDataSource: LoginDataSource, 18 | private val messageTokenDataSource: MessageTokenDataSource 19 | ) : LoginRepository { 20 | 21 | override suspend fun checkCurrentUser(uid: String): UCMCResult = 22 | userDataSource.getUser(uid).first()?.let { user -> 23 | UCMCResult.Success(user.toProfile(uid)) 24 | } ?: UCMCResult.Error(NotFoundException()) 25 | 26 | override suspend fun signUp(uid: String): UCMCResult = 27 | userDataSource.getUser(uid).first()?.let { user -> 28 | UCMCResult.Success(user.toProfile(uid)) 29 | } ?: createUser(uid) 30 | 31 | override suspend fun updateUserMessageToken(uid: String): Boolean { 32 | val messageToken = messageTokenDataSource.getMessageToken().first() 33 | return userDataSource.updateUserMessageToken(uid, messageToken).first() 34 | } 35 | 36 | private suspend fun createUser(uid: String): UCMCResult { 37 | val messageToken = messageTokenDataSource.getMessageToken().first() 38 | return loginDataSource.createUser(uid, messageToken).first()?.let { userInfo -> 39 | UCMCResult.Success(userInfo.toProfile(uid)) 40 | } ?: UCMCResult.Error(FirestoreException()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/repository/MapRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.repository 2 | 3 | import com.gta.data.model.toLocationInfo 4 | import com.gta.data.source.MapDataSource 5 | import com.gta.domain.model.LocationInfo 6 | import com.gta.domain.model.NotFoundDataException 7 | import com.gta.domain.model.UCMCResult 8 | import com.gta.domain.repository.MapRepository 9 | import kotlinx.coroutines.flow.Flow 10 | import kotlinx.coroutines.flow.catch 11 | import kotlinx.coroutines.flow.flow 12 | import javax.inject.Inject 13 | 14 | class MapRepositoryImpl @Inject constructor(private val mapDataSource: MapDataSource) : 15 | MapRepository { 16 | override fun getSearchAddressList(query: String): Flow>> = flow>> { 17 | val addressResult = mapDataSource.getSearchAddressList(query) 18 | val keywordResult = mapDataSource.getSearchKeywordList(query) 19 | val result = mutableListOf() 20 | 21 | addressResult.documents 22 | .map { 23 | it.toLocationInfo() 24 | }.also { 25 | result.addAll(it) 26 | } 27 | 28 | keywordResult.documents 29 | .map { 30 | it.toLocationInfo() 31 | }.also { 32 | result.addAll(it) 33 | } 34 | emit(UCMCResult.Success(result)) 35 | }.catch { e -> 36 | emit(UCMCResult.Error(Exception(e.message))) 37 | } 38 | 39 | override fun getSearchCoordinate( 40 | longitude: String, 41 | latitude: String 42 | ): Flow> = flow { 43 | val location = mapDataSource.getSearchCoordinate( 44 | longitude, 45 | latitude 46 | ).documents[0].addressName.addressName 47 | 48 | emit( 49 | if (location != "") { 50 | UCMCResult.Success(location) 51 | } else { 52 | UCMCResult.Error(NotFoundDataException()) 53 | } 54 | ) 55 | }.catch { e -> 56 | emit(UCMCResult.Error(Exception(e.message))) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/repository/MessageTokenRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.repository 2 | 3 | import com.gta.data.source.MessageTokenDataSource 4 | import com.gta.domain.repository.MessageTokenRepository 5 | import javax.inject.Inject 6 | 7 | class MessageTokenRepositoryImpl @Inject constructor(private val messageTokenDataSource: MessageTokenDataSource) : MessageTokenRepository { 8 | override suspend fun setMessageToken(token: String): Boolean { 9 | return messageTokenDataSource.setMessageToken(token) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/repository/MyPageRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.repository 2 | 3 | import com.gta.data.source.MyPageDataSource 4 | import com.gta.data.source.StorageDataSource 5 | import com.gta.domain.model.FirestoreException 6 | import com.gta.domain.model.UCMCResult 7 | import com.gta.domain.repository.MyPageRepository 8 | import kotlinx.coroutines.flow.first 9 | import javax.inject.Inject 10 | 11 | class MyPageRepositoryImpl @Inject constructor( 12 | private val myPageDataSource: MyPageDataSource, 13 | private val storageDataSource: StorageDataSource 14 | ) : MyPageRepository { 15 | override suspend fun setThumbnail(uid: String, uri: String): UCMCResult { 16 | val result = storageDataSource.uploadPicture("users/$uid/thumbnail", uri).first() ?: "" 17 | return if (result.isNotEmpty() && myPageDataSource.setThumbnail(uid, result).first()) { 18 | UCMCResult.Success(uri) 19 | } else { 20 | UCMCResult.Error(FirestoreException()) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/repository/NicknameRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.repository 2 | 3 | import com.gta.data.source.NicknameDataSource 4 | import com.gta.domain.model.FirestoreException 5 | import com.gta.domain.model.NicknameState 6 | import com.gta.domain.model.UCMCResult 7 | import com.gta.domain.repository.NicknameRepository 8 | import kotlinx.coroutines.flow.first 9 | import javax.inject.Inject 10 | 11 | class NicknameRepositoryImpl @Inject constructor( 12 | private val dataSource: NicknameDataSource 13 | ) : NicknameRepository { 14 | override fun checkNicknameState(nickname: String): NicknameState = 15 | when { 16 | nickname.length < MIN_LENGTH -> NicknameState.SHORT_LENGTH 17 | SYMBOL_REGEX.containsMatchIn(nickname) -> NicknameState.CONTAIN_SYMBOL 18 | else -> NicknameState.GREAT 19 | } 20 | 21 | override suspend fun updateNickname(uid: String, nickname: String): UCMCResult = 22 | if (dataSource.updateNickname(uid, nickname).first()) { 23 | UCMCResult.Success(Unit) 24 | } else { 25 | UCMCResult.Error(FirestoreException()) 26 | } 27 | 28 | companion object { 29 | private const val MIN_LENGTH = 2 30 | private val SYMBOL_REGEX = "[^ㄱ-ㅎㅏ-ㅣ가-힣\\w]+".toRegex() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/repository/PinkSlipRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.repository 2 | 3 | import com.gta.data.model.Car 4 | import com.gta.data.source.CarDataSource 5 | import com.gta.data.source.PinkSlipDataSource 6 | import com.gta.data.source.UserDataSource 7 | import com.gta.domain.model.DuplicatedItemException 8 | import com.gta.domain.model.FirestoreException 9 | import com.gta.domain.model.PinkSlip 10 | import com.gta.domain.model.UCMCResult 11 | import com.gta.domain.repository.PinkSlipRepository 12 | import kotlinx.coroutines.flow.first 13 | import java.nio.ByteBuffer 14 | import javax.inject.Inject 15 | 16 | class PinkSlipRepositoryImpl @Inject constructor( 17 | private val carDataSource: CarDataSource, 18 | private val userDataSource: UserDataSource, 19 | private val pinkSlipDataSource: PinkSlipDataSource 20 | ) : PinkSlipRepository { 21 | 22 | override fun getPinkSlip(buffer: ByteBuffer): PinkSlip = 23 | pinkSlipDataSource.getRandomPinkSlip() 24 | 25 | override suspend fun setPinkSlip(uid: String, pinkSlip: PinkSlip): UCMCResult { 26 | /* 27 | 1. 유저 정보 가져오기 28 | 2. 리스트 뒤에 새로운 차의 ID 붙이고 업데이트 29 | 3. 차 테이블에 새로운 차 추가 30 | */ 31 | return userDataSource.getUser(uid).first()?.let { userInfo -> 32 | if (carDataSource.getCar(pinkSlip.informationNumber).first() != null) { 33 | UCMCResult.Error(DuplicatedItemException()) 34 | } else { 35 | val updatedCars = userInfo.myCars.plus(pinkSlip.informationNumber) 36 | updateCars(uid, updatedCars, pinkSlip) 37 | } 38 | } ?: UCMCResult.Error(FirestoreException()) 39 | } 40 | 41 | private suspend fun updateCars(uid: String, cars: List, pinkSlip: PinkSlip): UCMCResult { 42 | val car = Car(ownerId = uid, pinkSlip = pinkSlip) 43 | val result = pinkSlipDataSource.updateCars(uid, cars).first() && carDataSource.createCar(pinkSlip.informationNumber, car).first() 44 | return if (result) { 45 | UCMCResult.Success(Unit) 46 | } else { 47 | UCMCResult.Error(FirestoreException()) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/repository/ReportRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.repository 2 | 3 | import com.gta.data.source.UserDataSource 4 | import com.gta.domain.model.CoolDownException 5 | import com.gta.domain.model.FirestoreException 6 | import com.gta.domain.model.UCMCResult 7 | import com.gta.domain.repository.ReportRepository 8 | import kotlinx.coroutines.flow.first 9 | import javax.inject.Inject 10 | 11 | class ReportRepositoryImpl @Inject constructor( 12 | private val userDataSource: UserDataSource 13 | ) : ReportRepository { 14 | 15 | private var lastReportedTime = 0L 16 | 17 | override suspend fun reportUser( 18 | uid: String, 19 | currentTime: Long 20 | ): UCMCResult { 21 | val cooldown = REPORT_COOL_DOWN - getTimeAfterReporting(currentTime) 22 | return if (cooldown > 0) { 23 | UCMCResult.Error(CoolDownException(cooldown / 1000 + 1)) 24 | } else { 25 | addReportCount(uid, currentTime) 26 | } 27 | } 28 | 29 | private suspend fun addReportCount(uid: String, currentTime: Long): UCMCResult { 30 | return userDataSource.getUser(uid).first()?.let { user -> 31 | val result = userDataSource.addReportCount(uid, user.reportCount + 1).first() 32 | if (result) { 33 | lastReportedTime = currentTime 34 | UCMCResult.Success(Unit) 35 | } else { 36 | UCMCResult.Error(FirestoreException()) 37 | } 38 | } ?: UCMCResult.Error(FirestoreException()) 39 | } 40 | 41 | private fun getTimeAfterReporting(currentTime: Long): Long = 42 | (currentTime - lastReportedTime) 43 | 44 | companion object { 45 | private const val REPORT_COOL_DOWN = 10000 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/repository/TransactionRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.repository 2 | 3 | import androidx.paging.Pager 4 | import androidx.paging.PagingConfig 5 | import androidx.paging.PagingData 6 | import com.gta.data.source.TransactionDataSource 7 | import com.gta.data.source.TransactionPagingSource 8 | import com.gta.domain.model.SimpleReservation 9 | import com.gta.domain.repository.TransactionRepository 10 | import kotlinx.coroutines.flow.Flow 11 | import javax.inject.Inject 12 | 13 | class TransactionRepositoryImpl @Inject constructor(private val transactionDataSource: TransactionDataSource) : TransactionRepository { 14 | override fun getTransactions(userId: String, isLender: Boolean): Flow> { 15 | return Pager(PagingConfig(transactionDataSource.pagingSize.toInt())) { 16 | TransactionPagingSource(userId, isLender, transactionDataSource) 17 | }.flow 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/repository/UserRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.repository 2 | 3 | import com.gta.data.model.toProfile 4 | import com.gta.data.source.ReservationDataSource 5 | import com.gta.data.source.UserDataSource 6 | import com.gta.domain.model.FirestoreException 7 | import com.gta.domain.model.SimpleReservation 8 | import com.gta.domain.model.UCMCResult 9 | import com.gta.domain.model.UserProfile 10 | import com.gta.domain.repository.UserRepository 11 | import kotlinx.coroutines.channels.awaitClose 12 | import kotlinx.coroutines.flow.Flow 13 | import kotlinx.coroutines.flow.callbackFlow 14 | import kotlinx.coroutines.flow.first 15 | import kotlinx.coroutines.flow.map 16 | import javax.inject.Inject 17 | 18 | class UserRepositoryImpl @Inject constructor( 19 | private val userDataSource: UserDataSource, 20 | private val reservationDataSource: ReservationDataSource 21 | ) : UserRepository { 22 | 23 | override fun getUserProfile(uid: String): Flow> = callbackFlow { 24 | userDataSource.getUser(uid).first()?.let { profile -> 25 | trySend(UCMCResult.Success(profile.toProfile(uid))) 26 | } ?: trySend(UCMCResult.Error(FirestoreException())) 27 | awaitClose() 28 | } 29 | 30 | // 현재 유저가 이 차에 대한 대여중인 예약 정보 (실시간) 31 | override fun getNowReservation( 32 | uid: String, 33 | carId: String 34 | ): Flow> { 35 | return reservationDataSource.getRentingStateReservations(uid).map { 36 | UCMCResult.Success( 37 | it.find { reservation -> reservation.carId == carId } 38 | ?: SimpleReservation() 39 | ) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/service/AddressSearchService.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.service 2 | 3 | import com.gta.data.model.AddressResult 4 | import com.gta.data.model.SearchResult 5 | import retrofit2.http.GET 6 | import retrofit2.http.Query 7 | 8 | interface AddressSearchService { 9 | @GET("v2/local/search/address.json") 10 | suspend fun requestAddressList( 11 | @Query("query") query: String 12 | ): SearchResult 13 | 14 | @GET("v2/local/search/keyword.json") 15 | suspend fun requestKeywordList( 16 | @Query("query") query: String 17 | ): SearchResult 18 | 19 | @GET("v2/local/geo/coord2address.json") 20 | suspend fun requestLocation( 21 | @Query("x") longitude: String, 22 | @Query("y") latitude: String 23 | ): AddressResult 24 | } 25 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/service/CloudMessageService.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.service 2 | 3 | import com.gta.data.model.MessageResult 4 | import com.gta.data.model.NotificationMessage 5 | import retrofit2.http.Body 6 | import retrofit2.http.POST 7 | 8 | interface CloudMessageService { 9 | @POST("fcm/send") 10 | suspend fun sendNotificationMessage( 11 | @Body message: NotificationMessage 12 | ): MessageResult 13 | } 14 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/source/LicenseDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.source 2 | 3 | import com.google.firebase.firestore.FirebaseFirestore 4 | import com.gta.domain.model.DrivingLicense 5 | import kotlinx.coroutines.channels.awaitClose 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.callbackFlow 8 | import javax.inject.Inject 9 | 10 | class LicenseDataSource @Inject constructor( 11 | private val fireStore: FirebaseFirestore 12 | ) { 13 | fun registerLicense(uid: String, license: DrivingLicense): Flow = callbackFlow { 14 | fireStore.collection("users").document(uid).update("license", license) 15 | .addOnCompleteListener { 16 | trySend(it.isSuccessful) 17 | } 18 | awaitClose() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/source/LoginDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.source 2 | 3 | import com.google.firebase.firestore.FirebaseFirestore 4 | import com.gta.data.model.UserInfo 5 | import kotlinx.coroutines.channels.awaitClose 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.callbackFlow 8 | import javax.inject.Inject 9 | 10 | class LoginDataSource @Inject constructor( 11 | private val fireStore: FirebaseFirestore 12 | ) { 13 | fun createUser(uid: String, messageToken: String): Flow = callbackFlow { 14 | val userInfo = UserInfo(messageToken = messageToken) 15 | fireStore 16 | .collection("users") 17 | .document(uid) 18 | .set(userInfo) 19 | .addOnCompleteListener { 20 | if (it.isSuccessful) { 21 | trySend(userInfo) 22 | } else { 23 | trySend(null) 24 | } 25 | } 26 | awaitClose() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/source/MapDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.source 2 | 3 | import com.gta.data.model.AddressResult 4 | import com.gta.data.model.SearchResult 5 | import com.gta.data.service.AddressSearchService 6 | import javax.inject.Inject 7 | 8 | class MapDataSource @Inject constructor(private val service: AddressSearchService) { 9 | suspend fun getSearchAddressList(query: String): SearchResult { 10 | return service.requestAddressList(query) 11 | } 12 | 13 | suspend fun getSearchKeywordList(query: String): SearchResult { 14 | return service.requestKeywordList(query) 15 | } 16 | 17 | suspend fun getSearchCoordinate(longitude: String, latitude: String): AddressResult { 18 | return service.requestLocation(longitude, latitude) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/source/MessageTokenDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.source 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.preferences.core.Preferences 6 | import androidx.datastore.preferences.core.edit 7 | import androidx.datastore.preferences.core.stringPreferencesKey 8 | import androidx.datastore.preferences.preferencesDataStore 9 | import dagger.hilt.android.qualifiers.ApplicationContext 10 | import kotlinx.coroutines.flow.Flow 11 | import kotlinx.coroutines.flow.map 12 | import javax.inject.Inject 13 | 14 | private val Context.datastore: DataStore by preferencesDataStore(name = "preferences") 15 | 16 | class MessageTokenDataSource @Inject constructor(@ApplicationContext private val context: Context) { 17 | private val tokenKey = stringPreferencesKey(name = "token") 18 | 19 | suspend fun setMessageToken(token: String): Boolean { 20 | return kotlin.runCatching { 21 | context.datastore.edit { preferences -> 22 | preferences[tokenKey] = token 23 | } 24 | }.isSuccess 25 | } 26 | 27 | fun getMessageToken(): Flow { 28 | return context.datastore.data.map { preferences -> 29 | preferences[tokenKey] ?: "" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/source/MyPageDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.source 2 | 3 | import com.google.firebase.firestore.FirebaseFirestore 4 | import kotlinx.coroutines.channels.awaitClose 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.callbackFlow 7 | import javax.inject.Inject 8 | 9 | class MyPageDataSource @Inject constructor( 10 | private val fireStore: FirebaseFirestore 11 | ) { 12 | fun setThumbnail(uid: String, downloadUrl: String): Flow = callbackFlow { 13 | fireStore.collection("users").document(uid).update("icon", downloadUrl).addOnCompleteListener { 14 | trySend(it.isSuccessful) 15 | } 16 | awaitClose() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/source/NicknameDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.source 2 | 3 | import com.google.firebase.firestore.FirebaseFirestore 4 | import kotlinx.coroutines.channels.awaitClose 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.callbackFlow 7 | import javax.inject.Inject 8 | 9 | class NicknameDataSource @Inject constructor( 10 | private val fireStore: FirebaseFirestore 11 | ) { 12 | fun updateNickname(uid: String, nickname: String): Flow = callbackFlow { 13 | fireStore.collection("users").document(uid).update("nickname", nickname).addOnCompleteListener { 14 | trySend(it.isSuccessful) 15 | } 16 | awaitClose() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/source/NotificationDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.source 2 | 3 | import com.google.firebase.firestore.DocumentSnapshot 4 | import com.google.firebase.firestore.FirebaseFirestore 5 | import com.google.firebase.firestore.Query 6 | import com.google.firebase.firestore.QuerySnapshot 7 | import com.gta.data.model.NotificationMessage 8 | import com.gta.data.service.CloudMessageService 9 | import com.gta.domain.model.Notification 10 | import kotlinx.coroutines.channels.awaitClose 11 | import kotlinx.coroutines.flow.Flow 12 | import kotlinx.coroutines.flow.callbackFlow 13 | import kotlinx.coroutines.tasks.await 14 | import javax.inject.Inject 15 | 16 | class NotificationDataSource @Inject constructor( 17 | private val cloudMessageService: CloudMessageService, 18 | private val fireStore: FirebaseFirestore 19 | ) { 20 | suspend fun sendNotification(notification: Notification, to: String): Boolean { 21 | return kotlin.runCatching { 22 | cloudMessageService.sendNotificationMessage( 23 | NotificationMessage( 24 | to = to, 25 | data = notification 26 | ) 27 | ) 28 | }.isSuccess 29 | } 30 | 31 | fun saveNotification( 32 | notification: Notification, 33 | userId: String, 34 | notificationId: String 35 | ): Flow = 36 | callbackFlow { 37 | fireStore.document("users/$userId/notifications/$notificationId").set(notification) 38 | .addOnCompleteListener { 39 | trySend(it.isSuccessful) 40 | } 41 | awaitClose() 42 | } 43 | 44 | private val ITEMS_PER_PAGE = 10L 45 | 46 | suspend fun getNotificationInfoCurrentItem(userId: String): QuerySnapshot { 47 | return fireStore.collection("users/$userId/notifications") 48 | .orderBy("timestamp", Query.Direction.DESCENDING) 49 | .limit(ITEMS_PER_PAGE) 50 | .get() 51 | .await() 52 | } 53 | 54 | suspend fun getNotificationInfoNextItem(userId: String, doc: DocumentSnapshot): QuerySnapshot { 55 | return fireStore.collection("users/$userId/notifications") 56 | .orderBy("timestamp", Query.Direction.DESCENDING) 57 | .limit(ITEMS_PER_PAGE) 58 | .startAfter(doc) 59 | .get() 60 | .await() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/source/NotificationPagingSource.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.source 2 | 3 | import androidx.paging.PagingSource 4 | import androidx.paging.PagingState 5 | import com.google.firebase.firestore.QuerySnapshot 6 | import com.gta.data.repository.NotificationRepositoryImpl 7 | import com.gta.domain.model.Notification 8 | import com.gta.domain.model.NotificationInfo 9 | import com.gta.domain.model.toInfo 10 | 11 | class NotificationPagingSource( 12 | private val userId: String, 13 | private val dataSource: NotificationDataSource, 14 | private val repository: NotificationRepositoryImpl 15 | ) : PagingSource() { 16 | override fun getRefreshKey(state: PagingState): QuerySnapshot? = 17 | null 18 | 19 | override suspend fun load(params: LoadParams): LoadResult { 20 | return try { 21 | val currentPage = params.key ?: dataSource.getNotificationInfoCurrentItem(userId) 22 | 23 | val nextPage: QuerySnapshot? = 24 | if (currentPage.size() > 0) { 25 | val lastDocumentSnapshot = currentPage.documents[currentPage.size() - 1] 26 | dataSource.getNotificationInfoNextItem(userId, lastDocumentSnapshot) 27 | } else { 28 | null 29 | } 30 | 31 | LoadResult.Page( 32 | data = currentPage.map { 33 | repository.getNotificationInfoDetailItem( 34 | it.toObject( 35 | Notification::class.java 36 | ).toInfo(it.id) 37 | ) 38 | }, 39 | prevKey = null, 40 | nextKey = nextPage 41 | ) 42 | } catch (e: Exception) { 43 | LoadResult.Error(e) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/source/ReviewDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.source 2 | 3 | import com.google.firebase.firestore.FirebaseFirestore 4 | import com.gta.domain.model.UserReview 5 | import kotlinx.coroutines.channels.awaitClose 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.callbackFlow 8 | import javax.inject.Inject 9 | 10 | class ReviewDataSource @Inject constructor( 11 | private val fireStore: FirebaseFirestore 12 | ) { 13 | fun isExistReview(opponentId: String, reservationId: String): Flow = callbackFlow { 14 | fireStore 15 | .collection("users") 16 | .document(opponentId) 17 | .collection("reviews") 18 | .document(reservationId) 19 | .get() 20 | .addOnCompleteListener { 21 | trySend(it.isSuccessful && it.result.exists()) 22 | } 23 | awaitClose() 24 | } 25 | 26 | fun addReview(opponentId: String, reservationId: String, review: UserReview): Flow = callbackFlow { 27 | fireStore 28 | .collection("users") 29 | .document(opponentId) 30 | .collection("reviews") 31 | .document(reservationId) 32 | .set(review) 33 | .addOnCompleteListener { 34 | trySend(it.isSuccessful) 35 | } 36 | awaitClose() 37 | } 38 | 39 | fun updateTemperature(opponentId: String, temperature: Float): Flow = callbackFlow { 40 | fireStore 41 | .collection("users") 42 | .document(opponentId) 43 | .update("temperature", temperature) 44 | .addOnCompleteListener { 45 | trySend(it.isSuccessful) 46 | } 47 | awaitClose() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/source/StorageDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.source 2 | 3 | import android.net.Uri 4 | import com.google.firebase.storage.StorageReference 5 | import kotlinx.coroutines.channels.awaitClose 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.callbackFlow 8 | import javax.inject.Inject 9 | 10 | class StorageDataSource @Inject constructor( 11 | private val storageReference: StorageReference 12 | ) { 13 | fun uploadPicture(path: String, uri: String): Flow = callbackFlow { 14 | val image = Uri.parse(uri) 15 | val ref = storageReference.child(path) 16 | ref.putFile(image).continueWithTask { 17 | ref.downloadUrl 18 | }.addOnCompleteListener { 19 | if (it.isSuccessful) { 20 | trySend(it.result.toString()) 21 | } else { 22 | trySend(null) 23 | } 24 | } 25 | awaitClose() 26 | } 27 | 28 | fun deletePicture(path: String): Flow = callbackFlow { 29 | storageReference.storage.getReferenceFromUrl(path).delete().addOnCompleteListener { 30 | trySend(it.isSuccessful) 31 | } 32 | awaitClose() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/source/TransactionDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.source 2 | 3 | import com.google.firebase.firestore.DocumentSnapshot 4 | import com.google.firebase.firestore.FirebaseFirestore 5 | import com.google.firebase.firestore.QuerySnapshot 6 | import kotlinx.coroutines.tasks.await 7 | import javax.inject.Inject 8 | 9 | class TransactionDataSource @Inject constructor(private val fireStore: FirebaseFirestore) { 10 | val pagingSize = 10L 11 | 12 | suspend fun getYourCarTransactions(uid: String): QuerySnapshot { 13 | return fireStore.collection("reservations") 14 | .whereEqualTo("lenderId", uid) 15 | .limit(pagingSize) 16 | .get() 17 | .await() 18 | } 19 | 20 | suspend fun getYourCarTransactionsFromCursor( 21 | uid: String, 22 | docCursor: DocumentSnapshot 23 | ): QuerySnapshot { 24 | return fireStore.collection("reservations") 25 | .whereEqualTo("lenderId", uid) 26 | .limit(pagingSize) 27 | .startAfter(docCursor) 28 | .get() 29 | .await() 30 | } 31 | 32 | suspend fun getMyCarTransactions(uid: String): QuerySnapshot { 33 | return fireStore.collection("reservations") 34 | .whereEqualTo("ownerId", uid) 35 | .limit(pagingSize) 36 | .get() 37 | .await() 38 | } 39 | 40 | suspend fun getMyCarTransactionsFromCursor( 41 | uid: String, 42 | docCursor: DocumentSnapshot 43 | ): QuerySnapshot { 44 | return fireStore.collection("reservations") 45 | .whereEqualTo("ownerId", uid) 46 | .limit(pagingSize) 47 | .startAfter(docCursor) 48 | .get() 49 | .await() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/source/TransactionPagingSource.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.source 2 | 3 | import androidx.paging.PagingSource 4 | import androidx.paging.PagingState 5 | import com.google.firebase.firestore.DocumentSnapshot 6 | import com.google.firebase.firestore.QuerySnapshot 7 | import com.gta.domain.model.Reservation 8 | import com.gta.domain.model.SimpleReservation 9 | import com.gta.domain.model.toSimpleReservation 10 | import kotlin.reflect.KSuspendFunction1 11 | import kotlin.reflect.KSuspendFunction2 12 | 13 | class TransactionPagingSource( 14 | private val userId: String, 15 | isLender: Boolean, 16 | dataSource: TransactionDataSource 17 | ) : PagingSource() { 18 | 19 | private val getTransactions: KSuspendFunction1 = 20 | if (isLender) dataSource::getYourCarTransactions else dataSource::getMyCarTransactions 21 | 22 | private val getTransactionsNext: KSuspendFunction2 = 23 | if (isLender) dataSource::getYourCarTransactionsFromCursor else dataSource::getMyCarTransactionsFromCursor 24 | 25 | override fun getRefreshKey(state: PagingState): QuerySnapshot? = 26 | null 27 | 28 | override suspend fun load(params: LoadParams): LoadResult { 29 | return try { 30 | val currentPage = params.key ?: getTransactions(userId) 31 | val nextPage: QuerySnapshot? = 32 | if (currentPage.size() > 0) { 33 | val lastDocumentSnapshot = currentPage.documents[currentPage.size() - 1] 34 | getTransactionsNext(userId, lastDocumentSnapshot) 35 | } else { 36 | null 37 | } 38 | 39 | LoadResult.Page( 40 | data = currentPage.map { snapshot -> 41 | snapshot.toObject(Reservation::class.java).toSimpleReservation(snapshot.id) 42 | }, 43 | prevKey = null, 44 | nextKey = nextPage 45 | ) 46 | } catch (e: Exception) { 47 | LoadResult.Error(e) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /data/src/main/java/com/gta/data/source/UserDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data.source 2 | 3 | import com.google.firebase.firestore.FirebaseFirestore 4 | import com.gta.data.model.UserInfo 5 | import kotlinx.coroutines.channels.awaitClose 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.callbackFlow 8 | import kotlinx.coroutines.tasks.await 9 | import javax.inject.Inject 10 | 11 | class UserDataSource @Inject constructor( 12 | private val fireStore: FirebaseFirestore 13 | ) { 14 | fun getUser(uid: String): Flow = callbackFlow { 15 | fireStore.collection("users").document(uid).get().addOnCompleteListener { 16 | if (it.isSuccessful) { 17 | trySend(it.result.toObject(UserInfo::class.java)) 18 | } else { 19 | trySend(null) 20 | } 21 | } 22 | awaitClose() 23 | } 24 | 25 | suspend fun getSuspendUser(uid: String): UserInfo? { 26 | return fireStore.collection("users") 27 | .document(uid) 28 | .get() 29 | .await() 30 | .toObject(UserInfo::class.java) 31 | } 32 | 33 | fun removeCar(uid: String, newCars: List): Flow = callbackFlow { 34 | fireStore.collection("users").document(uid).update("myCars", newCars) 35 | .addOnCompleteListener { 36 | trySend(it.isSuccessful) 37 | } 38 | awaitClose() 39 | } 40 | 41 | fun updateUserMessageToken(uid: String, token: String) = callbackFlow { 42 | fireStore.collection("users").document(uid).update("messageToken", token) 43 | .addOnCompleteListener { 44 | trySend(it.isSuccessful) 45 | } 46 | awaitClose() 47 | } 48 | 49 | fun addReportCount(uid: String, count: Int): Flow = callbackFlow { 50 | fireStore.collection("users").document(uid).update("reportCount", count).addOnCompleteListener { 51 | trySend(it.isSuccessful) 52 | } 53 | awaitClose() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /data/src/test/java/com/gta/data/MessageTokenRepositoryUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data 2 | 3 | import com.gta.data.repository.MessageTokenRepositoryImpl 4 | import com.gta.data.source.MessageTokenDataSource 5 | import com.gta.domain.repository.MessageTokenRepository 6 | import kotlinx.coroutines.runBlocking 7 | import org.junit.jupiter.api.Assertions 8 | import org.junit.jupiter.api.DisplayName 9 | import org.junit.jupiter.api.Test 10 | import org.junit.jupiter.api.extension.ExtendWith 11 | import org.mockito.Mock 12 | import org.mockito.Mockito.anyString 13 | import org.mockito.Mockito.`when` 14 | import org.mockito.junit.jupiter.MockitoExtension 15 | 16 | @ExtendWith(MockitoExtension::class) 17 | class MessageTokenRepositoryUnitTest(@Mock private val messageTokenDataSource: MessageTokenDataSource) { 18 | private val repository: MessageTokenRepository = 19 | MessageTokenRepositoryImpl(messageTokenDataSource) 20 | private val token = anyString() 21 | 22 | @Test 23 | @DisplayName("setMessageToken: 토큰이 정상적으로 저장되면 경우 true를 반환한다.") 24 | fun Should_True_When_storeTokenSuccess() = runBlocking { 25 | `when`(messageTokenDataSource.setMessageToken(token)).thenReturn(true) 26 | 27 | val result = repository.setMessageToken(token) 28 | 29 | Assertions.assertEquals(true, result) 30 | } 31 | 32 | @Test 33 | @DisplayName("setMessageToken: 토큰이 정상적으로 저장되지 않으면 false를 반환한다.") 34 | fun Should_False_When_storeTokenFail() = runBlocking { 35 | `when`(messageTokenDataSource.setMessageToken(token)).thenReturn(false) 36 | 37 | val result = repository.setMessageToken(token) 38 | 39 | Assertions.assertEquals(false, result) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /data/src/test/java/com/gta/data/TestUtil.kt: -------------------------------------------------------------------------------- 1 | package com.gta.data 2 | 3 | const val GOOD_UID = "GoodDonghoon" 4 | const val BAD_UID = "BadDonghoon" 5 | const val EXCEPTION_UID = "ErrorDonghoon" 6 | const val GOOD_URI = "GoodUri" 7 | const val BAD_URI = "BadUri" 8 | const val GOOD_RESERVATION = "goodReservation" 9 | const val BAD_RESERVATION = "badReservation" 10 | const val DUPLICATED_RESERVATION = "duplicatedReservation" 11 | const val GOOD_CAR = "goodCar" 12 | const val BAD_CAR = "badCar" 13 | const val DUPLICATED_CAR = "duplicatedCar" 14 | -------------------------------------------------------------------------------- /domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /domain/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | kotlin("jvm") 4 | } 5 | 6 | java { 7 | sourceCompatibility = JavaVersion.VERSION_1_8 8 | targetCompatibility = JavaVersion.VERSION_1_8 9 | } 10 | 11 | dependencies { 12 | implementation(Dependencies.Libraries.domainLibraries) 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/AvailableDate.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | data class AvailableDate( 4 | val start: Long = 0, 5 | val end: Long = 0 6 | ) 7 | 8 | fun AvailableDate.toPair(): Pair { 9 | return start to end 10 | } 11 | 12 | fun List.toPairList(): List> { 13 | return this.map { it.toPair() } 14 | } 15 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/CarDetail.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | enum class RentState(val string: String) { 4 | AVAILABLE("대여 가능"), UNAVAILABLE("대여 불가능"), RENTED("대여중") 5 | } 6 | 7 | data class CarDetail( 8 | val id: String = "정보 없음", 9 | val carType: String = "정보 없음", 10 | val model: String = "정보 없음", 11 | val year: Int = 0, 12 | val licensePlate: String = "정보 없음", 13 | val price: Int = 0, 14 | val location: String = "정보 없음", 15 | val rentState: RentState = RentState.UNAVAILABLE, 16 | val comment: String = "", 17 | val availableDate: AvailableDate = AvailableDate(), 18 | val images: List = emptyList(), 19 | val owner: UserProfile = UserProfile(), 20 | val coordinate: Coordinate = Coordinate() 21 | ) 22 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/CarRentInfo.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | data class CarRentInfo( 4 | val images: List = emptyList(), 5 | val model: String = "", 6 | val price: Int = 10000, 7 | val comment: String = "차였어요", 8 | val availableDate: AvailableDate = AvailableDate(), 9 | val reservationDates: List = emptyList(), 10 | val ownerId: String = "정보 없음" 11 | ) 12 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/Coordinate.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | data class Coordinate( 4 | val latitude: Double = 37.3588798, 5 | val longitude: Double = 127.1051933 6 | ) : java.io.Serializable 7 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/DrivingLicense.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | data class DrivingLicense( 4 | val id: String = "", 5 | val name: String = "", 6 | val uri: String = "", 7 | val residentNumberFront: String = "", 8 | val residentNumberBack: String = "", 9 | val aptitudeTestDate: String = "", 10 | val expireDate: String = "" 11 | ) 12 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/InsuranceOption.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | enum class InsuranceOption(val price: Int) { 4 | LOW(6310), MEDIUM(5210), HIGH(3870) 5 | } 6 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/LocationInfo.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | data class LocationInfo( 4 | val address: String, 5 | val name: String?, 6 | val latitude: Double, 7 | val longitude: Double 8 | ) 9 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/NicknameState.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | enum class NicknameState { 4 | IDLE, 5 | GREAT, 6 | SHORT_LENGTH, 7 | CONTAIN_SYMBOL 8 | } 9 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/Notification.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | data class Notification( 4 | val type: String = "", 5 | val message: String = "", 6 | val reservationId: String = "정보 없음", 7 | val fromId: String = "정보 없음", 8 | val timestamp: Long = 0 9 | ) 10 | 11 | enum class NotificationType(val title: String, val msg: String) { 12 | REQUEST_RESERVATION( 13 | "예약 요청", 14 | "자동차 대여 예약 요청이 도착했습니다." 15 | ), 16 | ACCEPT_RESERVATION( 17 | "예약 수락", 18 | "자동차 대여 예약이 완료됐습니다." 19 | ), 20 | DECLINE_RESERVATION( 21 | "예약 거절", 22 | "자동차 대여 예약이 거절됐습니다." 23 | ), 24 | RETURN_CAR( 25 | "차량 반납", 26 | "대여자가 차를 반납했습니다." 27 | ) 28 | } 29 | 30 | fun Notification.toInfo(id: String): NotificationInfo = NotificationInfo( 31 | id = id, 32 | type = NotificationType.values().filter { it.title == type }[0], 33 | reservationId = reservationId, 34 | fromId = fromId, 35 | fromNickName = fromId, 36 | carImage = "", 37 | licensePlate = "정보 없음", 38 | date = id.substringBefore("-") 39 | ) 40 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/NotificationInfo.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | data class NotificationInfo( 4 | val id: String, 5 | val type: NotificationType, 6 | val reservationId: String, 7 | val fromId: String, 8 | var fromNickName: String, 9 | var carImage: String?, 10 | var licensePlate: String, 11 | var date: String 12 | ) 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/PinkSlip.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | data class PinkSlip( 4 | val id: String = "", 5 | val informationNumber: String = "", 6 | val owner: String = "", 7 | val type: String = "", 8 | val model: String = "", 9 | val year: Int = 0 10 | ) 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/Reservation.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | enum class ReservationState(val state: Int) { 4 | CANCEL(-1), PENDING(0), ACCEPT(1), RENTING(2), DONE(-2) 5 | } 6 | 7 | data class Reservation( 8 | val carId: String = "정보 없음", 9 | val lenderId: String = "정보 없음", 10 | val ownerId: String = "정보 없음", 11 | val state: Int = ReservationState.PENDING.state, 12 | val reservationDate: AvailableDate = AvailableDate(), 13 | val price: Long = 0, 14 | val insuranceOption: String = InsuranceOption.LOW.name 15 | ) 16 | 17 | fun Reservation.toSimpleReservation(reservationId: String): SimpleReservation = SimpleReservation( 18 | reservationId = reservationId, 19 | carId = carId, 20 | reservationState = state, 21 | reservationDate = reservationDate 22 | ) 23 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/ReviewDTO.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | data class ReviewDTO( 4 | val reviewType: ReviewType = ReviewType.LENDER_TO_OWNER, 5 | val opponent: UserProfile = UserProfile(), 6 | val carImage: String = "", 7 | val carModel: String = "" 8 | ) 9 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/ReviewType.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | enum class ReviewType { 4 | OWNER_TO_LENDER, 5 | LENDER_TO_OWNER 6 | } 7 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/SimpleCar.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | data class SimpleCar( 4 | val id: String = "정보 없음", 5 | val image: String = "", 6 | val carType: String = "정보 없음", 7 | val model: String = "정보 없음", 8 | val year: Int = 0, 9 | val price: Int = 0, 10 | val coordinate: Coordinate = Coordinate(), 11 | val licensePlate: String = "정보 없음" 12 | ) 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/SimpleReservation.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | data class SimpleReservation( 4 | val reservationId: String = "", 5 | val carId: String = "", 6 | val reservationState: Int = ReservationState.PENDING.state, 7 | val reservationDate: AvailableDate = AvailableDate() 8 | ) 9 | 10 | fun SimpleReservation.toTransaction(simpleCar: SimpleCar): Transaction { 11 | return Transaction( 12 | reservationId = reservationId, 13 | reservationState = ReservationState.values().find { it.state == reservationState } ?: ReservationState.RENTING, 14 | reservationDate = reservationDate, 15 | carModel = simpleCar.model, 16 | thumbnailImg = simpleCar.image 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/Transaction.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | data class Transaction( 4 | val reservationId: String = "", 5 | val reservationState: ReservationState = ReservationState.PENDING, 6 | val reservationDate: AvailableDate = AvailableDate(), 7 | val carModel: String = "", 8 | val thumbnailImg: String = "" 9 | ) 10 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/UCMCException.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | class CoolDownException(val cooldown: Long) : Exception() 4 | class FirestoreException : Exception() 5 | class DuplicatedItemException : Exception() 6 | class DeleteFailException : Exception() 7 | class UserNotFoundException : Exception() 8 | class ExpiredItemException : Exception() 9 | class UpdateFailException : Exception() 10 | class NotFoundDataException : Exception() 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/UCMCResult.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | sealed class UCMCResult { 4 | data class Success(val data: T) : UCMCResult() 5 | data class Error(val e: Exception) : UCMCResult() 6 | } 7 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/UpdateCar.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | data class UpdateCar( 4 | val images: List = emptyList(), 5 | val price: Int, 6 | val comment: String = "", 7 | val rentState: RentState, 8 | val availableDate: AvailableDate, 9 | val location: String, 10 | val coordinate: Coordinate 11 | ) 12 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/UserProfile.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | data class UserProfile( 4 | val id: String = "정보 없음", 5 | val name: String = "정보 없음", 6 | val temp: Float = 0.0f, 7 | val image: String? = "" 8 | ) 9 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/model/UserReview.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.model 2 | 3 | data class UserReview( 4 | val from: String = "정보 없음", 5 | val comment: String = "", 6 | val rating: Float = 5.0f 7 | ) 8 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/repository/CarRepository.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.repository 2 | 3 | import com.gta.domain.model.CarDetail 4 | import com.gta.domain.model.CarRentInfo 5 | import com.gta.domain.model.Coordinate 6 | import com.gta.domain.model.RentState 7 | import com.gta.domain.model.SimpleCar 8 | import com.gta.domain.model.UCMCResult 9 | import com.gta.domain.model.UpdateCar 10 | import kotlinx.coroutines.flow.Flow 11 | 12 | interface CarRepository { 13 | fun getOwnerId(carId: String): Flow> 14 | fun getCarRentState(carId: String): Flow> 15 | fun getCarData(carId: String): Flow> 16 | fun updateCarDetail(carId: String, update: UpdateCar): Flow 17 | fun getCarRentInfo(carId: String): Flow> 18 | fun getSimpleCar(carId: String): Flow 19 | fun getSimpleCarList(ownerId: String): Flow>> 20 | fun getAllCars(): Flow> 21 | fun getNearCars(min: Coordinate, max: Coordinate): Flow>> 22 | suspend fun removeCar(userId: String, carId: String): UCMCResult 23 | fun setCarImagesStorage(carId: String, images: List): Flow>> 24 | fun deleteImagesStorage(images: List): Flow 25 | } 26 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/repository/LicenseRepository.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.repository 2 | 3 | import com.gta.domain.model.DrivingLicense 4 | import com.gta.domain.model.UCMCResult 5 | import java.nio.ByteBuffer 6 | 7 | interface LicenseRepository { 8 | fun getLicenseFromImage(buffer: ByteBuffer): DrivingLicense 9 | suspend fun getLicenseFromDatabase(uid: String): UCMCResult 10 | suspend fun setLicense(uid: String, license: DrivingLicense, uri: String): UCMCResult 11 | } 12 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/repository/LoginRepository.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.repository 2 | 3 | import com.gta.domain.model.UCMCResult 4 | import com.gta.domain.model.UserProfile 5 | 6 | interface LoginRepository { 7 | suspend fun checkCurrentUser(uid: String): UCMCResult 8 | suspend fun signUp(uid: String): UCMCResult 9 | suspend fun updateUserMessageToken(uid: String): Boolean 10 | } 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/repository/MapRepository.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.repository 2 | 3 | import com.gta.domain.model.LocationInfo 4 | import com.gta.domain.model.UCMCResult 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface MapRepository { 8 | fun getSearchCoordinate(longitude: String, latitude: String): Flow> 9 | fun getSearchAddressList(query: String): Flow>> 10 | } 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/repository/MessageTokenRepository.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.repository 2 | 3 | interface MessageTokenRepository { 4 | suspend fun setMessageToken(token: String): Boolean 5 | } 6 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/repository/MyPageRepository.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.repository 2 | 3 | import com.gta.domain.model.UCMCResult 4 | 5 | interface MyPageRepository { 6 | suspend fun setThumbnail(uid: String, uri: String): UCMCResult 7 | } 8 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/repository/NicknameRepository.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.repository 2 | 3 | import com.gta.domain.model.NicknameState 4 | import com.gta.domain.model.UCMCResult 5 | 6 | interface NicknameRepository { 7 | fun checkNicknameState(nickname: String): NicknameState 8 | suspend fun updateNickname(uid: String, nickname: String): UCMCResult 9 | } 10 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/repository/NotificationRepository.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.repository 2 | 3 | import androidx.paging.PagingData 4 | import com.gta.domain.model.Notification 5 | import com.gta.domain.model.NotificationInfo 6 | import com.gta.domain.model.UCMCResult 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | interface NotificationRepository { 10 | suspend fun sendNotification(notification: Notification, receiverId: String): UCMCResult 11 | suspend fun saveNotification(notification: Notification, userId: String): UCMCResult 12 | fun getNotificationInfoList(userId: String): Flow> 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/repository/PinkSlipRepository.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.repository 2 | 3 | import com.gta.domain.model.PinkSlip 4 | import com.gta.domain.model.UCMCResult 5 | import java.nio.ByteBuffer 6 | 7 | interface PinkSlipRepository { 8 | fun getPinkSlip(buffer: ByteBuffer): PinkSlip 9 | suspend fun setPinkSlip(uid: String, pinkSlip: PinkSlip): UCMCResult 10 | } 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/repository/ReportRepository.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.repository 2 | 3 | import com.gta.domain.model.UCMCResult 4 | 5 | interface ReportRepository { 6 | suspend fun reportUser( 7 | uid: String, 8 | currentTime: Long = System.currentTimeMillis() 9 | ): UCMCResult 10 | } 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/repository/ReservationRepository.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.repository 2 | 3 | import com.gta.domain.model.Reservation 4 | import com.gta.domain.model.ReservationState 5 | import com.gta.domain.model.UCMCResult 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | interface ReservationRepository { 9 | suspend fun createReservation(reservation: Reservation): UCMCResult 10 | fun getReservationInfo(reservationId: String): Flow> 11 | fun getReservationCar(reservationId: String): Flow 12 | suspend fun updateReservationState(reservationId: String, state: ReservationState): UCMCResult 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/repository/ReviewRepository.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.repository 2 | 3 | import com.gta.domain.model.ReviewDTO 4 | import com.gta.domain.model.UCMCResult 5 | import com.gta.domain.model.UserReview 6 | 7 | interface ReviewRepository { 8 | suspend fun addReview(opponentId: String, reservationId: String, review: UserReview): UCMCResult 9 | suspend fun getReviewDTO(uid: String, reservationId: String): UCMCResult 10 | } 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/repository/TransactionRepository.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.repository 2 | 3 | import androidx.paging.PagingData 4 | import com.gta.domain.model.SimpleReservation 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface TransactionRepository { 8 | fun getTransactions(userId: String, isLender: Boolean): Flow> 9 | } 10 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/repository/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.repository 2 | 3 | import com.gta.domain.model.SimpleReservation 4 | import com.gta.domain.model.UCMCResult 5 | import com.gta.domain.model.UserProfile 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | interface UserRepository { 9 | fun getUserProfile(uid: String): Flow> 10 | fun getNowReservation(uid: String, carId: String): Flow> 11 | } 12 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/SendNotificationUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase 2 | 3 | import com.gta.domain.model.Notification 4 | import com.gta.domain.model.UCMCResult 5 | import com.gta.domain.repository.NotificationRepository 6 | import javax.inject.Inject 7 | 8 | class SendNotificationUseCase @Inject constructor(private val notificationRepository: NotificationRepository) { 9 | suspend operator fun invoke(notification: Notification, receiverId: String): UCMCResult { 10 | val saveResult = notificationRepository.saveNotification(notification, receiverId) 11 | return if (saveResult is UCMCResult.Success) { 12 | notificationRepository.sendNotification(notification, receiverId) 13 | } else { 14 | saveResult 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/car/GetSimpleCarUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.car 2 | 3 | import com.gta.domain.model.SimpleCar 4 | import com.gta.domain.repository.CarRepository 5 | import kotlinx.coroutines.flow.Flow 6 | import javax.inject.Inject 7 | 8 | class GetSimpleCarUseCase @Inject constructor( 9 | private val carRepository: CarRepository 10 | ) { 11 | operator fun invoke(carId: String): Flow = 12 | carRepository.getSimpleCar(carId) 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/cardetail/GetCarDetailDataUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.cardetail 2 | 3 | import com.gta.domain.model.CarDetail 4 | import com.gta.domain.model.UCMCResult 5 | import com.gta.domain.repository.CarRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetCarDetailDataUseCase @Inject constructor( 10 | private val carRepository: CarRepository 11 | ) { 12 | operator fun invoke(carId: String): Flow> { 13 | return carRepository.getCarData(carId) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/cardetail/GetNowRentCarUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.cardetail 2 | 3 | import com.gta.domain.model.SimpleReservation 4 | import com.gta.domain.model.UCMCResult 5 | import com.gta.domain.repository.UserRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetNowRentCarUseCase @Inject constructor( 10 | private val userRepository: UserRepository 11 | ) { 12 | operator fun invoke(uid: String, carId: String): Flow> { 13 | return userRepository.getNowReservation(uid, carId) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/cardetail/GetOwnerCarsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.cardetail 2 | 3 | import com.gta.domain.model.SimpleCar 4 | import com.gta.domain.model.UCMCResult 5 | import com.gta.domain.repository.CarRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetOwnerCarsUseCase @Inject constructor( 10 | private val carRepository: CarRepository 11 | ) { 12 | operator fun invoke(ownerId: String): Flow>> { 13 | return carRepository.getSimpleCarList(ownerId) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/cardetail/GetOwnerInfoUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.cardetail 2 | 3 | import com.gta.domain.model.UCMCResult 4 | import com.gta.domain.model.UserProfile 5 | import com.gta.domain.repository.UserRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetOwnerInfoUseCase @Inject constructor( 10 | private val userRepository: UserRepository 11 | ) { 12 | operator fun invoke(ownerId: String): Flow> { 13 | return userRepository.getUserProfile(ownerId) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/cardetail/GetUseStateAboutCarUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.cardetail 2 | 3 | import com.gta.domain.model.FirestoreException 4 | import com.gta.domain.model.RentState 5 | import com.gta.domain.model.UCMCResult 6 | import com.gta.domain.repository.CarRepository 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.combine 9 | import kotlinx.coroutines.flow.first 10 | import javax.inject.Inject 11 | 12 | enum class UseState { 13 | OWNER, NOW_RENT_USER, USER, UNAVAILABLE 14 | } 15 | 16 | class GetUseStateAboutCarUseCase @Inject constructor( 17 | private val carRepository: CarRepository, 18 | private val getNowRentCarUseCase: GetNowRentCarUseCase 19 | ) { 20 | operator fun invoke(uid: String, carId: String): Flow> { 21 | return getNowRentCarUseCase(uid, carId).combine(carRepository.getCarRentState(carId)) { reservation, carRentState -> 22 | val ownerId = carRepository.getOwnerId(carId).first() 23 | if (reservation is UCMCResult.Success && carRentState is UCMCResult.Success && ownerId is UCMCResult.Success) { 24 | when { 25 | // 내 아이디와 차주의 아이디가 같을 때 (first) 26 | uid == ownerId.data -> { 27 | UCMCResult.Success(UseState.OWNER) 28 | } 29 | // 차 아이디와 현재 대여중인 예약의 차 아이디가 같을 때 (실시간) 30 | carId == reservation.data.carId -> { 31 | UCMCResult.Success(UseState.NOW_RENT_USER) 32 | } 33 | // 현재 차 상태가 대여 불가능 일때 (실시간) 34 | RentState.UNAVAILABLE == carRentState.data -> { 35 | UCMCResult.Success(UseState.UNAVAILABLE) 36 | } 37 | else -> { 38 | UCMCResult.Success(UseState.USER) 39 | } 40 | } 41 | } else { 42 | UCMCResult.Error(FirestoreException()) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/cardetail/RemoveCarUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.cardetail 2 | 3 | import com.gta.domain.model.UCMCResult 4 | import com.gta.domain.repository.CarRepository 5 | import javax.inject.Inject 6 | 7 | class RemoveCarUseCase @Inject constructor( 8 | private val carRepository: CarRepository 9 | ) { 10 | suspend operator fun invoke(userId: String, carId: String): UCMCResult { 11 | return carRepository.removeCar(userId, carId) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/cardetail/edit/DeleteImagesUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.cardetail.edit 2 | 3 | import com.gta.domain.repository.CarRepository 4 | import kotlinx.coroutines.flow.Flow 5 | import javax.inject.Inject 6 | 7 | class DeleteImagesUseCase @Inject constructor( 8 | private val carRepository: CarRepository 9 | ) { 10 | operator fun invoke(images: List): Flow { 11 | return carRepository.deleteImagesStorage(images) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/cardetail/edit/GetCoordinateLocationUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.cardetail.edit 2 | 3 | import com.gta.domain.model.UCMCResult 4 | import com.gta.domain.repository.MapRepository 5 | import kotlinx.coroutines.flow.Flow 6 | import javax.inject.Inject 7 | 8 | class GetCoordinateLocationUseCase @Inject constructor( 9 | private val mapRepository: MapRepository 10 | ) { 11 | operator fun invoke(longitude: Double, latitude: Double): Flow> { 12 | return mapRepository.getSearchCoordinate(longitude.toString(), latitude.toString()) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/cardetail/edit/SetCarImagesUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.cardetail.edit 2 | 3 | import com.gta.domain.model.UCMCResult 4 | import com.gta.domain.repository.CarRepository 5 | import kotlinx.coroutines.flow.Flow 6 | import javax.inject.Inject 7 | 8 | class SetCarImagesUseCase @Inject constructor( 9 | private val carRepository: CarRepository 10 | ) { 11 | operator fun invoke(carId: String, images: List): Flow>> { 12 | return carRepository.setCarImagesStorage(carId, images) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/cardetail/edit/UpdateCarDetailDataUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.cardetail.edit 2 | 3 | import com.gta.domain.model.AvailableDate 4 | import com.gta.domain.model.Coordinate 5 | import com.gta.domain.model.RentState 6 | import com.gta.domain.model.UpdateCar 7 | import com.gta.domain.repository.CarRepository 8 | import kotlinx.coroutines.flow.Flow 9 | import javax.inject.Inject 10 | 11 | class UpdateCarDetailDataUseCase @Inject constructor( 12 | private val carRepository: CarRepository 13 | ) { 14 | operator fun invoke( 15 | carId: String, 16 | image: List?, 17 | price: Int, 18 | comment: String, 19 | rentState: RentState, 20 | availableDate: AvailableDate, 21 | location: String, 22 | coordinate: Coordinate 23 | ): Flow { 24 | return carRepository.updateCarDetail( 25 | carId, 26 | UpdateCar( 27 | image ?: emptyList(), 28 | price, 29 | comment, 30 | rentState, 31 | availableDate, 32 | location, 33 | coordinate 34 | ) 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/cardetail/edit/UploadCarImagesUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.cardetail.edit 2 | 3 | import com.gta.domain.model.DeleteFailException 4 | import com.gta.domain.model.UCMCResult 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.combine 7 | import javax.inject.Inject 8 | 9 | class UploadCarImagesUseCase @Inject constructor( 10 | private val deleteImagesUseCase: DeleteImagesUseCase, 11 | private val setCarImagesUseCase: SetCarImagesUseCase 12 | ) { 13 | operator fun invoke( 14 | carId: String, 15 | oldData: List, 16 | newData: List 17 | ): Flow>> { 18 | val deleteData = oldData.filter { !newData.contains(it) } 19 | val updateData = newData.filter { !oldData.contains(it) } 20 | 21 | /* 22 | 1. 삭제된 이미지는 저장소에서 삭제 23 | 2. 새로 추가된 이미지만 저장소에 새로 추가 24 | 3. 기존에 있던 이미지 + 새로 추가된 이미지 주소값 return 25 | */ 26 | 27 | return deleteImagesUseCase(deleteData).combine(setCarImagesUseCase(carId, updateData)) { deleteResult, list -> 28 | if (!deleteResult) { 29 | oldData.filter { o -> newData.contains(o) }.map { UCMCResult.Success(it) } + list + UCMCResult.Error(DeleteFailException()) 30 | } else { 31 | oldData.filter { o -> newData.contains(o) }.map { UCMCResult.Success(it) } + list 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/license/GetLicenseFromDatabaseUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.license 2 | 3 | import com.gta.domain.model.DrivingLicense 4 | import com.gta.domain.model.UCMCResult 5 | import com.gta.domain.repository.LicenseRepository 6 | import javax.inject.Inject 7 | 8 | class GetLicenseFromDatabaseUseCase @Inject constructor( 9 | private val repository: LicenseRepository 10 | ) { 11 | suspend operator fun invoke(uid: String): UCMCResult = 12 | repository.getLicenseFromDatabase(uid) 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/license/GetLicenseFromImageUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.license 2 | 3 | import com.gta.domain.model.DrivingLicense 4 | import com.gta.domain.repository.LicenseRepository 5 | import java.nio.ByteBuffer 6 | import javax.inject.Inject 7 | 8 | class GetLicenseFromImageUseCase @Inject constructor( 9 | private val repository: LicenseRepository 10 | ) { 11 | operator fun invoke(buffer: ByteBuffer): DrivingLicense = repository.getLicenseFromImage(buffer) 12 | } 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/license/SetLicenseUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.license 2 | 3 | import com.gta.domain.model.DrivingLicense 4 | import com.gta.domain.model.UCMCResult 5 | import com.gta.domain.repository.LicenseRepository 6 | import javax.inject.Inject 7 | 8 | class SetLicenseUseCase @Inject constructor( 9 | private val repository: LicenseRepository 10 | ) { 11 | suspend operator fun invoke(uid: String, license: DrivingLicense, uri: String): UCMCResult = 12 | repository.setLicense(uid, license, uri) 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/login/CheckCurrentUserUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.login 2 | 3 | import com.gta.domain.model.UCMCResult 4 | import com.gta.domain.model.UserProfile 5 | import com.gta.domain.repository.LoginRepository 6 | import javax.inject.Inject 7 | 8 | class CheckCurrentUserUseCase @Inject constructor( 9 | private val repository: LoginRepository 10 | ) { 11 | suspend operator fun invoke(uid: String): UCMCResult = 12 | repository.checkCurrentUser(uid) 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/login/SignUpUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.login 2 | 3 | import com.gta.domain.model.UCMCResult 4 | import com.gta.domain.model.UserProfile 5 | import com.gta.domain.repository.LoginRepository 6 | import javax.inject.Inject 7 | 8 | class SignUpUseCase @Inject constructor( 9 | private val repository: LoginRepository 10 | ) { 11 | suspend operator fun invoke(uid: String): UCMCResult = repository.signUp(uid) 12 | } 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/login/UpdateUserMessageTokenUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.login 2 | 3 | import com.gta.domain.repository.LoginRepository 4 | import javax.inject.Inject 5 | 6 | class UpdateUserMessageTokenUseCase @Inject constructor(private val loginRepository: LoginRepository) { 7 | suspend operator fun invoke(uid: String): Boolean = loginRepository.updateUserMessageToken(uid) 8 | } 9 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/map/GetAllCarsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.map 2 | 3 | import com.gta.domain.model.SimpleCar 4 | import com.gta.domain.repository.CarRepository 5 | import kotlinx.coroutines.flow.Flow 6 | import javax.inject.Inject 7 | 8 | class GetAllCarsUseCase @Inject constructor(private val carRepository: CarRepository) { 9 | operator fun invoke(): Flow> { 10 | return carRepository.getAllCars() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/map/GetNearCarsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.map 2 | 3 | import com.gta.domain.model.Coordinate 4 | import com.gta.domain.model.SimpleCar 5 | import com.gta.domain.model.UCMCResult 6 | import com.gta.domain.repository.CarRepository 7 | import kotlinx.coroutines.flow.Flow 8 | import javax.inject.Inject 9 | 10 | class GetNearCarsUseCase @Inject constructor(private val carRepository: CarRepository) { 11 | operator fun invoke(min: Coordinate, max: Coordinate): Flow>> { 12 | return carRepository.getNearCars(min, max) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/map/GetSearchAddressUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.map 2 | 3 | import com.gta.domain.model.LocationInfo 4 | import com.gta.domain.model.UCMCResult 5 | import com.gta.domain.repository.MapRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetSearchAddressUseCase @Inject constructor(private val mapRepository: MapRepository) { 10 | operator fun invoke(query: String): Flow>> { 11 | return mapRepository.getSearchAddressList(query) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/mypage/SetThumbnailUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.mypage 2 | 3 | import com.gta.domain.model.UCMCResult 4 | import com.gta.domain.repository.MyPageRepository 5 | import javax.inject.Inject 6 | 7 | class SetThumbnailUseCase @Inject constructor( 8 | private val repository: MyPageRepository 9 | ) { 10 | suspend operator fun invoke(uid: String, uri: String): UCMCResult = 11 | repository.setThumbnail(uid, uri) 12 | } 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/nickname/CheckNicknameStateUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.nickname 2 | 3 | import com.gta.domain.model.NicknameState 4 | import com.gta.domain.repository.NicknameRepository 5 | import javax.inject.Inject 6 | 7 | class CheckNicknameStateUseCase @Inject constructor( 8 | private val repository: NicknameRepository 9 | ) { 10 | operator fun invoke(nickname: String): NicknameState = 11 | repository.checkNicknameState(nickname) 12 | } 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/nickname/UpdateNicknameUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.nickname 2 | 3 | import com.gta.domain.model.UCMCResult 4 | import com.gta.domain.repository.NicknameRepository 5 | import javax.inject.Inject 6 | 7 | class UpdateNicknameUseCase @Inject constructor( 8 | private val repository: NicknameRepository 9 | ) { 10 | suspend operator fun invoke(uid: String, nickname: String): UCMCResult = 11 | repository.updateNickname(uid, nickname) 12 | } 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/notification/GetNotificationsInfoUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.notification 2 | 3 | import androidx.paging.PagingData 4 | import com.gta.domain.model.NotificationInfo 5 | import com.gta.domain.repository.NotificationRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetNotificationsInfoUseCase @Inject constructor( 10 | private val notificationRepository: NotificationRepository 11 | ) { 12 | operator fun invoke(userId: String): Flow> { 13 | return notificationRepository.getNotificationInfoList(userId) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/notification/SetMessageTokenUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.notification 2 | 3 | import com.gta.domain.repository.MessageTokenRepository 4 | import javax.inject.Inject 5 | 6 | class SetMessageTokenUseCase @Inject constructor(private val messageTokenRepository: MessageTokenRepository) { 7 | suspend operator fun invoke(string: String): Boolean = messageTokenRepository.setMessageToken(string) 8 | } 9 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/pinkslip/GetPinkSlipUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.pinkslip 2 | 3 | import com.gta.domain.model.PinkSlip 4 | import com.gta.domain.repository.PinkSlipRepository 5 | import java.nio.ByteBuffer 6 | import javax.inject.Inject 7 | 8 | class GetPinkSlipUseCase @Inject constructor( 9 | private val repository: PinkSlipRepository 10 | ) { 11 | operator fun invoke(buffer: ByteBuffer): PinkSlip = repository.getPinkSlip(buffer) 12 | } 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/pinkslip/SetPinkSlipUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.pinkslip 2 | 3 | import com.gta.domain.model.PinkSlip 4 | import com.gta.domain.model.UCMCResult 5 | import com.gta.domain.repository.PinkSlipRepository 6 | import javax.inject.Inject 7 | 8 | class SetPinkSlipUseCase @Inject constructor( 9 | private val repository: PinkSlipRepository 10 | ) { 11 | suspend operator fun invoke(uid: String, pinkSlip: PinkSlip): UCMCResult = 12 | repository.setPinkSlip(uid, pinkSlip) 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/reservation/CreateReservationUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.reservation 2 | 3 | import com.gta.domain.model.Notification 4 | import com.gta.domain.model.NotificationType 5 | import com.gta.domain.model.Reservation 6 | import com.gta.domain.model.UCMCResult 7 | import com.gta.domain.repository.ReservationRepository 8 | import com.gta.domain.usecase.SendNotificationUseCase 9 | import javax.inject.Inject 10 | 11 | class CreateReservationUseCase @Inject constructor( 12 | private val repository: ReservationRepository, 13 | private val notificationUseCase: SendNotificationUseCase 14 | ) { 15 | suspend operator fun invoke(reservation: Reservation): UCMCResult { 16 | return when (val result = repository.createReservation(reservation)) { 17 | is UCMCResult.Success -> { 18 | return notificationUseCase( 19 | Notification( 20 | type = NotificationType.REQUEST_RESERVATION.title, 21 | message = NotificationType.REQUEST_RESERVATION.msg, 22 | reservationId = result.data, 23 | fromId = reservation.lenderId, 24 | timestamp = System.currentTimeMillis() 25 | ), 26 | reservation.ownerId 27 | ) 28 | } 29 | is UCMCResult.Error -> { 30 | result 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/reservation/FinishReservationUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.reservation 2 | 3 | import com.gta.domain.model.FirestoreException 4 | import com.gta.domain.model.Notification 5 | import com.gta.domain.model.NotificationType 6 | import com.gta.domain.model.Reservation 7 | import com.gta.domain.model.ReservationState 8 | import com.gta.domain.model.UCMCResult 9 | import com.gta.domain.repository.ReservationRepository 10 | import com.gta.domain.usecase.SendNotificationUseCase 11 | import javax.inject.Inject 12 | 13 | class FinishReservationUseCase @Inject constructor( 14 | private val reservationRepository: ReservationRepository, 15 | private val notificationUseCase: SendNotificationUseCase 16 | ) { 17 | suspend operator fun invoke( 18 | accepted: Boolean, 19 | reservation: Reservation, 20 | reservationId: String 21 | ): UCMCResult { 22 | val notification = Notification( 23 | type = if (accepted) NotificationType.ACCEPT_RESERVATION.title else NotificationType.DECLINE_RESERVATION.title, 24 | message = if (accepted) NotificationType.ACCEPT_RESERVATION.msg else NotificationType.DECLINE_RESERVATION.msg, 25 | reservationId = reservationId, 26 | fromId = reservation.ownerId, 27 | timestamp = System.currentTimeMillis() 28 | ) 29 | 30 | return if (reservationRepository.updateReservationState( 31 | reservationId, 32 | if (accepted) ReservationState.ACCEPT else ReservationState.CANCEL 33 | ) is UCMCResult.Success 34 | ) { 35 | notificationUseCase(notification, reservation.lenderId) 36 | } else { 37 | UCMCResult.Error(FirestoreException()) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/reservation/GetCarRentInfoUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.reservation 2 | 3 | import com.gta.domain.model.CarRentInfo 4 | import com.gta.domain.model.UCMCResult 5 | import com.gta.domain.repository.CarRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetCarRentInfoUseCase @Inject constructor(private val repository: CarRepository) { 10 | operator fun invoke(carId: String): Flow> { 11 | return repository.getCarRentInfo(carId) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/reservation/GetReservationUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.reservation 2 | 3 | import com.gta.domain.model.Reservation 4 | import com.gta.domain.model.UCMCResult 5 | import com.gta.domain.repository.ReservationRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetReservationUseCase @Inject constructor( 10 | private val repository: ReservationRepository 11 | ) { 12 | operator fun invoke(reservationId: String): Flow> { 13 | return repository.getReservationInfo(reservationId) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/returncar/ReturnCarUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.returncar 2 | 3 | import com.gta.domain.model.FirestoreException 4 | import com.gta.domain.model.Notification 5 | import com.gta.domain.model.NotificationType 6 | import com.gta.domain.model.ReservationState 7 | import com.gta.domain.model.UCMCResult 8 | import com.gta.domain.repository.CarRepository 9 | import com.gta.domain.repository.ReservationRepository 10 | import com.gta.domain.usecase.SendNotificationUseCase 11 | import kotlinx.coroutines.flow.first 12 | import javax.inject.Inject 13 | 14 | class ReturnCarUseCase @Inject constructor( 15 | private val carRepository: CarRepository, 16 | private val reservationRepository: ReservationRepository, 17 | private val sendNotificationUseCase: SendNotificationUseCase 18 | ) { 19 | suspend operator fun invoke(reservationId: String, carId: String, userId: String): UCMCResult { 20 | val userResult = carRepository.getOwnerId(carId).first() 21 | val notification = Notification( 22 | type = NotificationType.RETURN_CAR.title, 23 | message = NotificationType.RETURN_CAR.msg, 24 | reservationId = reservationId, 25 | fromId = userId, 26 | timestamp = System.currentTimeMillis() 27 | ) 28 | 29 | // userId가 있고 -> 예약 상태 업데이트 성공하고 -> Notification 보내기 30 | return if (userResult is UCMCResult.Success) { 31 | val updateResult = reservationRepository.updateReservationState(reservationId, ReservationState.DONE) 32 | return if (updateResult is UCMCResult.Success) { 33 | sendNotificationUseCase(notification, userResult.data) 34 | // TODO: 트랜잭션 처리 35 | } else { 36 | updateResult 37 | } 38 | } else { 39 | UCMCResult.Error(FirestoreException()) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/review/AddReviewUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.review 2 | 3 | import com.gta.domain.model.UCMCResult 4 | import com.gta.domain.model.UserReview 5 | import com.gta.domain.repository.ReviewRepository 6 | import javax.inject.Inject 7 | 8 | class AddReviewUseCase @Inject constructor( 9 | private val repository: ReviewRepository 10 | ) { 11 | suspend operator fun invoke( 12 | opponentId: String, 13 | reservationId: String, 14 | review: UserReview 15 | ): UCMCResult = repository.addReview(opponentId, reservationId, review) 16 | } 17 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/review/GetReviewDTOUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.review 2 | 3 | import com.gta.domain.model.ReviewDTO 4 | import com.gta.domain.model.UCMCResult 5 | import com.gta.domain.repository.ReviewRepository 6 | import javax.inject.Inject 7 | 8 | class GetReviewDTOUseCase @Inject constructor( 9 | private val repository: ReviewRepository 10 | ) { 11 | suspend operator fun invoke(uid: String, reservationId: String): UCMCResult = 12 | repository.getReviewDTO(uid, reservationId) 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/transaction/GetTransactionsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.transaction 2 | 3 | import androidx.paging.PagingData 4 | import androidx.paging.filter 5 | import androidx.paging.map 6 | import com.gta.domain.model.Transaction 7 | import com.gta.domain.model.toTransaction 8 | import com.gta.domain.repository.CarRepository 9 | import com.gta.domain.repository.TransactionRepository 10 | import kotlinx.coroutines.flow.Flow 11 | import kotlinx.coroutines.flow.first 12 | import kotlinx.coroutines.flow.map 13 | import javax.inject.Inject 14 | 15 | class GetTransactionsUseCase @Inject constructor( 16 | private val carRepository: CarRepository, 17 | private val transactionRepository: TransactionRepository 18 | ) { 19 | private val tradingCondition = 20 | { transaction: Transaction -> transaction.reservationState.state >= 0 } 21 | private val completedCondition = 22 | { transaction: Transaction -> transaction.reservationState.state < 0 } 23 | 24 | operator fun invoke( 25 | uid: String, 26 | isLender: Boolean, 27 | isTrading: Boolean 28 | ): Flow> { 29 | val transactionPagingData = transactionRepository.getTransactions(uid, isLender) 30 | 31 | val filterCondition = if (isTrading) tradingCondition else completedCondition 32 | 33 | return transactionPagingData.map { pagingData -> 34 | pagingData.map { reservation -> 35 | val simpleCar = carRepository.getSimpleCar(reservation.carId).first() 36 | reservation.toTransaction(simpleCar) 37 | }.filter(filterCondition) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/user/GetUserProfileUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.user 2 | 3 | import com.gta.domain.model.UCMCResult 4 | import com.gta.domain.model.UserProfile 5 | import com.gta.domain.repository.UserRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetUserProfileUseCase @Inject constructor( 10 | private val repository: UserRepository 11 | ) { 12 | operator fun invoke(uid: String): Flow> = repository.getUserProfile(uid) 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/gta/domain/usecase/user/ReportUserUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.gta.domain.usecase.user 2 | 3 | import com.gta.domain.model.UCMCResult 4 | import com.gta.domain.repository.ReportRepository 5 | import javax.inject.Inject 6 | 7 | class ReportUserUseCase @Inject constructor( 8 | private val repository: ReportRepository 9 | ) { 10 | suspend operator fun invoke(uid: String): UCMCResult = 11 | repository.reportUser(uid) 12 | } 13 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "functions": [ 3 | { 4 | "source": "functions", 5 | "codebase": "default", 6 | "ignore": [ 7 | "node_modules", 8 | ".git", 9 | "firebase-debug.log", 10 | "firebase-debug.*.log" 11 | ], 12 | "predeploy": [ 13 | "npm --prefix \"$RESOURCE_DIR\" run lint" 14 | ] 15 | } 16 | ], 17 | "emulators": { 18 | "functions": { 19 | "port": 5001 20 | }, 21 | "ui": { 22 | "enabled": true 23 | }, 24 | "singleProjectMode": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /functions/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "root": true, 3 | "env": { 4 | es6: true, 5 | node: true, 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "google", 10 | ], 11 | "rules": { 12 | quotes: ["error", "double"], 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 2020, 16 | "sourceType": "module", 17 | "ecmaFeatures": { 18 | "jsx": true, 19 | "experimentalObjectRestSpread": true, 20 | }, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /functions/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "description": "Cloud Functions for Firebase", 4 | "scripts": { 5 | "lint": "eslint", 6 | "serve": "firebase emulators:start --only functions", 7 | "shell": "firebase functions:shell", 8 | "start": "npm run shell", 9 | "deploy": "firebase deploy --only functions", 10 | "logs": "firebase functions:log" 11 | }, 12 | "engines": { 13 | "node": "16" 14 | }, 15 | "main": "index.js", 16 | "dependencies": { 17 | "firebase": "^9.14.0", 18 | "firebase-admin": "^10.0.2", 19 | "firebase-functions": "^3.18.0" 20 | }, 21 | "devDependencies": { 22 | "eslint": "8.22.0", 23 | "eslint-config-google": "^0.14.0", 24 | "firebase-functions-test": "^0.2.0" 25 | }, 26 | "private": true 27 | } 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | android.enableJetifier=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android01-UCMC/d326bfea3c8f199fae80f046cf6f9d2b03ca46ca/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Nov 07 11:40:32 KST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /presentation/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /presentation/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.gta.buildsrc.Configuration 2 | 3 | plugins { 4 | id("com.android.library") 5 | kotlin("android") 6 | kotlin("kapt") 7 | id("androidx.navigation.safeargs.kotlin") 8 | id("dagger.hilt.android.plugin") 9 | id("kotlin-parcelize") 10 | } 11 | 12 | android { 13 | namespace = "com.gta.presentation" 14 | compileSdk = Configuration.compileSdk 15 | 16 | defaultConfig { 17 | minSdk = Configuration.minSdk 18 | targetSdk = Configuration.targetSdk 19 | 20 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 21 | consumerProguardFiles("consumer-rules.pro") 22 | } 23 | 24 | buildTypes { 25 | release { 26 | isMinifyEnabled = false 27 | setProguardFiles( 28 | listOf( 29 | getDefaultProguardFile("proguard-android-optimize.txt"), 30 | "proguard-rules.pro" 31 | ) 32 | ) 33 | } 34 | } 35 | compileOptions { 36 | sourceCompatibility = JavaVersion.VERSION_1_8 37 | targetCompatibility = JavaVersion.VERSION_1_8 38 | } 39 | kotlinOptions { 40 | jvmTarget = JavaVersion.VERSION_1_8.toString() 41 | } 42 | buildFeatures { 43 | viewBinding = true 44 | dataBinding = true 45 | } 46 | } 47 | 48 | dependencies { 49 | implementation(project(":domain")) 50 | 51 | implementation(platform(Dependencies.Libraries.Firebase.PLATFORM)) 52 | 53 | implementation(Dependencies.Libraries.presentationLibraries) 54 | kapt(Dependencies.Libraries.presentationKaptLibraries) 55 | 56 | androidTestImplementation(Dependencies.Libraries.AndroidTest.ESPRESSO_CORE) 57 | } 58 | -------------------------------------------------------------------------------- /presentation/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android01-UCMC/d326bfea3c8f199fae80f046cf6f9d2b03ca46ca/presentation/consumer-rules.pro -------------------------------------------------------------------------------- /presentation/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /presentation/src/androidTest/java/com/gta/presentation/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.gta.presentation.test", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/di/FirebaseAuthModule.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.di 2 | 3 | import com.google.firebase.auth.FirebaseAuth 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.components.SingletonComponent 8 | import java.util.Locale 9 | import javax.inject.Singleton 10 | 11 | @Module 12 | @InstallIn(SingletonComponent::class) 13 | object FirebaseAuthModule { 14 | 15 | @Singleton 16 | @Provides 17 | fun provideFirebaseAuth(): FirebaseAuth = FirebaseAuth.getInstance().apply { 18 | setLanguageCode(Locale.getDefault().language) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/di/FirebaseSigninModule.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.di 2 | 3 | import android.content.Context 4 | import com.google.android.gms.auth.api.signin.GoogleSignIn 5 | import com.google.android.gms.auth.api.signin.GoogleSignInClient 6 | import com.google.android.gms.auth.api.signin.GoogleSignInOptions 7 | import com.gta.presentation.secret.FIREBASE_CLIENT_ID 8 | import dagger.Module 9 | import dagger.Provides 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.android.components.ActivityComponent 12 | import dagger.hilt.android.qualifiers.ActivityContext 13 | 14 | @Module 15 | @InstallIn(ActivityComponent::class) 16 | class FirebaseSigninModule { 17 | @Provides 18 | fun provideGoogleSignInOptions(): GoogleSignInOptions = GoogleSignInOptions 19 | .Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) 20 | .requestIdToken(FIREBASE_CLIENT_ID) 21 | .requestEmail() 22 | .build() 23 | 24 | @Provides 25 | fun provideGoogleSignInClient( 26 | @ActivityContext context: Context, 27 | options: GoogleSignInOptions 28 | ): GoogleSignInClient = GoogleSignIn.getClient(context, options) 29 | } 30 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/di/UtilModule.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.di 2 | 3 | import android.content.Context 4 | import com.gta.presentation.util.ImageUtil 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.qualifiers.ApplicationContext 9 | import dagger.hilt.components.SingletonComponent 10 | import javax.inject.Singleton 11 | 12 | @Module 13 | @InstallIn(SingletonComponent::class) 14 | object UtilModule { 15 | 16 | @Singleton 17 | @Provides 18 | fun provideImageUtil(@ApplicationContext context: Context): ImageUtil = 19 | ImageUtil(context.contentResolver) 20 | } 21 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/model/DateType.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.model 2 | 3 | enum class DateType { 4 | RANGE, DAY_COUNT 5 | } 6 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/model/TransactionState.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.model 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | 6 | @Parcelize 7 | enum class TransactionState : Parcelable { 8 | TRADING, COMPLETED 9 | } 10 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/model/TransactionUserState.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.model 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | 6 | @Parcelize 7 | enum class TransactionUserState : Parcelable { 8 | LENDER, OWNER 9 | } 10 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/GlideModule.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui 2 | 3 | import com.bumptech.glide.module.AppGlideModule 4 | 5 | @com.bumptech.glide.annotation.GlideModule 6 | class GlideModule : AppGlideModule() 7 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.google.firebase.auth.FirebaseAuth 6 | import com.gta.domain.usecase.login.UpdateUserMessageTokenUseCase 7 | import com.gta.presentation.util.FirebaseUtil 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.flow.MutableSharedFlow 10 | import kotlinx.coroutines.flow.SharedFlow 11 | import kotlinx.coroutines.launch 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class MainViewModel @Inject constructor( 16 | private val auth: FirebaseAuth, 17 | private val updateUserMessageTokenUseCase: UpdateUserMessageTokenUseCase 18 | ) : ViewModel() { 19 | 20 | private val _changeAuthStateEvent = MutableSharedFlow() 21 | val changeAuthStateEvent: SharedFlow get() = _changeAuthStateEvent 22 | 23 | private val authStateListener by lazy { 24 | FirebaseAuth.AuthStateListener { 25 | viewModelScope.launch { 26 | _changeAuthStateEvent.emit(it.currentUser == null) 27 | } 28 | } 29 | } 30 | 31 | init { 32 | auth.addAuthStateListener(authStateListener) 33 | updateMessageToken() 34 | } 35 | 36 | private fun updateMessageToken() { 37 | viewModelScope.launch { 38 | updateUserMessageTokenUseCase(FirebaseUtil.uid) 39 | } 40 | } 41 | 42 | override fun onCleared() { 43 | auth.removeAuthStateListener(authStateListener) 44 | super.onCleared() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.base 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.viewbinding.ViewBinding 7 | 8 | abstract class BaseActivity( 9 | private val bindingFactory: (LayoutInflater) -> VB 10 | ) : AppCompatActivity() { 11 | 12 | protected lateinit var binding: VB 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | binding = bindingFactory(layoutInflater) 17 | setContentView(binding.root) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.base 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.annotation.LayoutRes 8 | import androidx.databinding.DataBindingUtil 9 | import androidx.databinding.ViewDataBinding 10 | import androidx.fragment.app.Fragment 11 | import com.google.android.material.snackbar.Snackbar 12 | import com.gta.presentation.R 13 | 14 | abstract class BaseFragment( 15 | @LayoutRes private val layoutRes: Int 16 | ) : Fragment() { 17 | 18 | private var _binding: VB? = null 19 | protected val binding get() = _binding!! 20 | 21 | override fun onCreateView( 22 | inflater: LayoutInflater, 23 | container: ViewGroup?, 24 | savedInstanceState: Bundle? 25 | ): View { 26 | _binding = DataBindingUtil.inflate(inflater, layoutRes, container, false) 27 | return binding.run { 28 | lifecycleOwner = viewLifecycleOwner 29 | root 30 | } 31 | } 32 | 33 | fun isBindingNotNull(): Boolean = _binding != null 34 | 35 | fun sendSnackBar( 36 | message: String?, 37 | @androidx.annotation.IntRange(from = -2) length: Int = Snackbar.LENGTH_SHORT, 38 | anchorView: View? = null 39 | ) { 40 | Snackbar.make( 41 | binding.root, 42 | message ?: getString(R.string.exception_not_found), 43 | length 44 | ).apply { 45 | if (anchorView != null) { 46 | this.anchorView = anchorView 47 | } 48 | }.show() 49 | } 50 | 51 | override fun onDestroyView() { 52 | super.onDestroyView() 53 | _binding = null 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/base/CameraGuideFragment.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.base 2 | 3 | import android.Manifest 4 | import android.os.Bundle 5 | import android.os.Environment 6 | import android.view.View 7 | import androidx.activity.result.contract.ActivityResultContracts 8 | import androidx.core.content.FileProvider 9 | import androidx.core.content.PermissionChecker 10 | import com.gta.presentation.R 11 | import com.gta.presentation.databinding.FragmentCameraGuideBinding 12 | import java.io.File 13 | 14 | abstract class CameraGuideFragment : BaseFragment( 15 | R.layout.fragment_camera_guide 16 | ) { 17 | 18 | abstract fun navigate(uri: String) 19 | 20 | private val permissionLauncher = 21 | registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> 22 | if (isGranted) { 23 | cameraLauncher.launch(photoUri) 24 | } 25 | } 26 | 27 | private val cameraLauncher = 28 | registerForActivityResult(ActivityResultContracts.TakePicture()) { isSucceed -> 29 | if (isSucceed) { 30 | navigate(photoUri.toString()) 31 | } 32 | } 33 | 34 | private val photoFile by lazy { 35 | File.createTempFile( 36 | "IMG_", 37 | ".jpg", 38 | requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES) 39 | ) 40 | } 41 | 42 | private val photoUri by lazy { 43 | FileProvider.getUriForFile( 44 | requireContext(), 45 | "${requireContext().packageName}.provider", 46 | photoFile 47 | ) 48 | } 49 | 50 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 51 | super.onViewCreated(view, savedInstanceState) 52 | binding.btnCameraGuidePicture.setOnClickListener { 53 | takePicture() 54 | } 55 | } 56 | 57 | private fun takePicture() { 58 | val permissionCheck = PermissionChecker.checkCallingOrSelfPermission( 59 | requireContext(), 60 | Manifest.permission.CAMERA 61 | ) 62 | if (permissionCheck == PermissionChecker.PERMISSION_GRANTED) { 63 | cameraLauncher.launch(photoUri) 64 | } else { 65 | permissionLauncher.launch(Manifest.permission.CAMERA) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/cardetail/CarImagePagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.cardetail 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.DiffUtil 7 | import androidx.recyclerview.widget.ListAdapter 8 | import androidx.recyclerview.widget.RecyclerView.ViewHolder 9 | import com.gta.presentation.R 10 | import com.gta.presentation.databinding.ItemCarDetailImageBinding 11 | 12 | class CarImagePagerAdapter : 13 | ListAdapter(ImagesDiffCallback()) { 14 | 15 | class ImageViewHolder( 16 | private val binding: ItemCarDetailImageBinding 17 | ) : ViewHolder(binding.root) { 18 | fun bind(img: String) { 19 | binding.img = img 20 | } 21 | } 22 | 23 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder { 24 | return ImageViewHolder( 25 | DataBindingUtil.inflate( 26 | LayoutInflater.from(parent.context), 27 | R.layout.item_car_detail_image, 28 | parent, 29 | false 30 | ) 31 | ) 32 | } 33 | 34 | override fun onBindViewHolder(holder: ImageViewHolder, position: Int) { 35 | holder.bind(getItem(position)) 36 | } 37 | } 38 | 39 | private class ImagesDiffCallback : DiffUtil.ItemCallback() { 40 | override fun areItemsTheSame(oldItem: String, newItem: String): Boolean { 41 | return oldItem == newItem 42 | } 43 | 44 | override fun areContentsTheSame(oldItem: String, newItem: String): Boolean { 45 | return oldItem == newItem 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/cardetail/edit/CarEditImagesAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.cardetail.edit 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.DiffUtil 7 | import androidx.recyclerview.widget.ListAdapter 8 | import androidx.recyclerview.widget.RecyclerView.ViewHolder 9 | import com.gta.presentation.R 10 | import com.gta.presentation.databinding.ItemCarEditImageBinding 11 | import com.gta.presentation.ui.cardetail.edit.CarEditImagesAdapter.ImageViewHolder 12 | 13 | class CarEditImagesAdapter : ListAdapter(ImagesDiffCallback()) { 14 | 15 | private var itemClickListener: OnItemClickListener? = null 16 | 17 | interface OnItemClickListener { 18 | fun onClick(position: Int) 19 | } 20 | 21 | fun setItemClickListener(onItemClickListener: OnItemClickListener) { 22 | itemClickListener = onItemClickListener 23 | } 24 | 25 | class ImageViewHolder( 26 | private val binding: ItemCarEditImageBinding, 27 | itemClickListener: OnItemClickListener? 28 | ) : ViewHolder(binding.root) { 29 | 30 | init { 31 | binding.ivDelete.setOnClickListener { 32 | itemClickListener?.onClick(bindingAdapterPosition) 33 | } 34 | } 35 | 36 | fun bind(img: String) { 37 | binding.img = img 38 | } 39 | } 40 | 41 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder { 42 | return ImageViewHolder( 43 | DataBindingUtil.inflate( 44 | LayoutInflater.from(parent.context), 45 | R.layout.item_car_edit_image, 46 | parent, 47 | false 48 | ), 49 | itemClickListener 50 | ) 51 | } 52 | 53 | override fun onBindViewHolder(holder: ImageViewHolder, position: Int) { 54 | holder.bind(getItem(position)) 55 | } 56 | } 57 | 58 | private class ImagesDiffCallback : DiffUtil.ItemCallback() { 59 | override fun areItemsTheSame(oldItem: String, newItem: String): Boolean { 60 | return oldItem == newItem 61 | } 62 | 63 | override fun areContentsTheSame(oldItem: String, newItem: String): Boolean { 64 | return oldItem == newItem 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/chatting/chat/ChattingViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.chatting.chat 2 | 3 | import androidx.lifecycle.SavedStateHandle 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import com.gta.domain.model.SimpleCar 7 | import com.gta.domain.usecase.car.GetSimpleCarUseCase 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import io.getstream.chat.android.client.ChatClient 10 | import kotlinx.coroutines.flow.MutableSharedFlow 11 | import kotlinx.coroutines.flow.MutableStateFlow 12 | import kotlinx.coroutines.flow.SharedFlow 13 | import kotlinx.coroutines.flow.StateFlow 14 | import kotlinx.coroutines.flow.first 15 | import kotlinx.coroutines.launch 16 | import javax.inject.Inject 17 | 18 | @HiltViewModel 19 | class ChattingViewModel @Inject constructor( 20 | args: SavedStateHandle, 21 | private val chatClient: ChatClient, 22 | private val getSimpleCarUseCase: GetSimpleCarUseCase 23 | ) : ViewModel() { 24 | 25 | private val _car = MutableStateFlow(SimpleCar()) 26 | val car: StateFlow get() = _car 27 | 28 | private val _navigateCarDetailEvent = MutableSharedFlow() 29 | val navigateCarDetailEvent: SharedFlow get() = _navigateCarDetailEvent 30 | 31 | private val cid = args.get("cid") ?: "" 32 | private val channelId = cid.substringAfterLast(":") 33 | private val carId = cid.substringAfterLast("-") 34 | 35 | init { 36 | // cid는 "ChannelType:ChannelId"이 저장되어 있어요 37 | // ChannelId를 "대여자uid-차id"로 설정해줬기 때문에 ChannelId를 이용해 채팅 화면 상단에 차 정보를 보여줄 수 있습니다. 38 | viewModelScope.launch { 39 | _car.emit(getSimpleCarUseCase(carId).first()) 40 | } 41 | } 42 | 43 | fun navigateCarDetail() { 44 | if (carId.isEmpty()) return 45 | viewModelScope.launch { 46 | _navigateCarDetailEvent.emit(carId) 47 | } 48 | } 49 | 50 | fun muteChannel() { 51 | chatClient.muteChannel( 52 | channelType = "messaging", 53 | channelId = channelId 54 | ).execute() 55 | } 56 | 57 | fun unmuteChannel() { 58 | chatClient.unmuteChannel( 59 | channelType = "messaging", 60 | channelId = channelId 61 | ).execute() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/license/guide/LicenseGuideFragment.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.license.guide 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.navigation.fragment.findNavController 6 | import com.gta.presentation.R 7 | import com.gta.presentation.ui.base.CameraGuideFragment 8 | 9 | class LicenseGuideFragment : CameraGuideFragment() { 10 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 11 | super.onViewCreated(view, savedInstanceState) 12 | binding.tvCameraGuideTitle.setText(R.string.license_guide_title) 13 | binding.ivCameraGuideSample.setImageResource(R.drawable.img_driving_license) 14 | } 15 | 16 | override fun navigate(uri: String) { 17 | val direction = LicenseGuideFragmentDirections 18 | .actionLicenseGuideFragmentToLicenseRegistrationFragment(uri) 19 | findNavController().navigate(direction) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/mypage/MyPageTermsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.mypage 2 | 3 | import android.content.res.AssetManager 4 | import android.os.Bundle 5 | import android.text.method.ScrollingMovementMethod 6 | import android.view.View 7 | import com.gta.presentation.R 8 | import com.gta.presentation.databinding.FragmentMypageTermsBinding 9 | import com.gta.presentation.ui.base.BaseFragment 10 | import java.io.BufferedReader 11 | import java.io.IOException 12 | import java.io.InputStream 13 | import java.io.InputStreamReader 14 | 15 | class MyPageTermsFragment : 16 | BaseFragment(R.layout.fragment_mypage_terms) { 17 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 18 | super.onViewCreated(view, savedInstanceState) 19 | 20 | binding.tvDescriptionServices.text = getAssetData("terms_of_service.txt") 21 | binding.tvDescriptionPrivacy.text = getAssetData("terms_of_privacy.txt") 22 | 23 | binding.tvDescriptionServices.movementMethod = ScrollingMovementMethod() 24 | binding.tvDescriptionPrivacy.movementMethod = ScrollingMovementMethod() 25 | } 26 | 27 | private fun getAssetData(fileName: String): String { 28 | val inputStream: InputStream? 29 | var result: String 30 | try { 31 | inputStream = resources.assets.open(fileName, AssetManager.ACCESS_BUFFER) 32 | val reader = BufferedReader(InputStreamReader(inputStream)) 33 | result = reader.readLines().joinToString("\n") 34 | inputStream.close() 35 | } catch (e: IOException) { 36 | result = "" 37 | } 38 | 39 | return result 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/mypage/license/MyPageLicenseViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.mypage.license 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.gta.domain.model.DrivingLicense 6 | import com.gta.domain.model.UCMCResult 7 | import com.gta.domain.usecase.license.GetLicenseFromDatabaseUseCase 8 | import com.gta.presentation.util.FirebaseUtil 9 | import com.gta.presentation.util.MutableEventFlow 10 | import dagger.hilt.android.lifecycle.HiltViewModel 11 | import kotlinx.coroutines.flow.MutableStateFlow 12 | import kotlinx.coroutines.flow.StateFlow 13 | import kotlinx.coroutines.launch 14 | import javax.inject.Inject 15 | 16 | @HiltViewModel 17 | class MyPageLicenseViewModel @Inject constructor( 18 | getLicenseFromDatabaseUseCase: GetLicenseFromDatabaseUseCase 19 | ) : ViewModel() { 20 | 21 | private val _drivingLicense = MutableStateFlow(null) 22 | val drivingLicense: StateFlow get() = _drivingLicense 23 | 24 | private val _drivingLicenseEvent = MutableEventFlow>() 25 | val drivingLicenseEvent get() = _drivingLicenseEvent 26 | 27 | init { 28 | viewModelScope.launch { 29 | when (val result = getLicenseFromDatabaseUseCase(FirebaseUtil.uid)) { 30 | is UCMCResult.Error -> { 31 | _drivingLicenseEvent.emit(result) 32 | } 33 | is UCMCResult.Success -> { 34 | _drivingLicense.emit(result.data) 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/mypage/mycars/MyCarsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.mypage.mycars 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.gta.domain.model.SimpleCar 6 | import com.gta.domain.model.UCMCResult 7 | import com.gta.domain.usecase.cardetail.GetOwnerCarsUseCase 8 | import com.gta.domain.usecase.cardetail.RemoveCarUseCase 9 | import com.gta.presentation.util.EventFlow 10 | import com.gta.presentation.util.FirebaseUtil 11 | import com.gta.presentation.util.MutableEventFlow 12 | import com.gta.presentation.util.asEventFlow 13 | import dagger.hilt.android.lifecycle.HiltViewModel 14 | import kotlinx.coroutines.flow.collectLatest 15 | import kotlinx.coroutines.launch 16 | import javax.inject.Inject 17 | 18 | @HiltViewModel 19 | class MyCarsViewModel @Inject constructor( 20 | private val getOwnerCarsUseCase: GetOwnerCarsUseCase, 21 | private val removeCarUseCase: RemoveCarUseCase 22 | ) : 23 | ViewModel() { 24 | 25 | private val _deleteEvent = MutableEventFlow>() 26 | val deleteEvent: EventFlow> get() = _deleteEvent.asEventFlow() 27 | 28 | private val _userCarEvent = MutableEventFlow>>() 29 | val userCarEvent: EventFlow>> get() = _userCarEvent.asEventFlow() 30 | 31 | private val uid = FirebaseUtil.uid 32 | 33 | init { 34 | getCarList() 35 | } 36 | 37 | fun getCarList() { 38 | viewModelScope.launch { 39 | getOwnerCarsUseCase(uid).collectLatest { result -> 40 | _userCarEvent.emit(result) 41 | } 42 | } 43 | } 44 | 45 | fun deleteCar(carId: String) { 46 | viewModelScope.launch { 47 | _deleteEvent.emit(removeCarUseCase(uid, carId)) 48 | getCarList() 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/mypage/mycars/OnItemClickListener.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.mypage.mycars 2 | 3 | import android.view.View 4 | 5 | interface OnItemClickListener { 6 | fun onClick(value: T) 7 | fun onLongClick(v: View, value: T) 8 | } 9 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/notification/NotificationListViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.notification 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import androidx.paging.PagingData 6 | import androidx.paging.cachedIn 7 | import com.gta.domain.model.NotificationInfo 8 | import com.gta.domain.usecase.notification.GetNotificationsInfoUseCase 9 | import com.gta.presentation.util.FirebaseUtil 10 | import dagger.hilt.android.lifecycle.HiltViewModel 11 | import kotlinx.coroutines.flow.Flow 12 | import kotlinx.coroutines.flow.MutableStateFlow 13 | import kotlinx.coroutines.flow.first 14 | import kotlinx.coroutines.launch 15 | import javax.inject.Inject 16 | 17 | @HiltViewModel 18 | class NotificationListViewModel @Inject constructor( 19 | getNotificationInfo: GetNotificationsInfoUseCase 20 | ) : ViewModel() { 21 | 22 | private val _notifyList = MutableStateFlow>(PagingData.empty()) 23 | val notificationList: Flow> 24 | get() = _notifyList 25 | 26 | init { 27 | viewModelScope.launch { 28 | _notifyList.value = 29 | getNotificationInfo(FirebaseUtil.uid).cachedIn(viewModelScope).first() 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/pinkslip/guide/PinkSlipGuideFragment.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.pinkslip.guide 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.navigation.fragment.findNavController 6 | import com.gta.presentation.R 7 | import com.gta.presentation.ui.base.CameraGuideFragment 8 | 9 | class PinkSlipGuideFragment : CameraGuideFragment() { 10 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 11 | super.onViewCreated(view, savedInstanceState) 12 | binding.tvCameraGuideTitle.setText(R.string.pink_slip_guide_title) 13 | binding.ivCameraGuideSample.setImageResource(R.drawable.img_pink_slip) 14 | } 15 | 16 | override fun navigate(uri: String) { 17 | val direction = PinkSlipGuideFragmentDirections 18 | .actionPinkSlipGuideFragmentToPinkSlipRegistrationFragment(uri) 19 | findNavController().navigate(direction) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/pinkslip/registration/PinkSlipRegistrationViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.pinkslip.registration 2 | 3 | import androidx.lifecycle.SavedStateHandle 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import com.google.firebase.auth.FirebaseAuth 7 | import com.gta.domain.model.PinkSlip 8 | import com.gta.domain.model.UCMCResult 9 | import com.gta.domain.usecase.pinkslip.GetPinkSlipUseCase 10 | import com.gta.domain.usecase.pinkslip.SetPinkSlipUseCase 11 | import com.gta.presentation.util.ImageUtil 12 | import com.gta.presentation.util.MutableEventFlow 13 | import com.gta.presentation.util.asEventFlow 14 | import dagger.hilt.android.lifecycle.HiltViewModel 15 | import kotlinx.coroutines.flow.MutableStateFlow 16 | import kotlinx.coroutines.flow.StateFlow 17 | import kotlinx.coroutines.launch 18 | import javax.inject.Inject 19 | 20 | @HiltViewModel 21 | class PinkSlipRegistrationViewModel @Inject constructor( 22 | private val imageUtil: ImageUtil, 23 | private val auth: FirebaseAuth, 24 | private val getPinkSlipUseCase: GetPinkSlipUseCase, 25 | private val setPinkSlipUseCase: SetPinkSlipUseCase, 26 | args: SavedStateHandle 27 | ) : ViewModel() { 28 | 29 | private val _pinkSlip = MutableStateFlow(null) 30 | val pinkSlip: StateFlow get() = _pinkSlip 31 | 32 | private val _registerEvent = MutableEventFlow>() 33 | val registerEvent get() = _registerEvent 34 | 35 | private val _pinkSlipPictureEvent = MutableEventFlow() 36 | val pinkSlipPictureEvent get() = _pinkSlipPictureEvent.asEventFlow() 37 | 38 | val pinkSlipPicture = args.get("uri") ?: "" 39 | 40 | init { 41 | viewModelScope.launch { 42 | _pinkSlipPictureEvent.emit(pinkSlipPicture) 43 | } 44 | getPinkSlip(pinkSlipPicture) 45 | } 46 | 47 | private fun getPinkSlip(uri: String) { 48 | val buffer = imageUtil.getByteBuffer(uri) ?: return 49 | viewModelScope.launch { 50 | _pinkSlip.emit(getPinkSlipUseCase(buffer)) 51 | } 52 | } 53 | 54 | fun registerPinkSlip() { 55 | val pinkSlip = pinkSlip.value ?: return 56 | val uid = auth.currentUser?.uid ?: return 57 | viewModelScope.launch { 58 | _registerEvent.emit(setPinkSlipUseCase(uid, pinkSlip)) 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/transaction/TransactionFragment.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.transaction 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.navigation.Navigation 6 | import androidx.navigation.fragment.navArgs 7 | import com.google.android.material.tabs.TabLayoutMediator 8 | import com.gta.presentation.R 9 | import com.gta.presentation.databinding.FragmentTransactionBinding 10 | import com.gta.presentation.model.TransactionUserState 11 | import com.gta.presentation.ui.MainActivity 12 | import com.gta.presentation.ui.base.BaseFragment 13 | 14 | class TransactionFragment : BaseFragment(R.layout.fragment_transaction) { 15 | 16 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 17 | super.onViewCreated(view, savedInstanceState) 18 | 19 | val args: TransactionFragmentArgs by navArgs() 20 | (requireActivity() as MainActivity).supportActionBar?.title = args.title 21 | 22 | val userState = if (args.title == getString(R.string.mypage_btn_transaction_buy)) { 23 | TransactionUserState.LENDER 24 | } else { 25 | TransactionUserState.OWNER 26 | } 27 | 28 | binding.vpTransactionListFragment.adapter = TransactionPagerAdapter(userState, this) 29 | setUpTabLayoutWithViewPager() 30 | } 31 | 32 | private fun setUpTabLayoutWithViewPager() { 33 | TabLayoutMediator(binding.tlTransactionListTabs, binding.vpTransactionListFragment) { tab, position -> 34 | val tabTitle = when (position) { 35 | 0 -> getString(R.string.trading) 36 | else -> getString(R.string.transaction_completed) 37 | } 38 | tab.text = tabTitle 39 | }.attach() 40 | } 41 | 42 | companion object { 43 | fun navigateToReservationCheck(view: View, reservationId: String) { 44 | Navigation.findNavController(view).navigate(TransactionFragmentDirections.actionTransactionFragmentToReservationCheckFragment(reservationId)) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/transaction/TransactionListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.transaction 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.paging.PagingDataAdapter 7 | import androidx.recyclerview.widget.DiffUtil 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.gta.domain.model.Transaction 10 | import com.gta.presentation.R 11 | import com.gta.presentation.databinding.ItemTransactionBinding 12 | 13 | class TransactionListAdapter : PagingDataAdapter(TransactionDiffCallback()) { 14 | 15 | class TransactionViewHolder( 16 | private val binding: ItemTransactionBinding 17 | ) : RecyclerView.ViewHolder(binding.root) { 18 | 19 | fun bind(item: Transaction) { 20 | with(binding) { 21 | this.item = item 22 | root.setOnClickListener { 23 | TransactionFragment.navigateToReservationCheck(it, item.reservationId) 24 | } 25 | executePendingBindings() 26 | } 27 | } 28 | } 29 | 30 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionViewHolder { 31 | return TransactionViewHolder( 32 | DataBindingUtil.inflate( 33 | LayoutInflater.from(parent.context), 34 | R.layout.item_transaction, 35 | parent, 36 | false 37 | ) 38 | ) 39 | } 40 | 41 | override fun onBindViewHolder(holder: TransactionViewHolder, position: Int) { 42 | getItem(position)?.let { holder.bind(it) } 43 | } 44 | } 45 | 46 | private class TransactionDiffCallback : DiffUtil.ItemCallback() { 47 | override fun areItemsTheSame(oldItem: Transaction, newItem: Transaction): Boolean { 48 | return oldItem.reservationId == newItem.reservationId 49 | } 50 | 51 | override fun areContentsTheSame(oldItem: Transaction, newItem: Transaction): Boolean { 52 | return oldItem == newItem 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/transaction/TransactionListViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.transaction 2 | 3 | import androidx.lifecycle.SavedStateHandle 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import androidx.paging.PagingData 7 | import com.gta.domain.model.Transaction 8 | import com.gta.domain.usecase.transaction.GetTransactionsUseCase 9 | import com.gta.presentation.model.TransactionState 10 | import com.gta.presentation.model.TransactionUserState 11 | import com.gta.presentation.util.FirebaseUtil 12 | import dagger.hilt.android.lifecycle.HiltViewModel 13 | import kotlinx.coroutines.flow.SharingStarted 14 | import kotlinx.coroutines.flow.StateFlow 15 | import kotlinx.coroutines.flow.stateIn 16 | import javax.inject.Inject 17 | 18 | @HiltViewModel 19 | class TransactionListViewModel @Inject constructor( 20 | args: SavedStateHandle, 21 | private val getTransactionsUseCase: GetTransactionsUseCase 22 | ) : ViewModel() { 23 | 24 | private val userState: TransactionUserState = 25 | args.get(TransactionPagerAdapter.USER_STATE_ARG) 26 | ?: TransactionUserState.LENDER 27 | private val transactionState: TransactionState = 28 | args.get(TransactionPagerAdapter.TRANSACTION_STATE_ARG) 29 | ?: TransactionState.TRADING 30 | 31 | val transaction: StateFlow> 32 | get() = getTransactionsUseCase( 33 | FirebaseUtil.uid, 34 | userState == TransactionUserState.LENDER, 35 | transactionState == TransactionState.TRADING 36 | ).stateIn( 37 | scope = viewModelScope, 38 | started = SharingStarted.WhileSubscribed(5000), 39 | initialValue = PagingData.empty() 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/ui/transaction/TransactionPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.ui.transaction 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import androidx.viewpager2.adapter.FragmentStateAdapter 6 | import com.gta.presentation.model.TransactionState 7 | import com.gta.presentation.model.TransactionUserState 8 | 9 | class TransactionPagerAdapter(private val userState: TransactionUserState, fragment: Fragment) : FragmentStateAdapter(fragment) { 10 | private val transactionStates = TransactionState.values() 11 | 12 | override fun getItemCount(): Int = transactionStates.size 13 | 14 | override fun createFragment(position: Int): Fragment { 15 | val fragment = TransactionListFragment() 16 | fragment.arguments = Bundle().apply { 17 | putParcelable(USER_STATE_ARG, userState) 18 | putParcelable(TRANSACTION_STATE_ARG, transactionStates[position]) 19 | } 20 | return fragment 21 | } 22 | 23 | companion object { 24 | const val TRANSACTION_STATE_ARG = "TRANSACTION_STATE" 25 | const val USER_STATE_ARG = "USER_STATE" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/util/DateUtil.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.util 2 | 3 | import java.text.SimpleDateFormat 4 | import java.util.* 5 | import kotlin.math.abs 6 | 7 | object DateUtil { 8 | const val DAY_TIME_UNIT = 60 * 60 * 24 * 1000L 9 | const val MINUTE_UNIT = 60 * 1000L 10 | 11 | val dateFormat = SimpleDateFormat("yy/MM/dd", Locale.getDefault()) 12 | 13 | fun getDateCount(startDate: Long, endDate: Long) = 14 | abs(endDate - startDate).div(DAY_TIME_UNIT).plus(1).toInt() 15 | } 16 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/util/DateValidator.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.util 2 | 3 | import com.google.android.material.datepicker.CalendarConstraints 4 | import com.google.android.material.datepicker.MaterialDatePicker 5 | import kotlinx.parcelize.Parcelize 6 | 7 | @Parcelize 8 | class DateValidator( 9 | private val reservationRange: Pair, 10 | private val inValidList: List>? 11 | ) : CalendarConstraints.DateValidator { 12 | 13 | override fun isValid(date: Long): Boolean { 14 | return date in reservationRange.first..reservationRange.second && date > MaterialDatePicker.todayInUtcMilliseconds() && checkInvalidList( 15 | date 16 | ) 17 | } 18 | 19 | private fun checkInvalidList(date: Long): Boolean { 20 | inValidList ?: return true 21 | 22 | for (range in inValidList) { 23 | if (date in range.first..range.second) { 24 | return false 25 | } 26 | } 27 | return true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/util/EventFlow.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.util 2 | 3 | import com.gta.presentation.util.EventFlow.Companion.DEFAULT_REPLAY 4 | import kotlinx.coroutines.flow.Flow 5 | import kotlinx.coroutines.flow.FlowCollector 6 | import kotlinx.coroutines.flow.MutableSharedFlow 7 | import java.util.concurrent.atomic.AtomicBoolean 8 | 9 | /* 10 | collect가 닫혀있을때 이벤트가 들어와서 휘발되는 것을 막아야합니다. 11 | 아직 소비되지 않은 이벤트를 캐싱하는 이벤트 플로우 입니다. 12 | */ 13 | interface EventFlow : Flow { 14 | companion object { 15 | const val DEFAULT_REPLAY = 1 16 | } 17 | } 18 | 19 | // MutableEventFlow는 Emit을 할 수 있는 FlowCollector를 상속받습니다. 20 | interface MutableEventFlow : EventFlow, FlowCollector 21 | 22 | // collect에 들어가서 소비가 잘 됐는지 확인하기 위한 consumed 변수가 정의됐습니다. 23 | private class EventFlowSlot(val value: T) { 24 | private val consumed = AtomicBoolean(false) 25 | 26 | // markConsumed가 호출되면 consumed를 리턴한 다음 consumed를 true로 바꿔줍니다. 27 | fun markConsumed() = consumed.getAndSet(true) 28 | } 29 | 30 | // emit 할 수가 없는 ReadOnlyFlow를 정의합니다. 31 | private class ReadOnlyEventFlow(flow: EventFlow) : EventFlow by flow 32 | 33 | private class EventFlowImpl(replay: Int) : MutableEventFlow { 34 | 35 | // replay 만큼 이전에 emit한 값을 가지고 있습니다. 36 | private val flow: MutableSharedFlow> = MutableSharedFlow(replay) 37 | 38 | // 새로운 collect가 연결되면 flow는 이전 값을 방출 합니다. 39 | // 이전 값이 collect가 열려 있는 상태에서 들어왔었다면 consumed는 markConsumed()에 의해 true가 됐기 때문에 이번 방출에서 emit 되지 않습니다. 40 | // 이전 값이 collect가 닫혀 있는 상태에서 들어왔었다면 consumed는 초기값 false 그대로기 때문에 이번 방출에서 emit 됩니다. 41 | override suspend fun collect(collector: FlowCollector) = 42 | flow.collect { slot -> 43 | // 한 번 markConsumed가 호출된 slot은 다음 번 방출에서는 값을 emit하지 못합니다. 44 | if (!slot.markConsumed()) { 45 | collector.emit(slot.value) 46 | } 47 | } 48 | 49 | // EventFlowSlot의 consumed를 이용해야 하기 때문에 value는 EventFlowSlot에 감싸서 넣어줍니다. 50 | override suspend fun emit(value: T) { 51 | flow.emit(EventFlowSlot(value)) 52 | } 53 | } 54 | 55 | // 사용할 MutableEventFlow와 EventFlow를 메소드 형태로 정의 해줍니다. 56 | @Suppress("FunctionName") 57 | fun MutableEventFlow(replay: Int = DEFAULT_REPLAY): MutableEventFlow = EventFlowImpl(replay) 58 | 59 | fun MutableEventFlow.asEventFlow(): EventFlow = ReadOnlyEventFlow(this) 60 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/util/FirebaseUtil.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.util 2 | 3 | import com.google.firebase.auth.FirebaseUser 4 | 5 | object FirebaseUtil { 6 | var uid = "" 7 | private set 8 | 9 | fun setUid(firebaseUser: FirebaseUser) { 10 | uid = firebaseUser.uid 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/util/ImageUtil.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.util 2 | 3 | import android.content.ContentResolver 4 | import androidx.core.net.toUri 5 | import java.nio.ByteBuffer 6 | 7 | class ImageUtil( 8 | private val contentResolver: ContentResolver 9 | ) { 10 | fun getByteBuffer(uri: String): ByteBuffer? { 11 | var byteBuffer: ByteBuffer? = null 12 | runCatching { 13 | contentResolver.openInputStream(uri.toUri()) 14 | }.onSuccess { inputStream -> 15 | inputStream?.use { 16 | byteBuffer = ByteBuffer.wrap(it.readBytes()) 17 | } 18 | } 19 | return byteBuffer 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/gta/presentation/util/RepeatOnStarted.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation.util 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import androidx.lifecycle.LifecycleOwner 5 | import androidx.lifecycle.lifecycleScope 6 | import androidx.lifecycle.repeatOnLifecycle 7 | import kotlinx.coroutines.CoroutineScope 8 | import kotlinx.coroutines.launch 9 | 10 | fun LifecycleOwner.repeatOnStarted(lifecycleOwner: LifecycleOwner, block: suspend CoroutineScope.() -> Unit) { 11 | lifecycleScope.launch { 12 | lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED, block) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/bg_bottom_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/bg_reservation_tag.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/bg_textview_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/bg_transaction_state_tag.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/edittext_style.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_arrow_right_on_background.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_bottom_nav_car.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_bottom_nav_chatting.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_bottom_nav_my.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_broken_image.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_camera.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_check.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_logo.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_mypage_car.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_mypage_deal.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_mypage_deal_inverse.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_mypage_edit.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_mypage_thumb.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_notification.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_right_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/ic_splash.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/img_driving_license.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android01-UCMC/d326bfea3c8f199fae80f046cf6f9d2b03ca46ca/presentation/src/main/res/drawable/img_driving_license.png -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/img_pink_slip.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android01-UCMC/d326bfea3c8f199fae80f046cf6f9d2b03ca46ca/presentation/src/main/res/drawable/img_pink_slip.jpg -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/selector_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 26 | 27 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/fragment_chatting_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/fragment_my_cars.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 20 | 21 | 27 | 28 | 29 | 30 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/fragment_notification.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/fragment_notification_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 17 | 18 | 25 | 26 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/fragment_transaction.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 17 | 18 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/item_car_detail_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/item_car_edit_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 16 | 17 | 30 | 31 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/item_map_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 16 | 24 | 25 | 34 | 35 | -------------------------------------------------------------------------------- /presentation/src/main/res/menu/menu_bottom_nav.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /presentation/src/main/res/menu/menu_main_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /presentation/src/main/res/menu/menu_mycars_item_click.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /presentation/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 27 | 28 | -------------------------------------------------------------------------------- /presentation/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6a94ff 4 | #a1c4ff 5 | #2a67cb 6 | 7 | #f9cb00 8 | #fffe50 9 | #c19b00 10 | 11 | #FFFFFF 12 | #000000 13 | #000000 14 | #000000 15 | 16 | #FFFFFF 17 | #FEFBFF 18 | #E4E2E6 19 | #C7C6CA 20 | #ACAAAF 21 | #5E5E62 22 | #46464A 23 | #1B1B1F 24 | #000000 25 | -------------------------------------------------------------------------------- /presentation/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8dp 4 | 16dp 5 | 24dp 6 | 32dp 7 | 8 | 57sp 9 | 45sp 10 | 36sp 11 | 12 | 32sp 13 | 28sp 14 | 24sp 15 | 16 | 22sp 17 | 16sp 18 | 14sp 19 | 20 | 14sp 21 | 12sp 22 | 11sp 23 | 24 | 16sp 25 | 14sp 26 | 12sp 27 | -------------------------------------------------------------------------------- /presentation/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 27 | 28 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /presentation/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /presentation/src/test/java/com/gta/presentation/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.gta.presentation 2 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | // Required in naver map 14 | jcenter() 15 | maven("https://naver.jfrog.io/artifactory/maven/") 16 | maven("https://jitpack.io/") 17 | } 18 | } 19 | 20 | rootProject.name = "UCMC" 21 | include(":app", ":data", ":domain", ":presentation") 22 | --------------------------------------------------------------------------------