├── .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 |
--------------------------------------------------------------------------------
/presentation/src/main/res/menu/community_create_running_post_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/presentation/src/main/res/menu/community_go_to_create_running_post_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/presentation/src/main/res/menu/community_select_running_history_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/presentation/src/main/res/menu/create_group_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/presentation/src/main/res/menu/group_detail_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/presentation/src/main/res/menu/my_group_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/presentation/src/main/res/menu/my_run_setting_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------