├── .github ├── ISSUE_TEMPLATE │ ├── ✍️-템플릿.md │ ├── 🐛-템플릿.md │ └── 🤗-템플릿.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── mogakrunci.yml ├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── whyranoid │ │ └── mogakrun │ │ ├── MogakrunApplication.kt │ │ └── util │ │ └── TimberDebugTree.kt │ └── test │ └── java │ └── com │ └── whyranoid │ └── mogakrun │ └── ExampleUnitTest.kt ├── build.gradle ├── data ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── whyranoid │ │ └── data │ │ ├── account │ │ ├── AccountDataSource.kt │ │ ├── AccountDataSourceImpl.kt │ │ ├── AccountRepositoryImpl.kt │ │ ├── RunningHistoryDao.kt │ │ ├── RunningHistoryLocalDataBase.kt │ │ ├── RunningHistoryLocalDataSource.kt │ │ ├── RunningHistoryLocalDataSourceImpl.kt │ │ ├── RunningHistoryRemoteDataSource.kt │ │ ├── RunningHistoryRemoteDataSourceImpl.kt │ │ └── RunningHistoryRepositoryImpl.kt │ │ ├── constant │ │ ├── CollectionId.kt │ │ ├── Exceptions.kt │ │ └── FieldId.kt │ │ ├── di │ │ ├── AccountModule.kt │ │ ├── ConnectionModule.kt │ │ ├── CoroutineModule.kt │ │ ├── DispatchersQualifiers.kt │ │ ├── GroupModule.kt │ │ ├── NetworkModule.kt │ │ ├── PostModule.kt │ │ ├── RunningHistoryDataBaseModule.kt │ │ ├── RunningModule.kt │ │ └── UserDataBaseModule.kt │ │ ├── group │ │ ├── GroupDataSource.kt │ │ ├── GroupDataSourceImpl.kt │ │ └── GroupRepositoryImpl.kt │ │ ├── groupnotification │ │ ├── GroupNotificationDataSource.kt │ │ └── GroupNotificationDataSourceImpl.kt │ │ ├── model │ │ ├── GroupInfoResponse.kt │ │ ├── GroupNotificationResponse.kt │ │ ├── PostResponse.kt │ │ ├── RunningHistoryEntity.kt │ │ ├── RunningHistoryResponse.kt │ │ └── UserResponse.kt │ │ ├── post │ │ ├── PostDataSource.kt │ │ ├── PostDataSourceImpl.kt │ │ ├── PostPagingDataSource.kt │ │ └── PostRepositoryImpl.kt │ │ ├── running │ │ ├── NetworkRepositoryImpl.kt │ │ ├── RunnerDataSource.kt │ │ ├── RunnerDataSourceImpl.kt │ │ └── RunnerRepositoryImpl.kt │ │ └── user │ │ ├── UserDataSource.kt │ │ └── UserDataSourceImpl.kt │ └── test │ └── java │ └── com │ └── whyranoid │ └── data │ └── ExampleUnitTest.kt ├── domain ├── .gitignore ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── whyranoid │ │ └── domain │ │ ├── model │ │ ├── GroupInfo.kt │ │ ├── GroupNotification.kt │ │ ├── MoGakRunException.kt │ │ ├── Post.kt │ │ ├── Rule.kt │ │ ├── RunningHistory.kt │ │ └── User.kt │ │ ├── repository │ │ ├── AccountRepository.kt │ │ ├── GroupRepository.kt │ │ ├── NetworkRepository.kt │ │ ├── PostRepository.kt │ │ ├── RunnerRepository.kt │ │ └── RunningHistoryRepository.kt │ │ └── usecase │ │ ├── CheckIsDuplicatedGroupNameUseCase.kt │ │ ├── CreateGroupUseCase.kt │ │ ├── CreateRecruitPostUseCase.kt │ │ ├── CreateRunningPostUseCase.kt │ │ ├── DeleteGroupUseCase.kt │ │ ├── DeletePostUseCase.kt │ │ ├── ExitGroupUseCase.kt │ │ ├── FinishRunningUseCase.kt │ │ ├── GetEmailUseCase.kt │ │ ├── GetGroupInfoUseCase.kt │ │ ├── GetGroupNotificationsUseCase.kt │ │ ├── GetMyGroupListUseCase.kt │ │ ├── GetMyPagingPostsUseCase.kt │ │ ├── GetNicknameUseCase.kt │ │ ├── GetPagingPostsUseCase.kt │ │ ├── GetProfileUriUseCase.kt │ │ ├── GetRunnerCountUseCase.kt │ │ ├── GetRunningHistoryUseCase.kt │ │ ├── GetUidUseCase.kt │ │ ├── GetUnpostedRunningHistoryUseCase.kt │ │ ├── GetUserUseCase.kt │ │ ├── JoinGroupUseCase.kt │ │ ├── SaveRunningHistoryUseCase.kt │ │ ├── SignOutUseCase.kt │ │ ├── StartRunningUseCase.kt │ │ ├── UpdateGroupInfoUseCase.kt │ │ ├── UpdateNicknameUseCase.kt │ │ ├── UpdatePostRepository.kt │ │ ├── UpdateProfileUrlUseCase.kt │ │ └── WithDrawalUseCase.kt │ └── test │ ├── java │ └── com │ │ └── whyranoid │ │ └── domain │ │ └── useCase │ │ └── GetGroupInfoUseCaseTest.kt │ └── resources │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── presentation ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── whyranoid │ │ └── presentation │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── whyranoid │ │ │ └── presentation │ │ │ ├── MainActivity.kt │ │ │ ├── base │ │ │ ├── BaseActivity.kt │ │ │ └── BaseFragment.kt │ │ │ ├── community │ │ │ ├── CommunityCategory.kt │ │ │ ├── CommunityCategoryAdapter.kt │ │ │ ├── CommunityFragment.kt │ │ │ ├── CommunityItemFragment.kt │ │ │ ├── CommunityViewModel.kt │ │ │ ├── Event.kt │ │ │ ├── MyGroupAdapter.kt │ │ │ ├── PostAdapter.kt │ │ │ ├── group │ │ │ │ ├── create │ │ │ │ │ ├── CreateGroupFragment.kt │ │ │ │ │ ├── CreateGroupViewModel.kt │ │ │ │ │ └── Event.kt │ │ │ │ ├── detail │ │ │ │ │ ├── Event.kt │ │ │ │ │ ├── GroupDetailFragment.kt │ │ │ │ │ ├── GroupDetailViewModel.kt │ │ │ │ │ ├── GroupNotificationAdapter.kt │ │ │ │ │ └── GroupSettingDialog.kt │ │ │ │ └── edit │ │ │ │ │ ├── EditGroupFragment.kt │ │ │ │ │ ├── EditGroupViewModel.kt │ │ │ │ │ └── Event.kt │ │ │ └── runningpost │ │ │ │ ├── CommunityRunningHistoryAdapter.kt │ │ │ │ ├── CreateRunningPostFragment.kt │ │ │ │ ├── CreateRunningPostViewModel.kt │ │ │ │ ├── SelectRunningHistoryFragment.kt │ │ │ │ └── SelectRunningHistoryViewModel.kt │ │ │ ├── compose │ │ │ ├── DropDownMenu.kt │ │ │ ├── RulePicker.kt │ │ │ └── TopSnackBar.kt │ │ │ ├── di │ │ │ └── NetworkConnectionModule.kt │ │ │ ├── model │ │ │ ├── GroupInfoUiModel.kt │ │ │ ├── RuleUiModel.kt │ │ │ ├── RunningHistoryUiModel.kt │ │ │ ├── UiState.kt │ │ │ └── UserUiModel.kt │ │ │ ├── myrun │ │ │ ├── CalendarDayBinder.kt │ │ │ ├── MyRunFragment.kt │ │ │ ├── MyRunViewModel.kt │ │ │ ├── MyRunningHistoryAdapter.kt │ │ │ ├── SettingFragment.kt │ │ │ └── SettingViewModel.kt │ │ │ ├── running │ │ │ ├── Event.kt │ │ │ ├── RunningActivity.kt │ │ │ ├── RunningActivityObserver.kt │ │ │ ├── RunningUtil.kt │ │ │ ├── RunningViewModel.kt │ │ │ ├── RunningWorker.kt │ │ │ ├── TrackingMode.kt │ │ │ └── WorkManagerInitializer.kt │ │ │ ├── runningfinish │ │ │ ├── Event.kt │ │ │ ├── RunningFinishFragment.kt │ │ │ └── RunningFinishViewModel.kt │ │ │ ├── runningstart │ │ │ ├── Event.kt │ │ │ ├── RunningStartFragment.kt │ │ │ └── RunningStartViewModel.kt │ │ │ └── util │ │ │ ├── BindingAdapters.kt │ │ │ ├── EventFlow.kt │ │ │ ├── Extensions.kt │ │ │ ├── converters │ │ │ └── UnitConverters.kt │ │ │ ├── gpsstate │ │ │ └── GPSState.kt │ │ │ └── networkconnection │ │ │ ├── NetworkConnectionStateHolder.kt │ │ │ └── NetworkState.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── background_rounded.xml │ │ ├── bottom_navigation_color_selector.xml │ │ ├── bottom_navigation_community.xml │ │ ├── bottom_navigation_my_run.xml │ │ ├── bottom_navigation_running.xml │ │ ├── community_create_group_edit_text_background.xml │ │ ├── community_create_running_post_check.xml │ │ ├── community_create_running_post_edit_text_background.xml │ │ ├── community_create_running_post_uncheck.xml │ │ ├── done_outline_icon.xml │ │ ├── done_solid_icon.xml │ │ ├── ic_launcher_background.xml │ │ ├── kong.xml │ │ ├── my_run_edit_nick_name.xml │ │ ├── my_run_setting_account.xml │ │ ├── my_run_setting_log_out.xml │ │ ├── my_run_setting_open_source_license.xml │ │ ├── my_run_setting_privacy_policy.xml │ │ ├── my_run_setting_service_center.xml │ │ ├── my_run_setting_service_policy.xml │ │ ├── my_run_setting_whyranoider.xml │ │ ├── my_run_tool_bar_setting.xml │ │ ├── navigation_back_button.xml │ │ ├── plus_button.xml │ │ ├── radius_background.xml │ │ └── thumbnail_src_small.png │ │ ├── font │ │ ├── nanum_square_round_bold.otf │ │ ├── nanum_square_round_extra_bold.otf │ │ ├── nanum_square_round_light.otf │ │ └── nanum_square_round_regular.otf │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_running.xml │ │ ├── dialog_edit_nick_name.xml │ │ ├── fragment_community.xml │ │ ├── fragment_community_item.xml │ │ ├── fragment_create_group.xml │ │ ├── fragment_create_running_post.xml │ │ ├── fragment_edit_group.xml │ │ ├── fragment_group_detail.xml │ │ ├── fragment_my_run.xml │ │ ├── fragment_running_finish.xml │ │ ├── fragment_running_start.xml │ │ ├── fragment_select_running_history.xml │ │ ├── fragment_setting.xml │ │ ├── group_setting_dialog.xml │ │ ├── item_calendar_day.xml │ │ ├── item_recruit_post.xml │ │ ├── item_running_history.xml │ │ ├── item_running_post.xml │ │ ├── my_finish_notification_item.xml │ │ ├── my_group_item.xml │ │ ├── my_group_item_loading.xml │ │ ├── my_start_notification_item.xml │ │ ├── network_conntection_alert.xml │ │ ├── other_finish_notification_item.xml │ │ ├── other_start_notification_item.xml │ │ ├── post_item_loading.xml │ │ └── tool_bar.xml │ │ ├── menu │ │ ├── bottom_navigation_menu.xml │ │ ├── community_create_running_post_menu.xml │ │ ├── community_go_to_create_running_post_menu.xml │ │ ├── community_select_running_history_menu.xml │ │ ├── create_group_menu.xml │ │ ├── group_detail_menu.xml │ │ ├── my_group_menu.xml │ │ └── my_run_setting_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── navigation │ │ └── navigation_graph.xml │ │ ├── raw │ │ └── runing_tab_animation.json │ │ ├── values-night │ │ ├── colors.xml │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── whyranoid │ └── presentation │ └── ExampleUnitTest.kt ├── runningdata ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── whyranoid │ │ └── runningdata │ │ ├── RunningDataManager.kt │ │ └── model │ │ ├── RunningData.kt │ │ ├── RunningFinishData.kt │ │ ├── RunningHistoryModel.kt │ │ ├── RunningPosition.kt │ │ └── RunningState.kt │ └── test │ └── java │ └── com │ └── whyranoid │ └── runningdata │ └── ExampleUnitTest.kt ├── settings.gradle └── signin ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── whyranoid │ │ ├── RestoreRunningHistoryDataUseCase.kt │ │ ├── SaveLogInUserInfoUseCase.kt │ │ ├── SignInDataSource.kt │ │ ├── SignInDataSourceImpl.kt │ │ ├── SignInModule.kt │ │ ├── SignInRepository.kt │ │ ├── SignInRepositoryImpl.kt │ │ ├── SignInUserInfo.kt │ │ ├── SignInViewModel.kt │ │ └── signin │ │ └── SignInActivity.kt └── res │ ├── layout │ └── activity_sign_in.xml │ └── values │ └── strings.xml └── test └── java └── com └── whyranoid └── signin └── ExampleUnitTest.kt /.github/ISSUE_TEMPLATE/✍️-템플릿.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "✍️-템플릿" 3 | about: TODO 관련 이슈 템플릿입니다! 4 | title: '' 5 | labels: Todo 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 😆 관련 PR 11 | 12 | - 관련 PR 제목 #0 13 | 14 | ## 😲 개요 15 | 16 | - [ ] TODO 17 | - [ ] TODO 18 | - [ ] TODO 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/🐛-템플릿.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B-템플릿" 3 | about: 버그 관련 이슈 템플릿입니다! 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 🧾 버그 내용 11 | - 버그에 대한 설명을 작성해주세요. 12 | 13 | ## 💻 버그 화면 14 | - 버그가 발생하는 화면을 보여주세요. 15 | 16 | ## 🧐 의견 17 | - 의견 없음. 18 | 19 | ## 🤔 버그 발생 위치 20 | - 버그가 발생한 이슈 혹은 PR #0 21 | - 없음. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/🤗-템플릿.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F917-템플릿" 3 | about: 논의할 내용 관련 이슈 템플릿입니다! 4 | title: '' 5 | labels: 이슈 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 🥺 이슈 내용 11 | - 이슈 내용 12 | 13 | ## 🧐 의견 14 | - 의견 15 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## 😎 작업 내용 2 | - 작업 내용을 작성해주세요. 3 | 4 | ## 🧐 변경된 내용 5 | - 변경된 내용을 작성해주세요. 6 | 7 | ## 🥳 동작 화면 8 | - 동작 화면 없음 9 | 10 | ## 🤯 이슈 번호 11 | - 이슈 없음 12 | 13 | ## 🥲 비고 14 | - 비고 없음 15 | -------------------------------------------------------------------------------- /.github/workflows/mogakrunci.yml: -------------------------------------------------------------------------------- 1 | name: MoGakRun CI 2 | on: 3 | pull_request: 4 | branches: [ "main", "develop" ] 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - name: set up JDK 11 12 | uses: actions/setup-java@v3 13 | with: 14 | java-version: '11' 15 | distribution: 'zulu' 16 | cache: gradle 17 | 18 | - name: Grant execute permission for gradlew 19 | run: chmod +x gradlew 20 | 21 | - name: Create google-service 22 | run: echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > ./data/google-services.json 23 | 24 | - name: Create Local Properties 25 | run: echo '${{ secrets.LOCAL_PROPERTIES }}' > ./local.properties 26 | 27 | - name: Build with Gradle 28 | run: ./gradlew build 29 | 30 | - name: Build Debug APK 31 | run: bash ./gradlew assembleDebug --stacktrace 32 | 33 | - name: Build Release APK 34 | run: bash ./gradlew assembleRelease --stacktrace 35 | 36 | - name: 테스트용 Release APK artifact 업로드 37 | uses: actions/upload-artifact@v3 38 | with: 39 | name: app-release-unsigned.apk 40 | path: app/build/outputs/apk/release/app-release-unsigned.apk 41 | 42 | - name: 테스트용 Debug APK artifact 업로드 43 | uses: actions/upload-artifact@v3 44 | with: 45 | name: app-debug.apk 46 | path: app/build/outputs/apk/debug/app-debug.apk 47 | 48 | - uses: MeilCli/slack-upload-file@v1 49 | continue-on-error: true 50 | with: 51 | slack_token: ${{ secrets.SLACK_READ_WRITE_TOKEN }} 52 | channels: ${{ secrets.SLACK_CHANNEL_DEPLOY }} 53 | file_path: 'app/build/outputs/apk/release/app-release-unsigned.apk' 54 | file_name: 'app-release-unsigned.apk' 55 | file_type: 'apk' 56 | initial_comment: '🎁 release 버전 테스트 앱!' 57 | 58 | - uses: MeilCli/slack-upload-file@v1 59 | continue-on-error: true 60 | with: 61 | slack_token: ${{ secrets.SLACK_READ_WRITE_TOKEN }} 62 | channels: ${{ secrets.SLACK_CHANNEL_DEPLOY }} 63 | file_path: 'app/build/outputs/apk/debug/app-debug.apk' 64 | file_name: 'app-debug.apk' 65 | file_type: 'apk' 66 | initial_comment: '🙏 debug 버전 테스트 앱!' 67 | 68 | - name: action-slack 69 | uses: 8398a7/action-slack@v3.8.0 70 | with: 71 | status: ${{ job.status }} 72 | author_name: 빌드 알림 73 | env: 74 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/android,androidstudio,macos,windows,kotlin 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=android,androidstudio,macos,windows,kotlin 3 | 4 | ### Android ### 5 | # Gradle files 6 | .gradle/ 7 | build/ 8 | 9 | # Local configuration file (sdk path, etc) 10 | local.properties 11 | 12 | # Log/OS Files 13 | *.log 14 | 15 | # Android Studio generated files and folders 16 | captures/ 17 | .externalNativeBuild/ 18 | .cxx/ 19 | *.apk 20 | output.json 21 | 22 | # IntelliJ 23 | *.iml 24 | .idea/ 25 | misc.xml 26 | deploymentTargetDropDown.xml 27 | render.experimental.xml 28 | 29 | # Keystore files 30 | *.jks 31 | *.keystore 32 | 33 | # Google Services (e.g. APIs or Firebase) 34 | google-services.json 35 | 36 | # Android Profiling 37 | *.hprof 38 | 39 | ### Android Patch ### 40 | gen-external-apklibs 41 | 42 | ### Kotlin ### 43 | # Compiled class file 44 | *.class 45 | 46 | # Package Files # 47 | *.jar 48 | *.war 49 | *.nar 50 | *.ear 51 | *.zip 52 | *.tar.gz 53 | *.rar 54 | 55 | ### macOS ### 56 | # General 57 | .DS_Store 58 | .AppleDouble 59 | .LSOverride 60 | 61 | ### AndroidStudio ### 62 | 63 | # Built application files 64 | *.ap_ 65 | *.aab 66 | 67 | # Files for the ART/Dalvik VM 68 | *.dex 69 | 70 | # Generated files 71 | bin/ 72 | gen/ 73 | out/ 74 | 75 | # Gradle files 76 | .gradle 77 | 78 | # Signing files 79 | .signing/ 80 | 81 | # Proguard folder generated by Eclipse 82 | proguard/ 83 | 84 | # Android Studio 85 | /*/build/ 86 | /*/local.properties 87 | /*/out 88 | /*/*/build 89 | /*/*/production 90 | .navigation/ 91 | *.ipr 92 | *~ 93 | *.swp 94 | 95 | # User-specific configurations 96 | *.idea 97 | 98 | ## Plugin-specific files: 99 | 100 | # Crashlytics plugin (for Android Studio and IntelliJ) 101 | com_crashlytics_export_strings.xml 102 | crashlytics.properties 103 | crashlytics-build.properties 104 | fabric.properties 105 | 106 | ### AndroidStudio Patch ### 107 | 108 | !/gradle/wrapper/gradle-wrapper.jar 109 | 110 | # End of https://www.toptal.com/developers/gitignore/api/android,androidstudio,macos,windows,kotlin 111 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "org.jetbrains.kotlin.android" 4 | id "kotlin-kapt" 5 | id "dagger.hilt.android.plugin" 6 | id "com.google.firebase.crashlytics" 7 | } 8 | 9 | android { 10 | namespace "com.whyranoid.mogakrun" 11 | compileSdk 33 12 | 13 | defaultConfig { 14 | applicationId "com.whyranoid.mogakrun" 15 | minSdk 23 16 | targetSdk 33 17 | versionCode 1 18 | versionName "1.0" 19 | 20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" 27 | } 28 | } 29 | compileOptions { 30 | sourceCompatibility JavaVersion.VERSION_11 31 | targetCompatibility JavaVersion.VERSION_11 32 | } 33 | kotlinOptions { 34 | jvmTarget = "11" 35 | } 36 | buildFeatures { 37 | dataBinding true 38 | } 39 | } 40 | 41 | dependencies { 42 | 43 | implementation project(":data") 44 | implementation project(":domain") 45 | implementation project(':presentation') 46 | implementation project(":signin") 47 | 48 | implementation "androidx.core:core-ktx:$coreKtxVersion" 49 | implementation "androidx.appcompat:appcompat:$appcompatVersion" 50 | implementation "com.google.android.material:material:$materialVersion" 51 | implementation "androidx.constraintlayout:constraintlayout:$constraintlayoutVersion" 52 | testImplementation "junit:junit:$junitVersion" 53 | androidTestImplementation "androidx.test.ext:junit:$junitUiVersion" 54 | androidTestImplementation "androidx.test.espresso:espresso-core:$espressoCoreVersion" 55 | 56 | // timber 로그 57 | implementation "com.jakewharton.timber:timber:$timberVersion" 58 | 59 | // leakcanary 메모리 누수 체크 60 | debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakcanaryVersion" 61 | 62 | // Hilt 63 | implementation "com.google.dagger:hilt-android:$hiltVersion" 64 | kapt "com.google.dagger:hilt-android-compiler:$hiltVersion" 65 | 66 | // Hilt Worker with Kotlin 67 | implementation "androidx.hilt:hilt-work:$hiltWorkerVersion" 68 | kapt "androidx.hilt:hilt-compiler:$hiltCompilerVersion" 69 | 70 | // BoM for the Firebase platform 71 | implementation platform("com.google.firebase:firebase-bom:$firebaseVersion") 72 | 73 | // Crashlytics 74 | implementation "com.google.firebase:firebase-crashlytics" 75 | } -------------------------------------------------------------------------------- /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. 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 | 4 | 5 | 16 | 20 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/whyranoid/mogakrun/MogakrunApplication.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.mogakrun 2 | 3 | import android.app.Application 4 | import androidx.hilt.work.HiltWorkerFactory 5 | import androidx.work.Configuration 6 | import com.whyranoid.mogakrun.util.TimberDebugTree 7 | import dagger.hilt.android.HiltAndroidApp 8 | import timber.log.Timber 9 | import javax.inject.Inject 10 | 11 | @HiltAndroidApp 12 | class MogakrunApplication : Application(), Configuration.Provider { 13 | 14 | @Inject 15 | lateinit var workerFactory: HiltWorkerFactory 16 | 17 | override fun onCreate() { 18 | super.onCreate() 19 | 20 | if (BuildConfig.DEBUG) { 21 | Timber.plant(TimberDebugTree()) 22 | } 23 | } 24 | 25 | override fun getWorkManagerConfiguration() = 26 | Configuration.Builder() 27 | .setWorkerFactory(workerFactory) 28 | .setMinimumLoggingLevel(android.util.Log.DEBUG) 29 | .build() 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/whyranoid/mogakrun/util/TimberDebugTree.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.mogakrun.util 2 | 3 | import timber.log.Timber 4 | 5 | class TimberDebugTree : Timber.DebugTree() { 6 | override fun createStackElementTag(element: StackTraceElement): String { 7 | return "${element.fileName}:${element.lineNumber}" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /app/src/test/java/com/whyranoid/mogakrun/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.mogakrun 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | coreKtxVersion = "1.9.0" 4 | appcompatVersion = "1.5.1" 5 | materialVersion = "1.7.0" 6 | constraintlayoutVersion = "2.1.4" 7 | junitVersion = "4.13.2" 8 | junitUiVersion = "1.1.4" 9 | espressoCoreVersion = "3.5.0" 10 | firebaseVersion = "31.1.0" 11 | googleServiceVersion = "4.3.14" 12 | timberVersion = "5.0.1" 13 | leakcanaryVersion = "2.10" 14 | kotlinxCoroutinesVersion = "1.6.4" 15 | paging3Version = "3.1.1" 16 | navVersion = "2.5.3" 17 | playServicesAuthVersion = "20.4.0" 18 | hiltVersion = "2.44" 19 | lottieVersion = "5.2.0" 20 | activityKtxVersion = "1.6.1" 21 | fragmentKtxVersion = "1.5.4" 22 | lifecycleViewmodelKtxVersion = "2.5.1" 23 | splashVersion = "1.0.0" 24 | dataStoreVersion = "1.0.0" 25 | viewPager2Version = "1.0.0" 26 | shimmerVersion = "0.5.0" 27 | desugarVersion = "2.0.0" 28 | calendarVersion = "1.0.4" 29 | glideVersion = "4.14.2" 30 | roomVersion = "2.4.3" 31 | naverMapVersion = "21.0.1" 32 | naverMapVersion = "3.16.0" 33 | googleLocationVersion = "21.0.1" 34 | workManagerVersion = "2.7.1" 35 | hiltWorkerVersion = "1.0.0" 36 | hiltCompilerVersion = "1.0.0" 37 | crashlyticsVersion = "2.9.2" 38 | swipeRefreshLayoutVersion = "1.1.0" 39 | mockitoAndroidVersion = "2.24.5" 40 | mockitoInlineVersion = "3.5.13" 41 | } 42 | dependencies { 43 | classpath "com.google.gms:google-services:$googleServiceVersion" 44 | classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navVersion" 45 | classpath "com.google.firebase:firebase-crashlytics-gradle:$crashlyticsVersion" 46 | } 47 | } 48 | 49 | plugins { 50 | id "com.android.application" version "7.3.1" apply false 51 | id "com.android.library" version "7.3.1" apply false 52 | id "org.jetbrains.kotlin.android" version "1.7.20" apply false 53 | id "org.jetbrains.kotlin.jvm" version "1.7.20" apply false 54 | id "com.google.dagger.hilt.android" version "2.44" apply false 55 | } 56 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /data/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.library" 3 | id "org.jetbrains.kotlin.android" 4 | id "com.google.gms.google-services" 5 | id "kotlin-kapt" 6 | id "dagger.hilt.android.plugin" 7 | } 8 | 9 | android { 10 | namespace "com.whyranoid.data" 11 | compileSdk 33 12 | 13 | defaultConfig { 14 | minSdk 23 15 | targetSdk 33 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | consumerProguardFiles "consumer-rules.pro" 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_11 29 | targetCompatibility JavaVersion.VERSION_11 30 | } 31 | kotlinOptions { 32 | jvmTarget = "11" 33 | } 34 | } 35 | 36 | dependencies { 37 | 38 | implementation project(":domain") 39 | 40 | implementation "androidx.core:core-ktx:$coreKtxVersion" 41 | testImplementation "junit:junit:$junitVersion" 42 | 43 | // firebase 44 | implementation platform("com.google.firebase:firebase-bom:$firebaseVersion") 45 | implementation "com.google.firebase:firebase-analytics-ktx" 46 | implementation "com.google.firebase:firebase-firestore-ktx" 47 | implementation "com.google.firebase:firebase-auth-ktx" 48 | implementation "com.google.android.gms:play-services-auth:$playServicesAuthVersion" 49 | 50 | // Hilt 51 | implementation "com.google.dagger:hilt-android:$hiltVersion" 52 | kapt "com.google.dagger:hilt-android-compiler:$hiltVersion" 53 | 54 | // datastore 55 | implementation "androidx.datastore:datastore-preferences:$dataStoreVersion" 56 | implementation "androidx.datastore:datastore-preferences-core:$dataStoreVersion" 57 | 58 | // Room 59 | implementation "androidx.room:room-ktx:$roomVersion" 60 | implementation "androidx.room:room-runtime:$roomVersion" 61 | annotationProcessor "androidx.room:room-compiler:$roomVersion" 62 | kapt "androidx.room:room-compiler:$roomVersion" 63 | 64 | // Paging3 65 | implementation "androidx.paging:paging-runtime:$paging3Version" 66 | } -------------------------------------------------------------------------------- /data/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/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. 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 | 5 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/account/AccountDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.account 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | interface AccountDataSource { 6 | fun getUserNickName(): Flow 7 | fun getUserProfileImgUri(): Flow 8 | suspend fun getUserUid(): String 9 | fun getEmail(): Flow> 10 | suspend fun updateUserNickName(uid: String, newNickName: String): Result 11 | suspend fun signOut(): Result 12 | suspend fun withDrawal(): Result 13 | } 14 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/account/AccountRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.account 2 | 3 | import com.whyranoid.domain.model.User 4 | import com.whyranoid.domain.repository.AccountRepository 5 | import kotlinx.coroutines.flow.Flow 6 | import javax.inject.Inject 7 | 8 | // TODO AccountRepositoryImpl 재구현 필요!! 현재는 Fake 상태 9 | class AccountRepositoryImpl @Inject constructor( 10 | private val accountDataSource: AccountDataSource 11 | ) : AccountRepository { 12 | override suspend fun getUser(): Result { 13 | return Result.success(User("byeonghee-uid", "병희", "github.com/bngsh")) 14 | } 15 | 16 | override suspend fun loginUser(): Boolean { 17 | return true 18 | } 19 | 20 | override suspend fun getUid(): String { 21 | return accountDataSource.getUserUid() 22 | } 23 | 24 | override fun getNickname(): Flow { 25 | return accountDataSource.getUserNickName() 26 | } 27 | 28 | override fun getEmail(): Flow> { 29 | return accountDataSource.getEmail() 30 | } 31 | 32 | override suspend fun updateNickname(uid: String, newNickName: String): Result { 33 | return accountDataSource.updateUserNickName(uid, newNickName) 34 | } 35 | 36 | override fun getProfileUri(): Flow { 37 | return accountDataSource.getUserProfileImgUri() 38 | } 39 | 40 | override suspend fun updateProfileUrl(newProfileUrl: String): Boolean { 41 | return true 42 | } 43 | 44 | override suspend fun signOut(): Result { 45 | TODO("Not yet implemented") 46 | } 47 | 48 | override suspend fun withDrawal(): Result { 49 | TODO("Not yet implemented") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/account/RunningHistoryDao.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.account 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.whyranoid.data.model.RunningHistoryEntity 8 | import kotlinx.coroutines.flow.Flow 9 | 10 | @Dao 11 | interface RunningHistoryDao { 12 | @Insert(onConflict = OnConflictStrategy.REPLACE) 13 | suspend fun addRunningHistory(runningHistory: RunningHistoryEntity) 14 | 15 | @Query("SELECT * FROM running_history ORDER BY started_at DESC") 16 | fun getRunningHistory(): Flow> 17 | } 18 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/account/RunningHistoryLocalDataBase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.account 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import com.whyranoid.data.model.RunningHistoryEntity 6 | 7 | @Database( 8 | entities = [RunningHistoryEntity::class], 9 | version = 1 10 | ) 11 | abstract class RunningHistoryLocalDataBase : RoomDatabase() { 12 | abstract fun runningHistoryDao(): RunningHistoryDao 13 | } 14 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/account/RunningHistoryLocalDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.account 2 | 3 | import com.whyranoid.data.model.RunningHistoryEntity 4 | import com.whyranoid.domain.model.RunningHistory 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface RunningHistoryLocalDataSource { 8 | fun getRunningHistory(): Flow>> 9 | suspend fun saveRunningHistory(runningHistoryEntity: RunningHistoryEntity): Result 10 | } 11 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/account/RunningHistoryLocalDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.account 2 | 3 | import com.whyranoid.data.model.RunningHistoryEntity 4 | import com.whyranoid.data.model.toRunningHistory 5 | import com.whyranoid.domain.model.RunningHistory 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.map 8 | import javax.inject.Inject 9 | 10 | class RunningHistoryLocalDataSourceImpl @Inject constructor( 11 | private val runningHistoryDao: RunningHistoryDao 12 | ) : RunningHistoryLocalDataSource { 13 | 14 | override fun getRunningHistory(): Flow>> { 15 | return runningHistoryDao.getRunningHistory().map { runningHistoryList -> 16 | runCatching { 17 | runningHistoryList.map { runningHistoryEntity -> 18 | runningHistoryEntity.toRunningHistory() 19 | } 20 | } 21 | } 22 | } 23 | 24 | override suspend fun saveRunningHistory(runningHistoryEntity: RunningHistoryEntity): Result { 25 | return runCatching { 26 | runningHistoryDao.addRunningHistory(runningHistoryEntity) 27 | runningHistoryEntity.toRunningHistory() 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/account/RunningHistoryRemoteDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.account 2 | 3 | import com.whyranoid.domain.model.RunningHistory 4 | 5 | interface RunningHistoryRemoteDataSource { 6 | 7 | suspend fun uploadRunningHistory(uid: String, runningHistory: RunningHistory): Result 8 | } 9 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/account/RunningHistoryRemoteDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.account 2 | 3 | import com.google.firebase.firestore.FirebaseFirestore 4 | import com.whyranoid.data.constant.CollectionId 5 | import com.whyranoid.data.model.toRunningHistoryResponse 6 | import com.whyranoid.domain.model.RunningHistory 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.tasks.await 9 | import kotlinx.coroutines.withContext 10 | import javax.inject.Inject 11 | 12 | class RunningHistoryRemoteDataSourceImpl @Inject constructor(private val firebaseDB: FirebaseFirestore) : 13 | RunningHistoryRemoteDataSource { 14 | override suspend fun uploadRunningHistory(uid: String, runningHistory: RunningHistory): Result { 15 | val runningHistoryResponse = runningHistory.toRunningHistoryResponse(uid) 16 | var uploadSuccess = false 17 | 18 | return runCatching { 19 | withContext(Dispatchers.IO) { 20 | val task = firebaseDB.collection(CollectionId.RUNNING_HISTORY_COLLECTION) 21 | .document(runningHistoryResponse.historyId) 22 | .set(runningHistoryResponse) 23 | .addOnSuccessListener { 24 | uploadSuccess = true 25 | } 26 | .addOnFailureListener { exception -> 27 | throw exception 28 | } 29 | 30 | task.await() 31 | } 32 | uploadSuccess 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/account/RunningHistoryRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.account 2 | 3 | import com.whyranoid.data.model.RunningHistoryEntity 4 | import com.whyranoid.domain.model.RunningHistory 5 | import com.whyranoid.domain.repository.RunningHistoryRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class RunningHistoryRepositoryImpl @Inject constructor( 10 | private val runningHistoryLocalDataSource: RunningHistoryLocalDataSource, 11 | private val runningHistoryRemoteDataSource: RunningHistoryRemoteDataSource 12 | ) : RunningHistoryRepository { 13 | override fun getRunningHistory(): Flow>> { 14 | return runningHistoryLocalDataSource.getRunningHistory() 15 | } 16 | 17 | override suspend fun getUnpostedRunningHistory(): List { 18 | TODO("Not yet implemented") 19 | } 20 | 21 | override suspend fun saveRunningHistory( 22 | historyId: String, 23 | startedAt: Long, 24 | finishedAt: Long, 25 | totalRunningTime: Int, 26 | pace: Double, 27 | totalDistance: Double 28 | ): Result { 29 | return runningHistoryLocalDataSource.saveRunningHistory( 30 | RunningHistoryEntity( 31 | historyId = historyId, 32 | startedAt = startedAt, 33 | finishedAt = finishedAt, 34 | totalRunningTime = totalRunningTime, 35 | pace = pace, 36 | totalDistance = totalDistance 37 | ) 38 | ) 39 | } 40 | 41 | override suspend fun uploadRunningHistory(uid: String, runningHistory: RunningHistory): Result { 42 | return runningHistoryRemoteDataSource.uploadRunningHistory(uid, runningHistory) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/constant/CollectionId.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.constant 2 | 3 | object CollectionId { 4 | 5 | const val USERS_COLLECTION = "Users" 6 | const val RUNNERS_COLLECTION = "Runners" 7 | const val RUNNERS_ID = "runnersId" 8 | const val GROUPS_COLLECTION = "Groups" 9 | const val GROUP_NOTIFICATIONS_COLLECTION = "GroupNotifications" 10 | const val START_NOTIFICATION = "start" 11 | const val FINISH_NOTIFICATION = "finish" 12 | const val POST_COLLECTION = "Posts" 13 | const val RUNNING_HISTORY_COLLECTION = "RunningHistory" 14 | } 15 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/constant/Exceptions.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.constant 2 | 3 | object Exceptions { 4 | 5 | val TEMP_EXCEPTION = Exception("일시적인 오류가 발생하였습니다.") 6 | val NO_GROUP_EXCEPTION = Exception("해당하는 그룹이 없습니다.") 7 | val NO_USER_EXCEPTION = Exception("해당하는 유저가 없습니다.") 8 | val NO_JOINED_GROUP_EXCEPTION = Exception("가입한 그룹이 없습니다.") 9 | } 10 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/constant/FieldId.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.constant 2 | 3 | object FieldId { 4 | 5 | const val GROUP_MEMBERS_ID = "membersId" 6 | const val GROUP_ID = "groupId" 7 | const val AUTHOR_ID = "authorId" 8 | const val GROUP_NAME = "groupName" 9 | const val GROUP_INTRODUCE = "introduce" 10 | const val RULES = "rules" 11 | const val JOINED_GROUP_LIST = "joinedGroupList" 12 | const val UPDATED_AT = "updatedAt" 13 | const val RUNNING_HISTORY_ID = "runningHistoryId" 14 | } 15 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/di/AccountModule.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.di 2 | 3 | import com.whyranoid.data.account.AccountDataSource 4 | import com.whyranoid.data.account.AccountDataSourceImpl 5 | import com.whyranoid.data.account.AccountRepositoryImpl 6 | import com.whyranoid.data.account.RunningHistoryLocalDataSource 7 | import com.whyranoid.data.account.RunningHistoryLocalDataSourceImpl 8 | import com.whyranoid.data.account.RunningHistoryRemoteDataSource 9 | import com.whyranoid.data.account.RunningHistoryRemoteDataSourceImpl 10 | import com.whyranoid.data.account.RunningHistoryRepositoryImpl 11 | import com.whyranoid.domain.repository.AccountRepository 12 | import com.whyranoid.domain.repository.RunningHistoryRepository 13 | import dagger.Binds 14 | import dagger.Module 15 | import dagger.hilt.InstallIn 16 | import dagger.hilt.components.SingletonComponent 17 | 18 | @Module 19 | @InstallIn(SingletonComponent::class) 20 | abstract class AccountModule { 21 | 22 | @Binds 23 | abstract fun bindAccountRepository(accountRepositoryImpl: AccountRepositoryImpl): AccountRepository 24 | 25 | @Binds 26 | abstract fun bindAccountDataSource(accountDataSourceImpl: AccountDataSourceImpl): AccountDataSource 27 | 28 | @Binds 29 | abstract fun provideRunningHistoryRepository(runningHistoryRepositoryImpl: RunningHistoryRepositoryImpl): RunningHistoryRepository 30 | 31 | @Binds 32 | abstract fun provideRunningHistoryDataSource(runningHistoryLocalDataSourceImpl: RunningHistoryLocalDataSourceImpl): RunningHistoryLocalDataSource 33 | 34 | @Binds 35 | abstract fun bindRunningHistoryRemoteDataSource(runningHistoryRemoteDataSourceImpl: RunningHistoryRemoteDataSourceImpl): RunningHistoryRemoteDataSource 36 | } 37 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/di/ConnectionModule.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.di 2 | 3 | import android.content.Context 4 | import com.whyranoid.data.running.NetworkRepositoryImpl 5 | import com.whyranoid.domain.repository.NetworkRepository 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.android.qualifiers.ApplicationContext 10 | import dagger.hilt.components.SingletonComponent 11 | import javax.inject.Singleton 12 | 13 | @Module 14 | @InstallIn(SingletonComponent::class) 15 | class ConnectionModule { 16 | 17 | @Provides 18 | @Singleton 19 | fun provideNetworkRepository(@ApplicationContext context: Context): NetworkRepository { 20 | return NetworkRepositoryImpl(context) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/di/CoroutineModule.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.di 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import kotlinx.coroutines.Dispatchers 8 | 9 | @Module 10 | @InstallIn(SingletonComponent::class) 11 | class CoroutineModule { 12 | 13 | @Provides 14 | @IODispatcher 15 | fun provideIODispatcher() = Dispatchers.IO 16 | } 17 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/di/DispatchersQualifiers.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.di 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Retention(AnnotationRetention.RUNTIME) 6 | @Qualifier 7 | annotation class IODispatcher 8 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/di/GroupModule.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.di 2 | 3 | import com.whyranoid.data.group.GroupDataSource 4 | import com.whyranoid.data.group.GroupDataSourceImpl 5 | import com.whyranoid.data.group.GroupRepositoryImpl 6 | import com.whyranoid.data.groupnotification.GroupNotificationDataSource 7 | import com.whyranoid.data.groupnotification.GroupNotificationDataSourceImpl 8 | import com.whyranoid.data.user.UserDataSource 9 | import com.whyranoid.data.user.UserDataSourceImpl 10 | import com.whyranoid.domain.repository.GroupRepository 11 | import dagger.Binds 12 | import dagger.Module 13 | import dagger.hilt.InstallIn 14 | import dagger.hilt.components.SingletonComponent 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | abstract class GroupModule { 19 | 20 | @Binds 21 | abstract fun bindGroupDataSource(groupDataSourceImpl: GroupDataSourceImpl): GroupDataSource 22 | 23 | @Binds 24 | abstract fun bindUserDataSource(userDataSourceImpl: UserDataSourceImpl): UserDataSource 25 | 26 | @Binds 27 | abstract fun bindGroupNotificationDataSource(groupNotificationDataSourceImpl: GroupNotificationDataSourceImpl): GroupNotificationDataSource 28 | 29 | @Binds 30 | abstract fun bindGroupRepository(groupRepositoryImpl: GroupRepositoryImpl): GroupRepository 31 | } 32 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.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 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 NetworkModule { 15 | 16 | @Provides 17 | @Singleton 18 | fun provideFireStoreDatabase(): FirebaseFirestore = 19 | Firebase.firestore 20 | } 21 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/di/PostModule.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.di 2 | 3 | import com.whyranoid.data.post.PostDataSource 4 | import com.whyranoid.data.post.PostDataSourceImpl 5 | import com.whyranoid.data.post.PostRepositoryImpl 6 | import com.whyranoid.domain.repository.PostRepository 7 | import dagger.Binds 8 | import dagger.Module 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.components.SingletonComponent 11 | 12 | @Module 13 | @InstallIn(SingletonComponent::class) 14 | abstract class PostModule { 15 | 16 | @Binds 17 | abstract fun bindPostDataSource(postDataSourceImpl: PostDataSourceImpl): PostDataSource 18 | 19 | @Binds 20 | abstract fun bindPostRepository(postRepositoryImpl: PostRepositoryImpl): PostRepository 21 | } 22 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/di/RunningHistoryDataBaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.di 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import com.whyranoid.data.account.RunningHistoryDao 6 | import com.whyranoid.data.account.RunningHistoryLocalDataBase 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | object RunningHistoryDataBaseModule { 17 | @Singleton 18 | @Provides 19 | fun provideRoomDataBase( 20 | @ApplicationContext appContext: Context 21 | ): RunningHistoryLocalDataBase = Room.databaseBuilder( 22 | appContext, 23 | RunningHistoryLocalDataBase::class.java, 24 | "mogakrun_running_history.db" 25 | ) 26 | .build() 27 | 28 | @Singleton 29 | @Provides 30 | fun provideRunningHistoryDao(runningHistoryLocalDataBase: RunningHistoryLocalDataBase): RunningHistoryDao = 31 | runningHistoryLocalDataBase.runningHistoryDao() 32 | } 33 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/di/RunningModule.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.di 2 | 3 | import com.google.firebase.firestore.FirebaseFirestore 4 | import com.whyranoid.data.running.RunnerDataSource 5 | import com.whyranoid.data.running.RunnerDataSourceImpl 6 | import com.whyranoid.data.running.RunnerRepositoryImpl 7 | import com.whyranoid.domain.repository.RunnerRepository 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 | class RunningModule { 17 | 18 | @Provides 19 | @Singleton 20 | fun provideRunningDataSource(db: FirebaseFirestore): RunnerDataSource { 21 | return RunnerDataSourceImpl(db) 22 | } 23 | 24 | @Provides 25 | @Singleton 26 | fun provideRunningRepository(runnerDataSource: RunnerDataSource): RunnerRepository { 27 | return RunnerRepositoryImpl(runnerDataSource) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/di/UserDataBaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.di 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.preferences.core.PreferenceDataStoreFactory 6 | import androidx.datastore.preferences.core.Preferences 7 | import androidx.datastore.preferences.preferencesDataStoreFile 8 | import com.google.firebase.auth.FirebaseAuth 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.android.qualifiers.ApplicationContext 13 | import dagger.hilt.components.SingletonComponent 14 | import javax.inject.Singleton 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object UserDataBaseModule { 19 | 20 | private const val USER_PREFERENCES = "user_preferences" 21 | 22 | @Singleton 23 | @Provides 24 | fun providePreferencesDataStore(@ApplicationContext appContext: Context): DataStore = 25 | PreferenceDataStoreFactory.create( 26 | produceFile = { 27 | appContext.preferencesDataStoreFile(USER_PREFERENCES) 28 | } 29 | ) 30 | 31 | @Singleton 32 | @Provides 33 | fun provideFirebaseAuth(): FirebaseAuth = FirebaseAuth.getInstance() 34 | } 35 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/group/GroupDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.group 2 | 3 | import com.whyranoid.domain.model.GroupInfo 4 | import com.whyranoid.domain.model.Rule 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface GroupDataSource { 8 | 9 | suspend fun updateGroupInfo( 10 | groupId: String, 11 | groupName: String, 12 | groupIntroduce: String, 13 | rules: List 14 | ): Boolean 15 | 16 | suspend fun joinGroup(uid: String, groupId: String): Boolean 17 | 18 | suspend fun exitGroup(uid: String, groupId: String): Boolean 19 | 20 | suspend fun createGroup( 21 | groupName: String, 22 | introduce: String, 23 | rules: List, 24 | uid: String 25 | ): Boolean 26 | 27 | suspend fun deleteGroup( 28 | uid: String, 29 | groupId: String 30 | ): Boolean 31 | 32 | fun getGroupInfoFlow(uid: String, groupId: String): Flow 33 | 34 | suspend fun isDuplicatedGroupName(groupName: String): Boolean 35 | } 36 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/groupnotification/GroupNotificationDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.groupnotification 2 | 3 | import com.whyranoid.domain.model.GroupNotification 4 | import com.whyranoid.domain.model.RunningHistory 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface GroupNotificationDataSource { 8 | 9 | fun getGroupNotifications(groupId: String): Flow> 10 | 11 | suspend fun notifyRunningStart(uid: String, groupIdList: List) 12 | 13 | suspend fun notifyRunningFinish( 14 | uid: String, 15 | runningHistory: RunningHistory, 16 | groupIdList: List 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/model/GroupInfoResponse.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.model 2 | 3 | import com.whyranoid.domain.model.GroupInfo 4 | import com.whyranoid.domain.model.Rule 5 | import com.whyranoid.domain.model.User 6 | 7 | data class GroupInfoResponse( 8 | val groupId: String = "", 9 | val groupName: String = "", 10 | val introduce: String = "", 11 | val leaderId: String = "", 12 | val membersId: List = emptyList(), 13 | val rules: List = emptyList() 14 | ) 15 | 16 | fun GroupInfoResponse.toGroupInfo(leader: User, rules: List) = 17 | GroupInfo( 18 | name = this.groupName, 19 | groupId = this.groupId, 20 | introduce = this.introduce, 21 | rules = rules, 22 | headCount = this.membersId.size, 23 | leader = leader 24 | ) 25 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/model/GroupNotificationResponse.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.model 2 | 3 | import com.whyranoid.domain.model.StartNotification 4 | 5 | sealed interface GroupNotificationResponse { 6 | val uid: String 7 | } 8 | 9 | data class StartNotificationResponse( 10 | override val uid: String = "", 11 | val startedAt: Long = 0L 12 | ) : GroupNotificationResponse 13 | 14 | data class FinishNotificationResponse( 15 | override val uid: String = "", 16 | val historyId: String = "" 17 | ) : GroupNotificationResponse 18 | 19 | fun StartNotificationResponse.toStartNotification() = 20 | StartNotification( 21 | uid = this.uid, 22 | startedAt = this.startedAt 23 | ) 24 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/model/PostResponse.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.model 2 | 3 | sealed interface PostResponse { 4 | val postId: String 5 | val authorId: String 6 | val updatedAt: Long 7 | } 8 | 9 | data class RecruitPostResponse( 10 | override val authorId: String = "", 11 | val groupId: String = "", 12 | override val postId: String = "", 13 | override val updatedAt: Long = 0L 14 | ) : PostResponse 15 | 16 | data class RunningPostResponse( 17 | override val postId: String = "", 18 | override val authorId: String = "", 19 | override val updatedAt: Long = 0L, 20 | val runningHistoryId: String = "", 21 | val content: String = "" 22 | ) : PostResponse 23 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/model/RunningHistoryEntity.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.model 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import com.whyranoid.domain.model.RunningHistory 7 | 8 | @Entity(tableName = "running_history") 9 | data class RunningHistoryEntity( 10 | @PrimaryKey 11 | val historyId: String, 12 | @ColumnInfo(name = "started_at") val startedAt: Long, 13 | @ColumnInfo(name = "finished_at") val finishedAt: Long, 14 | @ColumnInfo(name = "total_running_time") val totalRunningTime: Int, 15 | @ColumnInfo(name = "pace") val pace: Double, 16 | @ColumnInfo(name = "total_distance") val totalDistance: Double 17 | ) 18 | 19 | fun RunningHistoryEntity.toRunningHistory(): RunningHistory { 20 | return RunningHistory( 21 | historyId = historyId, 22 | startedAt = startedAt, 23 | finishedAt = finishedAt, 24 | totalRunningTime = totalRunningTime, 25 | pace = pace, 26 | totalDistance = totalDistance 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/model/RunningHistoryResponse.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.model 2 | 3 | import com.whyranoid.domain.model.RunningHistory 4 | 5 | data class RunningHistoryResponse( 6 | val uid: String = "", 7 | val historyId: String = "", 8 | val startedAt: Long = 0L, 9 | val finishedAt: Long = 0L, 10 | val totalRunningTime: Int = 0, 11 | val pace: Double = 0.0, 12 | val totalDistance: Double = 0.0 13 | ) 14 | 15 | fun RunningHistory.toRunningHistoryResponse(uid: String) = 16 | RunningHistoryResponse( 17 | uid = uid, 18 | historyId = historyId, 19 | startedAt = startedAt, 20 | finishedAt = finishedAt, 21 | totalRunningTime = totalRunningTime, 22 | pace = pace, 23 | totalDistance = totalDistance 24 | ) 25 | 26 | fun RunningHistoryResponse.toRunningHistoryEntity() = 27 | RunningHistoryEntity( 28 | historyId = historyId, 29 | startedAt = startedAt, 30 | finishedAt = finishedAt, 31 | totalRunningTime = totalRunningTime, 32 | pace = pace, 33 | totalDistance = totalDistance 34 | ) 35 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/model/UserResponse.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.model 2 | 3 | import com.whyranoid.domain.model.User 4 | 5 | data class UserResponse( 6 | val uid: String = "", 7 | val name: String? = "", 8 | val profileUrl: String? = "", 9 | val joinedGroupList: List = emptyList() 10 | ) 11 | 12 | fun UserResponse.toUser() = 13 | User( 14 | uid = this.uid, 15 | name = this.name, 16 | profileUrl = this.profileUrl 17 | ) 18 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/post/PostDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.post 2 | 3 | import com.google.firebase.firestore.DocumentSnapshot 4 | import com.google.firebase.firestore.QueryDocumentSnapshot 5 | import com.google.firebase.firestore.QuerySnapshot 6 | import com.whyranoid.domain.model.Post 7 | 8 | interface PostDataSource { 9 | 10 | suspend fun getCurrentPagingPost(key: QuerySnapshot?): QuerySnapshot 11 | 12 | suspend fun getNextPagingPost(lastDocumentSnapshot: DocumentSnapshot): QuerySnapshot 13 | 14 | suspend fun getMyCurrentPagingPost(key: QuerySnapshot?, uid: String): QuerySnapshot 15 | 16 | suspend fun getMyNextPagingPost( 17 | lastDocumentSnapshot: DocumentSnapshot, 18 | uid: String 19 | ): QuerySnapshot 20 | 21 | suspend fun convertPostType(document: QueryDocumentSnapshot): Result 22 | 23 | suspend fun createRecruitPost( 24 | authorUid: String, 25 | groupUid: String 26 | ): Boolean 27 | 28 | suspend fun createRunningPost( 29 | authorUid: String, 30 | runningHistoryId: String, 31 | content: String 32 | ): Result 33 | 34 | suspend fun deletePost(postId: String): Boolean 35 | } 36 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/post/PostPagingDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.post 2 | 3 | import androidx.paging.PagingSource 4 | import androidx.paging.PagingState 5 | import com.google.firebase.firestore.QuerySnapshot 6 | import com.whyranoid.domain.model.Post 7 | 8 | class PostPagingDataSource( 9 | private val myUid: String = EMPTY_STRING, 10 | private val postDataSource: PostDataSource 11 | ) : PagingSource() { 12 | 13 | override fun getRefreshKey(state: PagingState): QuerySnapshot? { 14 | // 새로 고침하면 데이터를 처음부터 로드 15 | return null 16 | } 17 | 18 | override suspend fun load(params: LoadParams): LoadResult { 19 | return try { 20 | val postList = mutableListOf() 21 | 22 | // 현재 페이지 23 | val currentPage = 24 | if (myUid == EMPTY_STRING) { 25 | postDataSource.getCurrentPagingPost(params.key) 26 | } else { 27 | postDataSource.getMyCurrentPagingPost(params.key, myUid) 28 | } 29 | 30 | // Post 타입 캐스팅 31 | // TODO 예외 처리 32 | currentPage.forEach { document -> 33 | postDataSource.convertPostType(document).onSuccess { post -> 34 | postList.add(post) 35 | } 36 | } 37 | 38 | // 마지막 스냅샷 저장 39 | val lastDocumentSnapshot = currentPage.documents[currentPage.size() - 1] 40 | 41 | // 마지막 스냅샷 이후 페이지 불러오기 42 | val nextPage = 43 | if (myUid == EMPTY_STRING) { 44 | postDataSource.getNextPagingPost(lastDocumentSnapshot) 45 | } else { 46 | postDataSource.getMyNextPagingPost(lastDocumentSnapshot, myUid) 47 | } 48 | 49 | LoadResult.Page( 50 | data = postList, 51 | prevKey = null, 52 | nextKey = nextPage 53 | ) 54 | } catch (e: Exception) { 55 | LoadResult.Error(e) 56 | } 57 | } 58 | 59 | companion object { 60 | private const val EMPTY_STRING = "" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/post/PostRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.post 2 | 3 | import androidx.paging.Pager 4 | import androidx.paging.PagingConfig 5 | import androidx.paging.PagingData 6 | import androidx.paging.cachedIn 7 | import com.whyranoid.domain.model.Post 8 | import com.whyranoid.domain.repository.PostRepository 9 | import kotlinx.coroutines.CoroutineScope 10 | import kotlinx.coroutines.flow.Flow 11 | import javax.inject.Inject 12 | 13 | class PostRepositoryImpl @Inject constructor( 14 | private val postDataSource: PostDataSource 15 | ) : PostRepository { 16 | 17 | // TODO : 캐싱하기 18 | override fun getPagingPosts(coroutineScope: CoroutineScope): Flow> { 19 | return Pager( 20 | PagingConfig(pageSize = 5) 21 | ) { 22 | PostPagingDataSource(postDataSource = postDataSource) 23 | }.flow.cachedIn(coroutineScope) 24 | } 25 | 26 | override fun getMyPagingPosts(uid: String, coroutineScope: CoroutineScope): Flow> { 27 | return Pager( 28 | PagingConfig(pageSize = 5) 29 | ) { 30 | PostPagingDataSource(myUid = uid, postDataSource) 31 | }.flow.cachedIn(coroutineScope) 32 | } 33 | 34 | override suspend fun createRunningPost( 35 | authorUid: String, 36 | runningHistoryId: String, 37 | content: String 38 | ): Result { 39 | return postDataSource.createRunningPost(authorUid, runningHistoryId, content) 40 | } 41 | 42 | override suspend fun createRecruitPost( 43 | authorUid: String, 44 | groupUid: String 45 | ): Boolean { 46 | return postDataSource.createRecruitPost(authorUid, groupUid) 47 | } 48 | 49 | override suspend fun deletePost(postId: String): Boolean { 50 | return postDataSource.deletePost(postId) 51 | } 52 | 53 | override suspend fun updatePost(postId: String, postContent: String, updatedAt: Long): Boolean { 54 | TODO("Not yet implemented") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/running/RunnerDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.running 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | interface RunnerDataSource { 6 | 7 | fun getCurrentRunnerCount(): Flow> 8 | 9 | suspend fun startRunning(uid: String): Boolean 10 | 11 | suspend fun finishRunning(uid: String): Boolean 12 | } 13 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/running/RunnerDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.running 2 | 3 | import com.google.firebase.firestore.FieldValue 4 | import com.google.firebase.firestore.FirebaseFirestore 5 | import com.whyranoid.data.constant.CollectionId 6 | import com.whyranoid.domain.model.MoGakRunException 7 | import kotlinx.coroutines.channels.awaitClose 8 | import kotlinx.coroutines.flow.Flow 9 | import kotlinx.coroutines.flow.callbackFlow 10 | import kotlinx.coroutines.suspendCancellableCoroutine 11 | import kotlin.coroutines.resume 12 | 13 | class RunnerDataSourceImpl(private val db: FirebaseFirestore) : RunnerDataSource { 14 | 15 | override fun getCurrentRunnerCount(): Flow> = callbackFlow { 16 | val registration = db.collection(CollectionId.RUNNERS_COLLECTION) 17 | .document(CollectionId.RUNNERS_ID) 18 | .addSnapshotListener { snapshot, _ -> 19 | trySend(runCatching { 20 | snapshot?.data?.size 21 | ?: throw MoGakRunException.FileNotFoundedException 22 | }) 23 | } 24 | 25 | awaitClose { 26 | registration.remove() 27 | } 28 | } 29 | 30 | override suspend fun startRunning(uid: String): Boolean { 31 | if (uid.isBlank()) return false 32 | return suspendCancellableCoroutine { continuation -> 33 | db.collection(CollectionId.RUNNERS_COLLECTION) 34 | .document(CollectionId.RUNNERS_ID) 35 | .update(uid, uid) 36 | .addOnSuccessListener { 37 | continuation.resume(true) 38 | } 39 | .addOnFailureListener { 40 | continuation.resume(false) 41 | } 42 | } 43 | } 44 | 45 | override suspend fun finishRunning(uid: String): Boolean { 46 | if (uid.isBlank()) return false 47 | return suspendCancellableCoroutine { continuation -> 48 | db.collection(CollectionId.RUNNERS_COLLECTION) 49 | .document(CollectionId.RUNNERS_ID) 50 | .update(uid, FieldValue.delete()) 51 | .addOnSuccessListener { 52 | continuation.resume(true) 53 | } 54 | .addOnFailureListener { 55 | continuation.resume(false) 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/running/RunnerRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.running 2 | 3 | import com.whyranoid.domain.repository.RunnerRepository 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | class RunnerRepositoryImpl(private val runnerDataSource: RunnerDataSource) : RunnerRepository { 7 | override fun getCurrentRunnerCount(): Flow> { 8 | return runnerDataSource.getCurrentRunnerCount() 9 | } 10 | 11 | override suspend fun startRunning(uid: String): Boolean { 12 | return runnerDataSource.startRunning(uid) 13 | } 14 | 15 | override suspend fun finishRunning(uid: String): Boolean { 16 | return runnerDataSource.finishRunning(uid) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /data/src/main/java/com/whyranoid/data/user/UserDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data.user 2 | 3 | import com.whyranoid.domain.model.GroupInfo 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface UserDataSource { 7 | 8 | suspend fun getMyGroupList(uid: String): Result> 9 | 10 | fun getMyGroupListFlow(uid: String): Flow>> 11 | } 12 | -------------------------------------------------------------------------------- /data/src/test/java/com/whyranoid/data/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.data 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /domain/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "java-library" 3 | id "org.jetbrains.kotlin.jvm" 4 | } 5 | 6 | java { 7 | sourceCompatibility = JavaVersion.VERSION_11 8 | targetCompatibility = JavaVersion.VERSION_11 9 | } 10 | 11 | dependencies { 12 | // Inject 13 | implementation "javax.inject:javax.inject:1" 14 | 15 | // Coroutine 16 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion" 17 | // coroutineTest 18 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutinesVersion" 19 | 20 | // PagingData - Common 21 | implementation "androidx.paging:paging-common-ktx:$paging3Version" 22 | testImplementation "androidx.paging:paging-common:$paging3Version" 23 | 24 | // junit 25 | testImplementation "junit:junit:$junitVersion" 26 | 27 | // mockito 28 | testImplementation ("org.mockito:mockito-android:$mockitoAndroidVersion") 29 | // mockito - kotlin 30 | testImplementation "org.mockito:mockito-inline:$mockitoInlineVersion" 31 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/model/GroupInfo.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.model 2 | 3 | data class GroupInfo( 4 | val name: String, 5 | val groupId: String, 6 | val introduce: String, 7 | val rules: List, 8 | val headCount: Int, 9 | val leader: User 10 | ) 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/model/GroupNotification.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.model 2 | 3 | sealed interface GroupNotification { 4 | val uid: String 5 | } 6 | 7 | data class StartNotification( 8 | override val uid: String, 9 | val startedAt: Long 10 | ) : GroupNotification 11 | 12 | data class FinishNotification( 13 | override val uid: String, 14 | val runningHistory: RunningHistory 15 | ) : GroupNotification 16 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/model/MoGakRunException.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.model 2 | 3 | sealed class MoGakRunException : Exception() { 4 | 5 | object NetworkFailureException : MoGakRunException() 6 | 7 | object FileNotFoundedException : MoGakRunException() 8 | 9 | object OtherException : MoGakRunException() 10 | } 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/model/Post.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.model 2 | 3 | sealed interface Post { 4 | val postId: String 5 | val author: User 6 | val updatedAt: Long 7 | } 8 | 9 | data class RecruitPost( 10 | override val postId: String, 11 | override val author: User, 12 | override val updatedAt: Long, 13 | val groupInfo: GroupInfo 14 | ) : Post 15 | 16 | data class RunningPost( 17 | override val postId: String, 18 | override val author: User, 19 | override val updatedAt: Long, 20 | val runningHistory: RunningHistory, 21 | val likeCount: Int, 22 | val content: String 23 | ) : Post 24 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/model/Rule.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.model 2 | 3 | data class Rule( 4 | val dayOfWeek: DayOfWeek, 5 | val hour: Int, 6 | val minute: Int 7 | ) { 8 | override fun toString(): String { 9 | return "${dayOfWeek.dayResId}-$hour-$minute" 10 | } 11 | } 12 | 13 | fun String.toRule(): Rule { 14 | val ruleString = this.split("-") 15 | return Rule( 16 | dayOfWeek = DayOfWeek.values().find { it.dayResId == ruleString[0] } ?: DayOfWeek.SUN, 17 | hour = ruleString[1].toInt(), 18 | minute = ruleString[2].toInt() 19 | ) 20 | } 21 | 22 | enum class DayOfWeek(val dayResId: String) { 23 | MON("월"), 24 | TUE("화"), 25 | WED("수"), 26 | THU("목"), 27 | FRI("금"), 28 | SAT("토"), 29 | SUN("일") 30 | } 31 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/model/RunningHistory.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.model 2 | 3 | data class RunningHistory( 4 | val historyId: String = "", 5 | val startedAt: Long = 0L, 6 | val finishedAt: Long = 0L, 7 | val totalRunningTime: Int = 0, 8 | val pace: Double = 0.0, 9 | val totalDistance: Double = 0.0 10 | ) 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/model/User.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.model 2 | 3 | data class User( 4 | val uid: String, 5 | val name: String?, 6 | val profileUrl: String? 7 | ) 8 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/repository/AccountRepository.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.repository 2 | 3 | import com.whyranoid.domain.model.User 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface AccountRepository { 7 | 8 | // 로컬에서 유저 정보 가져오기 9 | suspend fun getUser(): Result 10 | 11 | // 파이어베이스에서 uid, 닉네임, 프로필사진 가져오기 12 | suspend fun loginUser(): Boolean 13 | 14 | // 데이터스토어에서 uid 가져오기 15 | suspend fun getUid(): String 16 | 17 | // 데이터스토어에서 닉네임 가져오기 18 | fun getNickname(): Flow 19 | 20 | // 데이터스토어에서 이메일 가져오기 21 | fun getEmail(): Flow> 22 | 23 | // 닉네임 수정, 서버에 먼저 보내고 성공하면 로컬에 반영 24 | // 실패하면 실패 사용자에게 알리기 25 | suspend fun updateNickname(uid: String, newNickName: String): Result 26 | 27 | // 데이터스토어에서 프로필 이미지 가져오기 28 | fun getProfileUri(): Flow 29 | 30 | // 프로필 사진 서버에 업데이트 31 | suspend fun updateProfileUrl(newProfileUrl: String): Boolean 32 | 33 | // 로그아웃 34 | suspend fun signOut(): Result 35 | 36 | // 회원탈퇴 37 | suspend fun withDrawal(): Result 38 | } 39 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/repository/GroupRepository.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.repository 2 | 3 | import com.whyranoid.domain.model.GroupInfo 4 | import com.whyranoid.domain.model.GroupNotification 5 | import com.whyranoid.domain.model.Rule 6 | import com.whyranoid.domain.model.RunningHistory 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | interface GroupRepository { 10 | 11 | suspend fun getMyGroupList(uid: String): Result> 12 | 13 | fun getMyGroupListFlow(uid: String): Flow>> 14 | 15 | // 그룹 정보 수정, 홍보 글 수정 16 | suspend fun updateGroupInfo( 17 | groupId: String, 18 | groupName: String, 19 | groupIntroduce: String, 20 | rules: List 21 | ): Boolean 22 | 23 | // 서버에는 해당 그룹에 내 정보 넘겨주기 24 | suspend fun joinGroup(uid: String, groupId: String): Boolean 25 | 26 | // 그룹 나가기 / 그룹에서 먼저 나간 후 성공하면 User 에 반영하기 27 | suspend fun exitGroup(uid: String, groupId: String): Boolean 28 | 29 | // 그룹의 정보를 Flow로 가져오기 30 | fun getGroupInfoFlow(uid: String, groupId: String): Flow 31 | 32 | // 혹은 그룹 채팅을 가져오기 + 글 작성하기 33 | fun getGroupNotifications(groupId: String): Flow> 34 | 35 | suspend fun notifyRunningStart(uid: String, groupIdList: List) 36 | 37 | suspend fun notifyRunningFinish( 38 | uid: String, 39 | runningHistory: RunningHistory, 40 | groupIdList: List 41 | ) 42 | 43 | // 그룹 생성하기 44 | suspend fun createGroup( 45 | groupName: String, 46 | introduce: String, 47 | rules: List, 48 | uid: String 49 | ): Boolean 50 | 51 | suspend fun deleteGroup( 52 | uid: String, 53 | groupId: String 54 | ): Boolean 55 | 56 | suspend fun isDuplicatedGroupName(groupName: String): Boolean 57 | } 58 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/repository/NetworkRepository.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.repository 2 | 3 | import kotlinx.coroutines.flow.StateFlow 4 | 5 | interface NetworkRepository { 6 | 7 | fun getNetworkConnectionState(): StateFlow 8 | 9 | fun addNetworkConnectionCallback() 10 | 11 | fun removeNetworkConnectionCallback() 12 | } 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/repository/PostRepository.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.repository 2 | 3 | import androidx.paging.PagingData 4 | import com.whyranoid.domain.model.Post 5 | import kotlinx.coroutines.CoroutineScope 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | interface PostRepository { 9 | 10 | // 글(홍보 / 인증) 페이징으로 가져오기 - 리모트 11 | fun getPagingPosts(coroutineScope: CoroutineScope): Flow> 12 | 13 | fun getMyPagingPosts(uid: String, coroutineScope: CoroutineScope): Flow> 14 | 15 | // 인증 글 작성 16 | suspend fun createRunningPost( 17 | authorUid: String, 18 | runningHistoryId: String, 19 | content: String 20 | ): Result 21 | 22 | // 홍보 글 작성 23 | suspend fun createRecruitPost( 24 | authorUid: String, 25 | groupUid: String 26 | ): Boolean 27 | 28 | // 글 삭제하기 - 리모트 29 | suspend fun deletePost(postId: String): Boolean 30 | 31 | // 글 수정하기 - 리모트 32 | suspend fun updatePost(postId: String, postContent: String, updatedAt: Long): Boolean 33 | } 34 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/repository/RunnerRepository.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.repository 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | interface RunnerRepository { 6 | 7 | // 현재 달리고 있는 사람 카운트 - 리모트 8 | fun getCurrentRunnerCount(): Flow> 9 | 10 | // 내가 달리기를 시작할 때 카운트 올려주고 가입한 그룹에 시작 알림 전달 - 리모트 11 | // 로컬에서는 내가 가입한 그룹 정보만, 서버에서는 그룹에 누가 속했는지 12 | suspend fun startRunning(uid: String): Boolean 13 | 14 | // 내가 달리기를 종료할 때 카운트 내려주기 - 리모트 15 | suspend fun finishRunning(uid: String): Boolean 16 | } 17 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/repository/RunningHistoryRepository.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.repository 2 | 3 | import com.whyranoid.domain.model.RunningHistory 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface RunningHistoryRepository { 7 | 8 | // 운동 내역 가져오기 - 로컬 9 | fun getRunningHistory(): Flow>> 10 | 11 | // 글 안쓴 운동 내역 가져오기 - 로컬 12 | suspend fun getUnpostedRunningHistory(): List 13 | 14 | // 운동한 기록 저장하기 - 로컬 15 | suspend fun saveRunningHistory( 16 | historyId: String, 17 | startedAt: Long, 18 | finishedAt: Long, 19 | totalRunningTime: Int, 20 | pace: Double, 21 | totalDistance: Double 22 | ): Result 23 | 24 | suspend fun uploadRunningHistory(uid: String, runningHistory: RunningHistory): Result 25 | } 26 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/CheckIsDuplicatedGroupNameUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.GroupRepository 4 | import javax.inject.Inject 5 | 6 | class CheckIsDuplicatedGroupNameUseCase @Inject constructor( 7 | private val groupRepository: GroupRepository 8 | ) { 9 | 10 | suspend operator fun invoke(groupName: String): Boolean { 11 | return groupRepository.isDuplicatedGroupName(groupName) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/CreateGroupUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.AccountRepository 4 | import com.whyranoid.domain.repository.GroupRepository 5 | import javax.inject.Inject 6 | 7 | class CreateGroupUseCase @Inject constructor( 8 | private val accountRepository: AccountRepository, 9 | private val groupRepository: GroupRepository 10 | ) { 11 | suspend operator fun invoke(groupName: String, introduce: String, rules: List): Boolean { 12 | val uid = accountRepository.getUid() 13 | return groupRepository.createGroup(groupName, introduce, rules = rules, uid = uid) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/CreateRecruitPostUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.AccountRepository 4 | import com.whyranoid.domain.repository.PostRepository 5 | import javax.inject.Inject 6 | 7 | class CreateRecruitPostUseCase @Inject constructor( 8 | private val postRepository: PostRepository, 9 | private val accountRepository: AccountRepository 10 | ) { 11 | suspend operator fun invoke( 12 | groupUid: String 13 | ): Boolean { 14 | val uid = accountRepository.getUid() 15 | return postRepository.createRecruitPost(uid, groupUid) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/CreateRunningPostUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.AccountRepository 4 | import com.whyranoid.domain.repository.PostRepository 5 | import javax.inject.Inject 6 | 7 | class CreateRunningPostUseCase @Inject constructor( 8 | private val postRepository: PostRepository, 9 | private val accountRepository: AccountRepository 10 | ) { 11 | suspend operator fun invoke( 12 | postContent: String, 13 | runningHistoryId: String 14 | ): Result { 15 | return postRepository.createRunningPost( 16 | accountRepository.getUid(), 17 | runningHistoryId, 18 | postContent 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/DeleteGroupUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.AccountRepository 4 | import com.whyranoid.domain.repository.GroupRepository 5 | import javax.inject.Inject 6 | 7 | // TODO : 그룹원들은 어떻게 그룹을 나가게 하지? 8 | class DeleteGroupUseCase @Inject constructor( 9 | private val accountRepository: AccountRepository, 10 | private val groupRepository: GroupRepository 11 | ) { 12 | suspend operator fun invoke(groupId: String): Boolean { 13 | return groupRepository.deleteGroup(accountRepository.getUid(), groupId) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/DeletePostUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.PostRepository 4 | import javax.inject.Inject 5 | 6 | class DeletePostUseCase @Inject constructor(private val postRepository: PostRepository) { 7 | suspend operator fun invoke(postId: String): Boolean { 8 | return postRepository.deletePost(postId) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/ExitGroupUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.AccountRepository 4 | import com.whyranoid.domain.repository.GroupRepository 5 | import javax.inject.Inject 6 | 7 | class ExitGroupUseCase @Inject constructor( 8 | private val accountRepository: AccountRepository, 9 | private val groupRepository: GroupRepository 10 | ) { 11 | suspend operator fun invoke(groupId: String): Boolean { 12 | return groupRepository.exitGroup(accountRepository.getUid(), groupId) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/FinishRunningUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.model.RunningHistory 4 | import com.whyranoid.domain.repository.AccountRepository 5 | import com.whyranoid.domain.repository.GroupRepository 6 | import com.whyranoid.domain.repository.RunnerRepository 7 | import com.whyranoid.domain.repository.RunningHistoryRepository 8 | import javax.inject.Inject 9 | 10 | class FinishRunningUseCase @Inject constructor( 11 | private val runnerRepository: RunnerRepository, 12 | private val accountRepository: AccountRepository, 13 | private val groupRepository: GroupRepository, 14 | private val runningHistoryRepository: RunningHistoryRepository 15 | ) { 16 | suspend operator fun invoke(runningHistory: RunningHistory? = null): Boolean { 17 | val uid = accountRepository.getUid() 18 | 19 | runnerRepository.finishRunning(uid) 20 | 21 | if (runningHistory != null) { 22 | val uploadResult = runningHistoryRepository.uploadRunningHistory(uid, runningHistory) 23 | 24 | if (uploadResult.isFailure) { 25 | return false 26 | } 27 | 28 | groupRepository.getMyGroupList(uid).onSuccess { groupInfos -> 29 | groupRepository.notifyRunningFinish( 30 | uid = uid, 31 | runningHistory = runningHistory, 32 | groupIdList = groupInfos.map { it.groupId } 33 | ) 34 | } 35 | } 36 | return true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/GetEmailUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.AccountRepository 4 | import kotlinx.coroutines.flow.Flow 5 | import javax.inject.Inject 6 | 7 | class GetEmailUseCase @Inject constructor(private val accountRepository: AccountRepository) { 8 | operator fun invoke(): Flow> = accountRepository.getEmail() 9 | } 10 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/GetGroupInfoUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.model.GroupInfo 4 | import com.whyranoid.domain.repository.GroupRepository 5 | import kotlinx.coroutines.flow.Flow 6 | import javax.inject.Inject 7 | 8 | class GetGroupInfoUseCase @Inject constructor( 9 | private val groupRepository: GroupRepository 10 | ) { 11 | operator fun invoke(uid: String, groupId: String): Flow { 12 | return groupRepository.getGroupInfoFlow(uid, groupId) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/GetGroupNotificationsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.model.GroupNotification 4 | import com.whyranoid.domain.repository.GroupRepository 5 | import kotlinx.coroutines.flow.Flow 6 | import javax.inject.Inject 7 | 8 | class GetGroupNotificationsUseCase @Inject constructor(private val groupRepository: GroupRepository) { 9 | 10 | operator fun invoke(groupId: String): Flow> { 11 | return groupRepository.getGroupNotifications(groupId) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/GetMyGroupListUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.model.GroupInfo 4 | import com.whyranoid.domain.repository.AccountRepository 5 | import com.whyranoid.domain.repository.GroupRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetMyGroupListUseCase @Inject constructor( 10 | private val groupRepository: GroupRepository, 11 | private val accountRepository: AccountRepository 12 | ) { 13 | suspend operator fun invoke(): Flow>> { 14 | val uid = accountRepository.getUid() 15 | return groupRepository.getMyGroupListFlow(uid) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/GetMyPagingPostsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import androidx.paging.PagingData 4 | import com.whyranoid.domain.model.Post 5 | import com.whyranoid.domain.repository.AccountRepository 6 | import com.whyranoid.domain.repository.PostRepository 7 | import kotlinx.coroutines.CoroutineScope 8 | import kotlinx.coroutines.flow.Flow 9 | import javax.inject.Inject 10 | 11 | class GetMyPagingPostsUseCase @Inject constructor( 12 | private val accountRepository: AccountRepository, 13 | private val postRepository: PostRepository 14 | ) { 15 | 16 | suspend operator fun invoke(coroutineScope: CoroutineScope): Flow> { 17 | return postRepository.getMyPagingPosts(accountRepository.getUid(), coroutineScope) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/GetNicknameUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.AccountRepository 4 | import kotlinx.coroutines.flow.Flow 5 | import javax.inject.Inject 6 | 7 | class GetNicknameUseCase @Inject constructor(private val accountRepository: AccountRepository) { 8 | operator fun invoke(): Flow { 9 | return accountRepository.getNickname() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/GetPagingPostsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import androidx.paging.PagingData 4 | import com.whyranoid.domain.model.Post 5 | import com.whyranoid.domain.repository.PostRepository 6 | import kotlinx.coroutines.CoroutineScope 7 | import kotlinx.coroutines.flow.Flow 8 | import javax.inject.Inject 9 | 10 | class GetPagingPostsUseCase @Inject constructor(private val postRepository: PostRepository) { 11 | operator fun invoke(coroutineScope: CoroutineScope): Flow> { 12 | return postRepository.getPagingPosts(coroutineScope) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/GetProfileUriUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.AccountRepository 4 | import kotlinx.coroutines.flow.Flow 5 | import javax.inject.Inject 6 | 7 | class GetProfileUriUseCase @Inject constructor(private val accountRepository: AccountRepository) { 8 | suspend operator fun invoke(): Flow { 9 | return accountRepository.getProfileUri() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/GetRunnerCountUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.RunnerRepository 4 | import kotlinx.coroutines.flow.Flow 5 | import javax.inject.Inject 6 | 7 | class GetRunnerCountUseCase @Inject constructor(private val runnerRepository: RunnerRepository) { 8 | operator fun invoke(): Flow> { 9 | return runnerRepository.getCurrentRunnerCount() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/GetRunningHistoryUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.model.RunningHistory 4 | import com.whyranoid.domain.repository.RunningHistoryRepository 5 | import kotlinx.coroutines.flow.Flow 6 | import javax.inject.Inject 7 | 8 | class GetRunningHistoryUseCase @Inject constructor(private val runningHistoryRepository: RunningHistoryRepository) { 9 | operator fun invoke(): Flow>> { 10 | return runningHistoryRepository.getRunningHistory() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/GetUidUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.AccountRepository 4 | import javax.inject.Inject 5 | 6 | class GetUidUseCase @Inject constructor(private val accountRepository: AccountRepository) { 7 | suspend operator fun invoke(): String { 8 | return accountRepository.getUid() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/GetUnpostedRunningHistoryUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.model.RunningHistory 4 | import com.whyranoid.domain.repository.RunningHistoryRepository 5 | import javax.inject.Inject 6 | 7 | class GetUnpostedRunningHistoryUseCase @Inject constructor(private val runningHistoryRepository: RunningHistoryRepository) { 8 | suspend operator fun invoke(): List { 9 | return runningHistoryRepository.getUnpostedRunningHistory() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/GetUserUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.model.User 4 | import com.whyranoid.domain.repository.AccountRepository 5 | import javax.inject.Inject 6 | 7 | class GetUserUseCase @Inject constructor(private val accountRepository: AccountRepository) { 8 | suspend operator fun invoke(): Result { 9 | return accountRepository.getUser() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/JoinGroupUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.AccountRepository 4 | import com.whyranoid.domain.repository.GroupRepository 5 | import javax.inject.Inject 6 | 7 | class JoinGroupUseCase @Inject constructor( 8 | private val groupRepository: GroupRepository, 9 | private val accountRepository: AccountRepository 10 | ) { 11 | suspend operator fun invoke(groupId: String): Boolean { 12 | groupRepository.joinGroup(accountRepository.getUid(), groupId) 13 | return true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/SaveRunningHistoryUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.model.RunningHistory 4 | import com.whyranoid.domain.repository.RunningHistoryRepository 5 | import javax.inject.Inject 6 | 7 | class SaveRunningHistoryUseCase @Inject constructor(private val runningHistoryRepository: RunningHistoryRepository) { 8 | suspend operator fun invoke( 9 | historyId: String, 10 | startedAt: Long, 11 | finishedAt: Long, 12 | totalRunningTime: Int, 13 | pace: Double, 14 | totalDistance: Double 15 | ): Result { 16 | return runningHistoryRepository.saveRunningHistory( 17 | historyId = historyId, 18 | startedAt = startedAt, 19 | finishedAt = finishedAt, 20 | totalRunningTime = totalRunningTime, 21 | pace = pace, 22 | totalDistance = totalDistance 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/SignOutUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.AccountRepository 4 | import javax.inject.Inject 5 | 6 | class SignOutUseCase @Inject constructor(private val accountRepository: AccountRepository) { 7 | suspend operator fun invoke(): Result { 8 | return accountRepository.signOut() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/StartRunningUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.AccountRepository 4 | import com.whyranoid.domain.repository.GroupRepository 5 | import com.whyranoid.domain.repository.RunnerRepository 6 | import javax.inject.Inject 7 | 8 | class StartRunningUseCase @Inject constructor( 9 | private val runnerRepository: RunnerRepository, 10 | private val accountRepository: AccountRepository, 11 | private val groupRepository: GroupRepository 12 | ) { 13 | suspend operator fun invoke(): Boolean { 14 | runnerRepository.startRunning(accountRepository.getUid()) 15 | groupRepository.getMyGroupList(accountRepository.getUid()).onSuccess { groupInfos -> 16 | groupRepository.notifyRunningStart( 17 | accountRepository.getUid(), 18 | groupInfos.map { it.groupId } 19 | ) 20 | } 21 | return false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/UpdateGroupInfoUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.model.Rule 4 | import com.whyranoid.domain.repository.GroupRepository 5 | import javax.inject.Inject 6 | 7 | class UpdateGroupInfoUseCase @Inject constructor(private val groupRepository: GroupRepository) { 8 | 9 | suspend operator fun invoke( 10 | groupId: String, 11 | groupName: String, 12 | groupIntroduce: String, 13 | rules: List 14 | ): Boolean { 15 | return groupRepository.updateGroupInfo(groupId, groupName, groupIntroduce, rules) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/UpdateNicknameUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.AccountRepository 4 | import javax.inject.Inject 5 | 6 | class UpdateNicknameUseCase @Inject constructor(private val accountRepository: AccountRepository) { 7 | suspend operator fun invoke(uid: String, newNickname: String): Result { 8 | return accountRepository.updateNickname(uid, newNickname) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/UpdatePostRepository.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.PostRepository 4 | import javax.inject.Inject 5 | 6 | class UpdatePostRepository @Inject constructor(private val postRepository: PostRepository) { 7 | suspend operator fun invoke(postId: String, postContent: String, updatedAt: Long): Boolean { 8 | return postRepository.updatePost(postId, postContent, updatedAt) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/UpdateProfileUrlUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.AccountRepository 4 | import javax.inject.Inject 5 | 6 | class UpdateProfileUrlUseCase @Inject constructor(private val accountRepository: AccountRepository) { 7 | suspend operator fun invoke(newProfileUrl: String): Boolean { 8 | return accountRepository.updateProfileUrl(newProfileUrl) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/whyranoid/domain/usecase/WithDrawalUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.domain.usecase 2 | 3 | import com.whyranoid.domain.repository.AccountRepository 4 | import javax.inject.Inject 5 | 6 | class WithDrawalUseCase @Inject constructor(private val accountRepository: AccountRepository) { 7 | suspend operator fun invoke(): Result { 8 | return accountRepository.withDrawal() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /domain/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline -------------------------------------------------------------------------------- /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 | #Your project has set `android.useAndroidX=true`, but configuration `:app:debugRuntimeClasspath` still contains legacy support libraries, which may cause runtime issues. 25 | #This behavior will not be allowed in Android Gradle plugin 8.0. 26 | #Please use only AndroidX dependencies or set `android.enableJetifier=true` in the `gradle.properties` file to migrate your project to AndroidX (see https://developer.android.com/jetpack/androidx/migrate for more info). 27 | android.enableJetifier=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Nov 10 16:08:30 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/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/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. 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/whyranoid/presentation/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.whyranoid.presentation.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /presentation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 17 | 21 | 23 | 24 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.navigation.fragment.NavHostFragment 7 | import androidx.navigation.ui.NavigationUI 8 | import com.whyranoid.presentation.databinding.ActivityMainBinding 9 | import dagger.hilt.android.AndroidEntryPoint 10 | 11 | @AndroidEntryPoint 12 | class MainActivity : AppCompatActivity() { 13 | 14 | private lateinit var binding: ActivityMainBinding 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | 19 | binding = ActivityMainBinding.inflate(layoutInflater) 20 | setContentView(binding.root) 21 | 22 | val navHostFragment = 23 | supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment 24 | val navController = navHostFragment.navController 25 | 26 | navController.addOnDestinationChangedListener { _, destination, _ -> 27 | when (destination.id) { 28 | R.id.communityFragment, R.id.runningStartFragment, R.id.myRunFragment -> 29 | binding.bottomNavigation.visibility = View.VISIBLE 30 | 31 | else -> binding.bottomNavigation.visibility = View.GONE 32 | } 33 | } 34 | 35 | NavigationUI.setupWithNavController(binding.bottomNavigation, navController) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.base 2 | 3 | import android.os.Bundle 4 | import androidx.annotation.LayoutRes 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.databinding.DataBindingUtil 7 | import androidx.databinding.ViewDataBinding 8 | 9 | internal abstract class BaseActivity( 10 | @LayoutRes val layoutRes: Int 11 | ) : AppCompatActivity() { 12 | 13 | private var _binding: VDB? = null 14 | protected val binding get() = requireNotNull(_binding) 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | 19 | _binding = DataBindingUtil.setContentView(this, layoutRes) 20 | binding.lifecycleOwner = this 21 | } 22 | 23 | override fun onDestroy() { 24 | _binding = null 25 | super.onDestroy() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.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 | 12 | internal abstract class BaseFragment( 13 | @LayoutRes val layoutRes: Int 14 | ) : Fragment() { 15 | 16 | private var _binding: VDB? = null 17 | protected val binding get() = requireNotNull(_binding) 18 | 19 | override fun onCreateView( 20 | inflater: LayoutInflater, 21 | container: ViewGroup?, 22 | savedInstanceState: Bundle? 23 | ): View? { 24 | super.onCreateView(inflater, container, savedInstanceState) 25 | _binding = DataBindingUtil.inflate(inflater, layoutRes, container, false) 26 | binding.lifecycleOwner = viewLifecycleOwner 27 | return binding.root 28 | } 29 | 30 | override fun onDestroyView() { 31 | _binding = null 32 | super.onDestroyView() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/community/CommunityCategory.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.community 2 | 3 | import androidx.annotation.StringRes 4 | import com.whyranoid.presentation.R 5 | 6 | enum class CommunityCategory(@StringRes val stringId: Int) { 7 | BOARD(R.string.text_board), 8 | MY_GROUP(R.string.text_my_group), 9 | MY_POST(R.string.text_my_post) 10 | } 11 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/community/CommunityCategoryAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.community 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.viewpager2.adapter.FragmentStateAdapter 5 | 6 | class CommunityCategoryAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) { 7 | 8 | override fun getItemCount(): Int = CATEGORY_COUNT 9 | 10 | override fun createFragment(position: Int): Fragment { 11 | return CommunityItemFragment.newInstance(CommunityCategory.values()[position]) 12 | } 13 | 14 | companion object { 15 | const val CATEGORY_COUNT = 3 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/community/Event.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.community 2 | 3 | import com.whyranoid.presentation.model.GroupInfoUiModel 4 | 5 | sealed class Event { 6 | data class GroupItemClick(val groupInfo: GroupInfoUiModel) : Event() 7 | data class JoinGroup(val isSuccess: Boolean = true) : Event() 8 | data class DeletePost(val isSuccess: Boolean = true, val block: () -> Unit) : Event() 9 | } 10 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/community/MyGroupAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.community 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.ListAdapter 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.whyranoid.presentation.databinding.MyGroupItemBinding 9 | import com.whyranoid.presentation.model.GroupInfoUiModel 10 | 11 | class MyGroupAdapter(private val onClickListener: (GroupInfoUiModel) -> Unit) : 12 | ListAdapter(diffUtil) { 13 | 14 | class MyGroupViewHolder(private val binding: MyGroupItemBinding) : 15 | RecyclerView.ViewHolder(binding.root) { 16 | fun bind(groupInfo: GroupInfoUiModel) { 17 | binding.groupInfo = groupInfo 18 | } 19 | } 20 | 21 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyGroupViewHolder { 22 | val layoutInflater = 23 | MyGroupItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) 24 | return MyGroupViewHolder(layoutInflater) 25 | } 26 | 27 | override fun onBindViewHolder(holder: MyGroupViewHolder, position: Int) { 28 | val curItem = getItem(position) 29 | holder.apply { 30 | itemView.setOnClickListener { 31 | curItem?.let { groupInfo -> 32 | onClickListener(groupInfo) 33 | } 34 | } 35 | bind(groupInfo = curItem) 36 | } 37 | } 38 | 39 | companion object { 40 | val diffUtil = object : DiffUtil.ItemCallback() { 41 | override fun areItemsTheSame( 42 | oldItem: GroupInfoUiModel, 43 | newItem: GroupInfoUiModel 44 | ): Boolean = 45 | oldItem.groupId == newItem.groupId 46 | 47 | override fun areContentsTheSame( 48 | oldItem: GroupInfoUiModel, 49 | newItem: GroupInfoUiModel 50 | ): Boolean = 51 | oldItem == newItem 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/community/group/create/Event.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.community.group.create 2 | 3 | sealed class Event { 4 | data class CreateGroupButtonClick(val isSuccess: Boolean = true) : Event() 5 | object WarningButtonClick : Event() 6 | object AddRuleButtonClick : Event() 7 | object InvalidRule : Event() 8 | data class DuplicateCheckButtonClick(val isDuplicatedGroupName: Boolean = false) : Event() 9 | } 10 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/community/group/detail/Event.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.community.group.detail 2 | 3 | sealed class Event { 4 | object RecruitButtonClick : Event() 5 | object ExitGroupButtonClick : Event() 6 | data class RecruitSnackBarButtonClick(val isSuccess: Boolean = true) : Event() 7 | data class ExitGroupSnackBarButtonClick(val isSuccess: Boolean = true) : Event() 8 | } 9 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/community/group/detail/GroupSettingDialog.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.community.group.detail 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment 8 | import com.whyranoid.presentation.databinding.GroupSettingDialogBinding 9 | 10 | class GroupSettingDialog( 11 | private val onEditButtonClickListener: () -> Unit, 12 | private val onDeleteButtonClickListener: () -> Unit 13 | ) : BottomSheetDialogFragment() { 14 | 15 | companion object { 16 | const val TAG = "GroupSettingDialog" 17 | } 18 | 19 | lateinit var binding: GroupSettingDialogBinding 20 | 21 | override fun onCreateView( 22 | inflater: LayoutInflater, 23 | container: ViewGroup?, 24 | savedInstanceState: Bundle? 25 | ): View { 26 | super.onCreateView(inflater, container, savedInstanceState) 27 | binding = GroupSettingDialogBinding.inflate(inflater, container, false) 28 | return binding.root 29 | } 30 | 31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 32 | super.onViewCreated(view, savedInstanceState) 33 | setButtons() 34 | } 35 | 36 | private fun setButtons() { 37 | with(binding) { 38 | btnEditGroup.setOnClickListener { 39 | onEditButtonClickListener.invoke() 40 | dismiss() 41 | } 42 | 43 | btnDeleteGroup.setOnClickListener { 44 | onDeleteButtonClickListener.invoke() 45 | dismiss() 46 | } 47 | 48 | btnCancel.setOnClickListener { 49 | dismiss() 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/community/group/edit/EditGroupViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.community.group.edit 2 | 3 | import androidx.lifecycle.SavedStateHandle 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import com.whyranoid.domain.model.toRule 7 | import com.whyranoid.domain.usecase.UpdateGroupInfoUseCase 8 | import com.whyranoid.presentation.model.GroupInfoUiModel 9 | import com.whyranoid.presentation.util.MutableEventFlow 10 | import com.whyranoid.presentation.util.asEventFlow 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import kotlinx.coroutines.flow.MutableStateFlow 13 | import kotlinx.coroutines.launch 14 | import javax.inject.Inject 15 | 16 | @HiltViewModel 17 | class EditGroupViewModel @Inject constructor( 18 | private val updateGroupInfoUseCase: UpdateGroupInfoUseCase, 19 | stateHandle: SavedStateHandle 20 | ) : ViewModel() { 21 | 22 | private val initGroupInfo = requireNotNull(stateHandle.get("groupInfo")) 23 | 24 | val groupName = MutableStateFlow(initGroupInfo.name) 25 | val groupIntroduce = MutableStateFlow(initGroupInfo.introduce) 26 | val rules = MutableStateFlow>(initGroupInfo.rules.map { it.toString() }) 27 | 28 | private val _eventFlow = MutableEventFlow() 29 | val eventFlow = _eventFlow.asEventFlow() 30 | 31 | fun onAddRuleButtonClicked() { 32 | emitEvent(Event.AddRuleButtonClick) 33 | } 34 | 35 | fun removeRule(rule: String) { 36 | rules.value = rules.value.filter { it != rule } 37 | } 38 | 39 | fun emitEvent(event: Event) { 40 | viewModelScope.launch { 41 | when (event) { 42 | is Event.AddRuleButtonClick -> { 43 | _eventFlow.emit(event) 44 | } 45 | // TODO : 성공 여부에 따른 분기처리 46 | is Event.EditGroupButtonClick -> { 47 | viewModelScope.launch { 48 | val isSuccess = updateGroupInfoUseCase( 49 | groupId = initGroupInfo.groupId, 50 | groupName = groupName.value, 51 | groupIntroduce = groupIntroduce.value, 52 | rules = rules.value.map { 53 | it.toRule() 54 | } 55 | ) 56 | if (isSuccess) { 57 | _eventFlow.emit(event) 58 | } else { 59 | _eventFlow.emit(Event.EditGroupButtonClick(false)) 60 | } 61 | } 62 | } 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/community/group/edit/Event.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.community.group.edit 2 | 3 | sealed class Event { 4 | data class EditGroupButtonClick(val isSuccess: Boolean = true) : Event() 5 | object AddRuleButtonClick : Event() 6 | } 7 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/compose/TopSnackBar.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.compose 2 | 3 | import androidx.compose.animation.AnimatedVisibility 4 | import androidx.compose.animation.core.FastOutLinearInEasing 5 | import androidx.compose.animation.core.LinearOutSlowInEasing 6 | import androidx.compose.animation.core.tween 7 | import androidx.compose.animation.slideInVertically 8 | import androidx.compose.animation.slideOutVertically 9 | import androidx.compose.foundation.background 10 | import androidx.compose.foundation.layout.Box 11 | import androidx.compose.foundation.layout.fillMaxWidth 12 | import androidx.compose.foundation.layout.padding 13 | import androidx.compose.foundation.shape.RoundedCornerShape 14 | import androidx.compose.material.Text 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.ui.Alignment 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.draw.clip 19 | import androidx.compose.ui.res.colorResource 20 | import androidx.compose.ui.unit.dp 21 | import com.whyranoid.presentation.R 22 | 23 | @Composable 24 | fun TopSnackBar(isVisible: Boolean, text: String) { 25 | AnimatedVisibility( 26 | visible = isVisible, 27 | enter = slideInVertically( 28 | initialOffsetY = { fullHeight -> -fullHeight }, 29 | animationSpec = tween(durationMillis = 250, easing = LinearOutSlowInEasing) 30 | ), 31 | exit = slideOutVertically( 32 | targetOffsetY = { fullHeight -> -fullHeight }, 33 | animationSpec = tween(durationMillis = 250, easing = FastOutLinearInEasing) 34 | ) 35 | ) { 36 | Box( 37 | modifier = Modifier.fillMaxWidth() 38 | .padding(8.dp) 39 | .clip(shape = RoundedCornerShape(15.dp)) 40 | .background(color = colorResource(id = R.color.mogakrun_secondary_dark)), 41 | contentAlignment = Alignment.Center 42 | ) { 43 | Text( 44 | modifier = Modifier.padding(8.dp), 45 | text = text, 46 | color = colorResource(id = R.color.mogakrun_on_secondary) 47 | ) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/di/NetworkConnectionModule.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.di 2 | 3 | import android.content.Context 4 | import com.whyranoid.presentation.util.networkconnection.NetworkConnectionStateHolder 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 | class NetworkConnectionModule { 15 | 16 | @Provides 17 | @Singleton 18 | fun provideNetworkConnectionStateHolder(@ApplicationContext context: Context): NetworkConnectionStateHolder { 19 | return NetworkConnectionStateHolder(context) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/model/GroupInfoUiModel.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.model 2 | 3 | import android.os.Parcelable 4 | import com.whyranoid.domain.model.GroupInfo 5 | import kotlinx.parcelize.Parcelize 6 | 7 | @Parcelize 8 | data class GroupInfoUiModel( 9 | val name: String, 10 | val groupId: String, 11 | val introduce: String, 12 | val rules: List, 13 | val headCount: Int, 14 | val leader: UserUiModel 15 | ) : Parcelable 16 | 17 | fun GroupInfoUiModel.toGroupInfo() = 18 | GroupInfo( 19 | name = this.name, 20 | groupId = this.groupId, 21 | introduce = this.introduce, 22 | rules = this.rules.map { rule -> 23 | rule.toRule() 24 | }, 25 | headCount = this.headCount, 26 | leader = this.leader.toUser() 27 | ) 28 | 29 | fun GroupInfo.toGroupInfoUiModel() = 30 | GroupInfoUiModel( 31 | name = this.name, 32 | groupId = this.groupId, 33 | introduce = this.introduce, 34 | rules = this.rules.map { rule -> 35 | rule.toRuleUiModel() 36 | }, 37 | headCount = this.headCount, 38 | leader = this.leader.toUserUiModel() 39 | ) 40 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/model/RuleUiModel.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.model 2 | 3 | import android.os.Parcelable 4 | import com.whyranoid.domain.model.DayOfWeek 5 | import com.whyranoid.domain.model.Rule 6 | import kotlinx.parcelize.Parcelize 7 | 8 | @Parcelize 9 | data class RuleUiModel( 10 | val dayOfWeek: DayOfWeek, 11 | val hour: Int, 12 | val minute: Int 13 | ) : Parcelable { 14 | override fun toString(): String { 15 | return "${dayOfWeek.dayResId}-$hour-$minute" 16 | } 17 | } 18 | 19 | fun RuleUiModel.toRule() = 20 | Rule( 21 | dayOfWeek = this.dayOfWeek, 22 | hour = this.hour, 23 | minute = this.minute 24 | ) 25 | 26 | fun Rule.toRuleUiModel() = 27 | RuleUiModel( 28 | dayOfWeek = this.dayOfWeek, 29 | hour = this.hour, 30 | minute = this.minute 31 | ) 32 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/model/RunningHistoryUiModel.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.model 2 | 3 | import com.whyranoid.domain.model.RunningHistory 4 | 5 | data class RunningHistoryUiModel( 6 | val historyId: String, 7 | val date: Long, 8 | val totalRunningTime: Int, 9 | val startedAt: Long, 10 | val finishedAt: Long, 11 | val totalDistance: Double, 12 | val pace: Double 13 | ) : java.io.Serializable 14 | 15 | // TODO 원하는 형태로 변환하도록 코드 수정해야함 16 | fun RunningHistory.toRunningHistoryUiModel(): RunningHistoryUiModel { 17 | return RunningHistoryUiModel( 18 | historyId = historyId, 19 | date = startedAt, 20 | totalRunningTime = totalRunningTime, 21 | startedAt = startedAt, 22 | finishedAt = finishedAt, 23 | totalDistance = totalDistance, 24 | pace = pace 25 | ) 26 | } 27 | 28 | fun RunningHistoryUiModel.toRunningHistory(): RunningHistory { 29 | return RunningHistory( 30 | historyId = historyId, 31 | startedAt = startedAt, 32 | finishedAt = finishedAt, 33 | totalRunningTime = totalRunningTime, 34 | pace = pace, 35 | totalDistance = totalDistance 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/model/UiState.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.model 2 | 3 | sealed class UiState { 4 | 5 | object UnInitialized : UiState() 6 | 7 | object Loading : UiState() 8 | 9 | data class Success(val value: T) : UiState() 10 | 11 | data class Failure(val throwable: Throwable) : UiState() 12 | } 13 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/model/UserUiModel.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.model 2 | 3 | import android.os.Parcelable 4 | import com.whyranoid.domain.model.User 5 | import kotlinx.parcelize.Parcelize 6 | 7 | @Parcelize 8 | data class UserUiModel( 9 | val uid: String, 10 | val name: String?, 11 | val profileUrl: String? 12 | ) : Parcelable 13 | 14 | fun UserUiModel.toUser() = 15 | User( 16 | uid = this.uid, 17 | name = this.name, 18 | profileUrl = this.profileUrl 19 | ) 20 | 21 | fun User.toUserUiModel() = 22 | UserUiModel( 23 | uid = this.uid, 24 | name = this.name, 25 | profileUrl = this.profileUrl 26 | ) 27 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/myrun/CalendarDayBinder.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.myrun 2 | 3 | import android.view.View 4 | import androidx.core.content.ContextCompat 5 | import com.kizitonwose.calendarview.CalendarView 6 | import com.kizitonwose.calendarview.model.CalendarDay 7 | import com.kizitonwose.calendarview.model.DayOwner 8 | import com.kizitonwose.calendarview.ui.DayBinder 9 | import com.kizitonwose.calendarview.ui.ViewContainer 10 | import com.whyranoid.presentation.R 11 | import com.whyranoid.presentation.databinding.ItemCalendarDayBinding 12 | 13 | class CalendarDayBinder( 14 | private val calendarView: CalendarView, 15 | private val runningDays: List> 16 | ) : DayBinder { 17 | 18 | class DayContainer( 19 | val binding: ItemCalendarDayBinding 20 | ) : ViewContainer(binding.root) 21 | 22 | override fun create(view: View): DayContainer = 23 | DayContainer(ItemCalendarDayBinding.bind(view)) 24 | 25 | override fun bind(container: DayContainer, day: CalendarDay) { 26 | container.binding.tvCalendarDay.text = day.date.dayOfMonth.toString() 27 | 28 | if (day.owner != DayOwner.THIS_MONTH) { 29 | container.binding.tvCalendarDay.setTextColor( 30 | ContextCompat.getColor( 31 | calendarView.context, 32 | R.color.gray 33 | ) 34 | ) 35 | container.binding.root.background = null 36 | } else { 37 | container.binding.tvCalendarDay.setTextColor( 38 | ContextCompat.getColor( 39 | calendarView.context, 40 | R.color.mogakrun_on_secondary 41 | ) 42 | ) 43 | container.binding.root.background = null 44 | } 45 | 46 | runningDays.forEach { runningDay -> 47 | val (runningDayYear, runningDayMonth, runningDayDay) = runningDay.map { it.toInt() } 48 | 49 | if (runningDayYear == day.date.year && runningDayMonth == day.date.monthValue && runningDayDay == day.date.dayOfMonth) { 50 | container.binding.root.background = 51 | ContextCompat.getDrawable( 52 | calendarView.context, 53 | R.drawable.kong 54 | ) 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/myrun/MyRunningHistoryAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.myrun 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.DiffUtil 7 | import androidx.recyclerview.widget.ListAdapter 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.whyranoid.presentation.R 10 | import com.whyranoid.presentation.databinding.ItemRunningHistoryBinding 11 | import com.whyranoid.presentation.model.RunningHistoryUiModel 12 | 13 | class MyRunningHistoryAdapter : 14 | ListAdapter( 15 | MyRunningHistoryDiffCallback() 16 | ) { 17 | 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyRunningHistoryViewHolder { 19 | val view = LayoutInflater.from(parent.context) 20 | .inflate(R.layout.item_running_history, parent, false) 21 | return MyRunningHistoryViewHolder(view) 22 | } 23 | 24 | override fun onBindViewHolder(holder: MyRunningHistoryViewHolder, position: Int) { 25 | holder.bind(getItem(position)) 26 | } 27 | } 28 | 29 | class MyRunningHistoryViewHolder(view: View) : RecyclerView.ViewHolder(view) { 30 | private val binding = ItemRunningHistoryBinding.bind(view) 31 | 32 | fun bind(runningHistory: RunningHistoryUiModel) { 33 | binding.runningHistory = runningHistory 34 | } 35 | } 36 | 37 | class MyRunningHistoryDiffCallback : DiffUtil.ItemCallback() { 38 | override fun areItemsTheSame( 39 | oldItem: RunningHistoryUiModel, 40 | newItem: RunningHistoryUiModel 41 | ): Boolean { 42 | return oldItem.historyId == newItem.historyId 43 | } 44 | 45 | override fun areContentsTheSame( 46 | oldItem: RunningHistoryUiModel, 47 | newItem: RunningHistoryUiModel 48 | ): Boolean { 49 | return oldItem == newItem 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/myrun/SettingFragment.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.myrun 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.fragment.app.viewModels 6 | import com.whyranoid.presentation.R 7 | import com.whyranoid.presentation.base.BaseFragment 8 | import com.whyranoid.presentation.databinding.FragmentSettingBinding 9 | import com.whyranoid.presentation.model.UiState 10 | import com.whyranoid.presentation.util.repeatWhenUiStarted 11 | import dagger.hilt.android.AndroidEntryPoint 12 | 13 | @AndroidEntryPoint 14 | internal class SettingFragment : BaseFragment(R.layout.fragment_setting) { 15 | 16 | private val viewModel by viewModels() 17 | 18 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 19 | super.onViewCreated(view, savedInstanceState) 20 | 21 | initViews() 22 | observeState() 23 | } 24 | 25 | private fun initViews() { 26 | // binding.tvLogOut.setOnClickListener { 27 | // viewModel.signOut() 28 | // val intent = Intent(requireActivity(), SignInActivity::class.java) 29 | // startActivity(intent) 30 | // } 31 | } 32 | 33 | private fun observeState() { 34 | viewLifecycleOwner.repeatWhenUiStarted { 35 | viewModel.emailState.collect { emailState -> 36 | when (emailState) { 37 | is UiState.UnInitialized -> { 38 | // 초기화 안됨 39 | } 40 | is UiState.Loading -> { 41 | // 로딩 중 42 | } 43 | is UiState.Success -> initEmailView(emailState.value) 44 | is UiState.Failure -> { 45 | // 실패 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | private fun initEmailView(email: String) { 53 | binding.tvConnectedAccount.text = email 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/myrun/SettingViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.myrun 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.whyranoid.domain.usecase.GetEmailUseCase 6 | import com.whyranoid.domain.usecase.SignOutUseCase 7 | import com.whyranoid.presentation.model.UiState 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | import kotlinx.coroutines.flow.StateFlow 11 | import kotlinx.coroutines.flow.asStateFlow 12 | import kotlinx.coroutines.launch 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class SettingViewModel @Inject constructor( 17 | private val getEmailUseCase: GetEmailUseCase, 18 | private val signOutUseCase: SignOutUseCase 19 | ) : ViewModel() { 20 | 21 | init { 22 | getEmail() 23 | } 24 | 25 | private val _emailState = MutableStateFlow>(UiState.UnInitialized) 26 | val emailState: StateFlow> 27 | get() = _emailState.asStateFlow() 28 | 29 | private fun getEmail() { 30 | viewModelScope.launch { 31 | _emailState.value = UiState.Loading 32 | 33 | getEmailUseCase().collect { emailResult -> 34 | emailResult.onSuccess { email -> 35 | _emailState.value = UiState.Success(email) 36 | }.onFailure { throwable -> 37 | _emailState.value = UiState.Failure(throwable) 38 | } 39 | } 40 | } 41 | } 42 | 43 | fun signOut() { 44 | viewModelScope.launch { 45 | signOutUseCase.invoke() 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/running/Event.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.running 2 | 3 | import com.whyranoid.runningdata.model.RunningFinishData 4 | 5 | sealed interface Event { 6 | data class FinishButtonClick(val runningFinishData: RunningFinishData) : Event 7 | object RunningFinishFailure : Event 8 | } 9 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/running/RunningActivityObserver.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.running 2 | 3 | import android.os.Bundle 4 | import androidx.lifecycle.DefaultLifecycleObserver 5 | import androidx.lifecycle.LifecycleOwner 6 | import com.naver.maps.map.MapView 7 | 8 | class RunningActivityObserver( 9 | private val mapView: MapView, 10 | private val savedInstanceState: Bundle? 11 | ) : DefaultLifecycleObserver { 12 | 13 | override fun onCreate(owner: LifecycleOwner) { 14 | super.onCreate(owner) 15 | mapView.onCreate(savedInstanceState) 16 | } 17 | 18 | override fun onStart(owner: LifecycleOwner) { 19 | super.onStart(owner) 20 | mapView.onStart() 21 | } 22 | 23 | override fun onResume(owner: LifecycleOwner) { 24 | super.onResume(owner) 25 | mapView.onResume() 26 | } 27 | 28 | override fun onPause(owner: LifecycleOwner) { 29 | mapView.onPause() 30 | super.onPause(owner) 31 | } 32 | 33 | override fun onStop(owner: LifecycleOwner) { 34 | mapView.onStop() 35 | super.onStop(owner) 36 | } 37 | 38 | override fun onDestroy(owner: LifecycleOwner) { 39 | mapView.onDestroy() 40 | super.onDestroy(owner) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/running/RunningUtil.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.running 2 | 3 | import com.naver.maps.geometry.LatLng 4 | import com.whyranoid.domain.model.RunningHistory 5 | import com.whyranoid.presentation.model.RunningHistoryUiModel 6 | import com.whyranoid.runningdata.model.RunningHistoryModel 7 | import com.whyranoid.runningdata.model.RunningPosition 8 | 9 | fun RunningPosition.toLatLng(): LatLng { 10 | return LatLng( 11 | this.latitude, 12 | this.longitude 13 | ) 14 | } 15 | 16 | fun RunningHistoryModel.toRunningHistoryUiModel() = 17 | RunningHistoryUiModel( 18 | historyId = historyId, 19 | date = startedAt, 20 | startedAt = startedAt, 21 | finishedAt = finishedAt, 22 | totalRunningTime = totalRunningTime, 23 | pace = pace, 24 | totalDistance = totalDistance 25 | ) 26 | 27 | fun RunningHistoryModel.toRunningHistory() = 28 | RunningHistory( 29 | historyId = historyId, 30 | startedAt = startedAt, 31 | finishedAt = finishedAt, 32 | totalRunningTime = totalRunningTime, 33 | pace = pace, 34 | totalDistance = totalDistance 35 | ) 36 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/running/TrackingMode.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.running 2 | 3 | enum class TrackingMode { 4 | NONE, NO_FOLLOW, FOLLOW 5 | } 6 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/running/WorkManagerInitializer.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.running 2 | 3 | import android.content.Context 4 | import androidx.startup.Initializer 5 | import androidx.work.Configuration 6 | import androidx.work.WorkManager 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | class WorkManagerInitializer : Initializer { 17 | 18 | @Provides 19 | @Singleton 20 | override fun create(@ApplicationContext context: Context): WorkManager { 21 | val configuration = Configuration.Builder().build() 22 | WorkManager.initialize(context, configuration) 23 | return WorkManager.getInstance(context) 24 | } 25 | 26 | override fun dependencies(): MutableList>> { 27 | return mutableListOf() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/runningfinish/Event.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.runningfinish 2 | 3 | import com.whyranoid.presentation.model.RunningHistoryUiModel 4 | 5 | sealed class Event { 6 | data class PositiveButtonClick(val runningHistory: RunningHistoryUiModel) : Event() 7 | object NegativeButtonButtonClick : Event() 8 | } 9 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/runningstart/Event.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.runningstart 2 | 3 | sealed class Event { 4 | object RunningStartButtonClick : Event() 5 | } 6 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/util/BindingAdapters.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.util 2 | 3 | import android.view.View 4 | import android.widget.ImageView 5 | import android.widget.TextView 6 | import androidx.databinding.BindingAdapter 7 | import com.bumptech.glide.Glide 8 | import com.whyranoid.presentation.R 9 | import com.whyranoid.presentation.util.networkconnection.NetworkState 10 | import java.text.SimpleDateFormat 11 | import java.util.* 12 | 13 | @BindingAdapter("networkConnectionVisibility") 14 | fun View.networkConnectionVisibility(networkState: NetworkState) { 15 | visibility = when (networkState) { 16 | is NetworkState.UnInitialized -> View.GONE 17 | is NetworkState.Connection -> View.GONE 18 | is NetworkState.DisConnection -> View.VISIBLE 19 | } 20 | } 21 | 22 | @BindingAdapter("enableWithNetworkState") 23 | fun View.enableWithNetworkState(networkState: NetworkState) { 24 | isEnabled = when (networkState) { 25 | is NetworkState.UnInitialized -> true 26 | is NetworkState.Connection -> true 27 | is NetworkState.DisConnection -> false 28 | } 29 | } 30 | 31 | @BindingAdapter("loadImage") 32 | fun ImageView.loadImage(uri: String) { 33 | Glide.with(this.context) 34 | .load(uri) 35 | .error(R.drawable.thumbnail_src_small) 36 | .circleCrop() 37 | .into(this) 38 | } 39 | 40 | @BindingAdapter("startLongToDate") 41 | fun TextView.startLongToDate(long: Long) { 42 | val formatter = SimpleDateFormat("yyyy.MM.dd / HH 시 mm 분 운동 시작", Locale.KOREA) 43 | text = formatter.format(Date(long)) 44 | } 45 | 46 | @BindingAdapter("finishLongToDate") 47 | fun TextView.finishLongToDate(long: Long) { 48 | val formatter = SimpleDateFormat("yyyy.MM.dd / HH 시 mm 분 운동 종료", Locale.KOREA) 49 | text = formatter.format(Date(long)) 50 | } 51 | 52 | @BindingAdapter("longToTime") 53 | fun TextView.longToTime(long: Long) { 54 | val formatter = SimpleDateFormat("HH:mm", Locale.getDefault()) 55 | text = formatter.format(Date(long)) 56 | } 57 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/util/EventFlow.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.util 2 | 3 | import com.whyranoid.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 | interface EventFlow : Flow { 10 | 11 | companion object { 12 | const val DEFAULT_REPLAY = 2 13 | } 14 | } 15 | 16 | interface MutableEventFlow : EventFlow, FlowCollector 17 | 18 | private class EventFlowSlot(val value: T) { 19 | 20 | private val consumed = AtomicBoolean(false) 21 | 22 | fun markConsumed() = consumed.getAndSet(true) 23 | } 24 | 25 | private class EventFlowImpl(replay: Int) : MutableEventFlow { 26 | 27 | private val flow: MutableSharedFlow> = MutableSharedFlow(replay) 28 | 29 | override suspend fun collect(collector: FlowCollector) = 30 | flow.collect { slot -> 31 | if (slot.markConsumed().not()) { 32 | collector.emit(slot.value) 33 | } 34 | } 35 | 36 | override suspend fun emit(value: T) { 37 | flow.emit(EventFlowSlot(value)) 38 | } 39 | } 40 | 41 | private class ReadOnlyEventFlow(flow: EventFlow) : EventFlow by flow 42 | 43 | @Suppress("FunctionName") 44 | fun MutableEventFlow(replay: Int = DEFAULT_REPLAY): MutableEventFlow = EventFlowImpl(replay) 45 | 46 | fun MutableEventFlow.asEventFlow(): EventFlow = ReadOnlyEventFlow(this) 47 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/util/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.util 2 | 3 | import android.content.Intent 4 | import android.os.Build 5 | import android.os.Bundle 6 | import android.os.Parcelable 7 | import android.view.View 8 | import androidx.lifecycle.Lifecycle 9 | import androidx.lifecycle.LifecycleOwner 10 | import androidx.lifecycle.lifecycleScope 11 | import androidx.lifecycle.repeatOnLifecycle 12 | import com.google.android.material.snackbar.Snackbar 13 | import kotlinx.coroutines.launch 14 | import java.io.Serializable 15 | import java.text.SimpleDateFormat 16 | import java.util.* 17 | 18 | fun LifecycleOwner.repeatWhenUiStarted(block: suspend () -> Unit) { 19 | lifecycleScope.launch { 20 | repeatOnLifecycle(Lifecycle.State.STARTED) { 21 | block.invoke() 22 | } 23 | } 24 | } 25 | 26 | fun Date.dateToString(format: String): String { 27 | val formatter = SimpleDateFormat(format, Locale.getDefault()) 28 | return formatter.format(this) 29 | } 30 | 31 | fun Long.toRunningDateString(): String { 32 | val formatter = SimpleDateFormat("yyyy.MM.dd", Locale.getDefault()) 33 | return formatter.format(this) 34 | } 35 | 36 | fun View.makeSnackBar(message: String): Snackbar { 37 | return Snackbar.make(this, message, Snackbar.LENGTH_SHORT) 38 | } 39 | 40 | inline fun Intent.getSerializableData(key: String): T? = when { 41 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializableExtra( 42 | key, 43 | T::class.java 44 | ) 45 | else -> @Suppress("DEPRECATION") getSerializableExtra(key) as? T 46 | } 47 | 48 | inline fun Bundle.getSerializableData(key: String): T? = when { 49 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializable(key, T::class.java) 50 | else -> @Suppress("DEPRECATION") getSerializable(key) as? T 51 | } 52 | 53 | inline fun Intent.getParcelableData(key: String): T? = when { 54 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getParcelableExtra(key, T::class.java) 55 | else -> @Suppress("DEPRECATION") getParcelableExtra(key) as? T 56 | } 57 | 58 | inline fun Bundle.getParcelableData(key: String): T? = when { 59 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getParcelable(key, T::class.java) 60 | else -> @Suppress("DEPRECATION") getParcelable(key) as? T 61 | } 62 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/util/converters/UnitConverters.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.util.converters 2 | 3 | import android.content.Context 4 | 5 | object UnitConverters { 6 | fun dpToPx(context: Context, dp: Int): Int { 7 | val scale: Float = context.resources.displayMetrics.density 8 | return (dp * scale + 0.5f).toInt() 9 | } 10 | 11 | fun pxToDp(context: Context, px: Int): Int { 12 | val scale: Float = context.resources.displayMetrics.density 13 | val mul = when (scale) { 14 | 1.0f -> 4.0f 15 | 1.5f -> 8 / 3.0f 16 | 2.0f -> 2.0f 17 | else -> 1.0f 18 | } 19 | return (px / (scale * mul)).toInt() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/util/gpsstate/GPSState.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.util.gpsstate 2 | 3 | import android.content.Context 4 | import android.location.LocationManager 5 | 6 | object GPSState { 7 | 8 | fun getGpsState(context: Context): Boolean { 9 | val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager 10 | return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/whyranoid/presentation/util/networkconnection/NetworkState.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation.util.networkconnection 2 | 3 | sealed class NetworkState { 4 | object UnInitialized : NetworkState() 5 | 6 | object Connection : NetworkState() 7 | 8 | object DisConnection : NetworkState() 9 | } 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/background_rounded.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/bottom_navigation_color_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/bottom_navigation_community.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/bottom_navigation_my_run.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/bottom_navigation_running.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/community_create_group_edit_text_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/community_create_running_post_check.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/community_create_running_post_edit_text_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/community_create_running_post_uncheck.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/done_outline_icon.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/done_solid_icon.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/kong.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 16 | 20 | 24 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/my_run_edit_nick_name.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/my_run_setting_account.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/my_run_setting_log_out.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/my_run_setting_open_source_license.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/my_run_setting_privacy_policy.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/my_run_setting_service_center.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/my_run_setting_service_policy.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/my_run_setting_whyranoider.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/my_run_tool_bar_setting.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/navigation_back_button.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/plus_button.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/radius_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/thumbnail_src_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/presentation/src/main/res/drawable/thumbnail_src_small.png -------------------------------------------------------------------------------- /presentation/src/main/res/font/nanum_square_round_bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/presentation/src/main/res/font/nanum_square_round_bold.otf -------------------------------------------------------------------------------- /presentation/src/main/res/font/nanum_square_round_extra_bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/presentation/src/main/res/font/nanum_square_round_extra_bold.otf -------------------------------------------------------------------------------- /presentation/src/main/res/font/nanum_square_round_light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/presentation/src/main/res/font/nanum_square_round_light.otf -------------------------------------------------------------------------------- /presentation/src/main/res/font/nanum_square_round_regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/presentation/src/main/res/font/nanum_square_round_regular.otf -------------------------------------------------------------------------------- /presentation/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 26 | 27 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/dialog_edit_nick_name.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 23 | 24 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/fragment_community.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 21 | 22 | 30 | 31 | 32 | 33 | 43 | 44 | 45 | 46 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/fragment_community_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 20 | 21 | 30 | 31 | 35 | 36 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/item_calendar_day.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/my_finish_notification_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 18 | 19 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/my_group_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 19 | 20 | 30 | 31 | 41 | 42 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/my_group_item_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 14 | 15 | 25 | 26 | 36 | 37 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/my_start_notification_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 18 | 19 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/network_conntection_alert.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/other_finish_notification_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 18 | 19 | 29 | 30 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/other_start_notification_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 18 | 19 | 29 | 30 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/tool_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 15 | 16 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /presentation/src/main/res/menu/bottom_navigation_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 13 | 19 | -------------------------------------------------------------------------------- /presentation/src/main/res/menu/community_create_running_post_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 14 | 15 | 16 | 19 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /presentation/src/main/res/menu/community_go_to_create_running_post_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /presentation/src/main/res/menu/community_select_running_history_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 14 | 15 | 16 | 19 | 25 | 26 | -------------------------------------------------------------------------------- /presentation/src/main/res/menu/create_group_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 14 | 15 | 16 | 19 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /presentation/src/main/res/menu/group_detail_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/menu/my_group_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /presentation/src/main/res/menu/my_run_setting_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /presentation/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /presentation/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/presentation/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /presentation/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/presentation/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /presentation/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/presentation/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /presentation/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/presentation/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /presentation/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/presentation/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /presentation/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/presentation/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /presentation/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/presentation/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /presentation/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/presentation/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /presentation/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /presentation/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /presentation/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /presentation/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /presentation/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #000000 9 | #FFFFFFFF 10 | 11 | #FFFFFF 12 | #FFFFFF 13 | #E6E4DD 14 | #60AD73 15 | 16 | #FFF3D0 17 | #FFF9DA 18 | #FDEDBB 19 | #787878 20 | 21 | #F3F2ED 22 | #6D6D6D 23 | 24 | #666666 25 | #CCCCCC 26 | 27 | -------------------------------------------------------------------------------- /presentation/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | 24 | 31 | -------------------------------------------------------------------------------- /presentation/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /presentation/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /presentation/src/test/java/com/whyranoid/presentation/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.presentation 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /runningdata/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /runningdata/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.library" 3 | id "org.jetbrains.kotlin.android" 4 | } 5 | 6 | android { 7 | namespace "com.whyranoid.runningdata" 8 | compileSdk 33 9 | 10 | defaultConfig { 11 | minSdk 23 12 | targetSdk 33 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles "consumer-rules.pro" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | } 28 | kotlinOptions { 29 | jvmTarget = "1.8" 30 | } 31 | } 32 | 33 | dependencies { 34 | 35 | implementation "androidx.core:core-ktx:$coreKtxVersion" 36 | implementation "androidx.appcompat:appcompat:$appcompatVersion" 37 | implementation "com.google.android.material:material:$materialVersion" 38 | testImplementation "junit:junit:$junitVersion" 39 | androidTestImplementation "androidx.test.ext:junit:$junitUiVersion" 40 | androidTestImplementation "androidx.test.espresso:espresso-core:$espressoCoreVersion" 41 | } -------------------------------------------------------------------------------- /runningdata/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/runningdata/consumer-rules.pro -------------------------------------------------------------------------------- /runningdata/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. 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 -------------------------------------------------------------------------------- /runningdata/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /runningdata/src/main/java/com/whyranoid/runningdata/model/RunningData.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.runningdata.model 2 | 3 | import android.location.Location 4 | import java.util.UUID 5 | 6 | data class RunningData( 7 | val startTime: Long = 0L, 8 | val runningTime: Int = 0, 9 | val totalDistance: Double = 0.0, 10 | val pace: Double = 0.0, 11 | val runningPositionList: List> = listOf(emptyList()), 12 | val lastLocation: Location? = null 13 | ) 14 | 15 | fun RunningData.toRunningFinishData() = 16 | RunningFinishData( 17 | RunningHistoryModel( 18 | historyId = UUID.randomUUID().toString(), 19 | startedAt = startTime, 20 | finishedAt = System.currentTimeMillis(), 21 | totalRunningTime = runningTime, 22 | pace = pace, 23 | totalDistance = totalDistance 24 | ), 25 | runningPositionList 26 | ) 27 | -------------------------------------------------------------------------------- /runningdata/src/main/java/com/whyranoid/runningdata/model/RunningFinishData.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.runningdata.model 2 | 3 | data class RunningFinishData( 4 | val runningHistory: RunningHistoryModel, 5 | val runningPositionList: List> 6 | ) : java.io.Serializable 7 | -------------------------------------------------------------------------------- /runningdata/src/main/java/com/whyranoid/runningdata/model/RunningHistoryModel.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.runningdata.model 2 | 3 | data class RunningHistoryModel( 4 | val historyId: String = "", 5 | val startedAt: Long = 0L, 6 | val finishedAt: Long = 0L, 7 | val totalRunningTime: Int = 0, 8 | val pace: Double = 0.0, 9 | val totalDistance: Double = 0.0 10 | ) : java.io.Serializable 11 | -------------------------------------------------------------------------------- /runningdata/src/main/java/com/whyranoid/runningdata/model/RunningPosition.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.runningdata.model 2 | 3 | data class RunningPosition( 4 | val latitude: Double, 5 | val longitude: Double 6 | ) : java.io.Serializable 7 | -------------------------------------------------------------------------------- /runningdata/src/main/java/com/whyranoid/runningdata/model/RunningState.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.runningdata.model 2 | 3 | sealed interface RunningState { 4 | val runningData: RunningData 5 | 6 | data class NotRunning(override val runningData: RunningData = RunningData()) : RunningState 7 | 8 | data class Running(override val runningData: RunningData) : RunningState 9 | 10 | data class Paused(override val runningData: RunningData) : RunningState 11 | } 12 | -------------------------------------------------------------------------------- /runningdata/src/test/java/com/whyranoid/runningdata/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.runningdata 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | 9 | dependencyResolutionManagement { 10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 11 | repositories { 12 | google() 13 | mavenCentral() 14 | maven { 15 | url "https://jitpack.io" 16 | } 17 | maven { 18 | url "https://naver.jfrog.io/artifactory/maven/" 19 | } 20 | } 21 | } 22 | rootProject.name = "MoGakRun" 23 | include ":app" 24 | include ":domain" 25 | include ":data" 26 | include ":presentation" 27 | include ":signin" 28 | include ":runningdata" 29 | -------------------------------------------------------------------------------- /signin/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /signin/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampwm-2022/android05-MoGakRun/35d6e401aaba33d3dd2c5fe8201bdb012ba0f3d8/signin/consumer-rules.pro -------------------------------------------------------------------------------- /signin/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. 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 -------------------------------------------------------------------------------- /signin/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /signin/src/main/java/com/whyranoid/RestoreRunningHistoryDataUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid 2 | 3 | import javax.inject.Inject 4 | 5 | class RestoreRunningHistoryDataUseCase @Inject constructor(private val signInRepository: SignInRepository) { 6 | suspend operator fun invoke(uid: String): Result { 7 | return signInRepository.restoreRunningHistoryData(uid) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /signin/src/main/java/com/whyranoid/SaveLogInUserInfoUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid 2 | 3 | import javax.inject.Inject 4 | 5 | class SaveLogInUserInfoUseCase @Inject constructor( 6 | private val signInRepository: SignInRepository 7 | ) { 8 | suspend operator fun invoke(userInfo: SignInUserInfo): Boolean { 9 | return signInRepository.saveLogInUserInfo(userInfo) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /signin/src/main/java/com/whyranoid/SignInDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid 2 | 3 | interface SignInDataSource { 4 | suspend fun saveLogInUserInfo(userInfo: SignInUserInfo): Boolean 5 | suspend fun restoreRunningHistoryData(uid: String): Result 6 | } 7 | -------------------------------------------------------------------------------- /signin/src/main/java/com/whyranoid/SignInModule.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import javax.inject.Singleton 8 | 9 | @Module 10 | @InstallIn(SingletonComponent::class) 11 | abstract class SignInModule { 12 | 13 | @Binds 14 | @Singleton 15 | abstract fun bindSignInRepository(signInRepositoryImpl: SignInRepositoryImpl): SignInRepository 16 | 17 | @Binds 18 | @Singleton 19 | abstract fun bindSignInDataSource(signInDataSourceImpl: SignInDataSourceImpl): SignInDataSource 20 | } 21 | -------------------------------------------------------------------------------- /signin/src/main/java/com/whyranoid/SignInRepository.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid 2 | 3 | interface SignInRepository { 4 | suspend fun saveLogInUserInfo(userInfo: SignInUserInfo): Boolean 5 | suspend fun restoreRunningHistoryData(uid: String): Result 6 | } 7 | -------------------------------------------------------------------------------- /signin/src/main/java/com/whyranoid/SignInRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid 2 | 3 | import javax.inject.Inject 4 | 5 | class SignInRepositoryImpl @Inject constructor(private val signInDataSource: SignInDataSource) : 6 | SignInRepository { 7 | 8 | override suspend fun saveLogInUserInfo(userInfo: SignInUserInfo): Boolean { 9 | return signInDataSource.saveLogInUserInfo(userInfo) 10 | } 11 | 12 | override suspend fun restoreRunningHistoryData(uid: String): Result { 13 | return signInDataSource.restoreRunningHistoryData(uid) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /signin/src/main/java/com/whyranoid/SignInUserInfo.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid 2 | 3 | data class SignInUserInfo( 4 | val uid: String, 5 | val email: String?, 6 | val nickName: String?, 7 | val profileImgUri: String? 8 | ) 9 | -------------------------------------------------------------------------------- /signin/src/main/java/com/whyranoid/SignInViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid 2 | 3 | import androidx.lifecycle.ViewModel 4 | import dagger.hilt.android.lifecycle.HiltViewModel 5 | import javax.inject.Inject 6 | 7 | @HiltViewModel 8 | class SignInViewModel @Inject constructor( 9 | private val saveLogInUserInfoUseCase: SaveLogInUserInfoUseCase, 10 | private val restoreRunningHistoryDataUseCase: RestoreRunningHistoryDataUseCase 11 | ) : ViewModel() { 12 | suspend fun saveUserInfo(userInfo: SignInUserInfo) { 13 | saveLogInUserInfoUseCase(userInfo) 14 | } 15 | 16 | suspend fun restoreRunningHistoryData(uid: String) { 17 | restoreRunningHistoryDataUseCase(uid) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /signin/src/main/res/layout/activity_sign_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 24 | 25 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /signin/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 로그인 화면의 모각런 로고입니다. 5 | 로그인 성공 6 | 네트워크 연결을 확인해주세요 7 | 구글 계정과 앱을 연동하는데 실패했어요 8 | -------------------------------------------------------------------------------- /signin/src/test/java/com/whyranoid/signin/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.whyranoid.signin 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } 17 | --------------------------------------------------------------------------------