├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── juniori
│ │ └── puzzle
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ │ └── com
│ │ │ └── juniori
│ │ │ └── puzzle
│ │ │ ├── MainActivity.kt
│ │ │ ├── PuzzleApp.kt
│ │ │ ├── SingleLiveEvent.kt
│ │ │ ├── adapter
│ │ │ ├── ImageBindingAdapter.kt
│ │ │ ├── PuzzleBindingAdapter.kt
│ │ │ └── WeatherRecyclerViewAdapter.kt
│ │ │ ├── data
│ │ │ ├── Resource.kt
│ │ │ ├── auth
│ │ │ │ └── AuthRepositoryImpl.kt
│ │ │ ├── firebase
│ │ │ │ ├── FirestoreDataSource.kt
│ │ │ │ ├── FirestoreService.kt
│ │ │ │ ├── StorageDataSource.kt
│ │ │ │ ├── StorageService.kt
│ │ │ │ └── dto
│ │ │ │ │ ├── FirestoreDTO.kt
│ │ │ │ │ ├── FirestoreValues.kt
│ │ │ │ │ ├── StructuredQuery.kt
│ │ │ │ │ ├── UserItem.kt
│ │ │ │ │ └── VideoItem.kt
│ │ │ ├── location
│ │ │ │ ├── LocationDataSource.kt
│ │ │ │ ├── LocationDataSourceImpl.kt
│ │ │ │ ├── LocationInfo.kt
│ │ │ │ └── LocationRepositoryImpl.kt
│ │ │ ├── video
│ │ │ │ ├── VideoRepositoryImpl.kt
│ │ │ │ └── VideoRepositoryMockImpl.kt
│ │ │ └── weather
│ │ │ │ ├── WeatherDataSource.kt
│ │ │ │ ├── WeatherDataSourceImpl.kt
│ │ │ │ └── WeatherResponse.kt
│ │ │ ├── di
│ │ │ ├── AuthModule.kt
│ │ │ ├── DialogModule.kt
│ │ │ ├── HiltAnnotation.kt
│ │ │ ├── LocalCacheModule.kt
│ │ │ ├── LocationModule.kt
│ │ │ ├── NetworkModule.kt
│ │ │ ├── VideoModule.kt
│ │ │ └── WeatherModule.kt
│ │ │ ├── domain
│ │ │ ├── entity
│ │ │ │ ├── LocationInfoEntity.kt
│ │ │ │ ├── UserInfoEntity.kt
│ │ │ │ ├── VideoInfoEntity.kt
│ │ │ │ └── WeatherEntity.kt
│ │ │ ├── repository
│ │ │ │ ├── AuthRepository.kt
│ │ │ │ ├── LocationRepository.kt
│ │ │ │ └── VideoRepository.kt
│ │ │ └── usecase
│ │ │ │ ├── ChangeVideoScopeUseCase.kt
│ │ │ │ ├── DeleteVideoUseCase.kt
│ │ │ │ ├── GetAddressUseCase.kt
│ │ │ │ ├── GetMyVideoListUseCase.kt
│ │ │ │ ├── GetPublisherInfoUseCase.kt
│ │ │ │ ├── GetSearchedMyVideoUseCase.kt
│ │ │ │ ├── GetSearchedSocialVideoListUseCase.kt
│ │ │ │ ├── GetSocialVideoListUseCase.kt
│ │ │ │ ├── GetUserInfoUseCase.kt
│ │ │ │ ├── GetVideoFileUseCase.kt
│ │ │ │ ├── GetWeatherUseCase.kt
│ │ │ │ ├── PostUserInfoUseCase.kt
│ │ │ │ ├── PostVideoUseCase.kt
│ │ │ │ ├── RegisterLocationListenerUseCase.kt
│ │ │ │ ├── RequestLoginUseCase.kt
│ │ │ │ ├── RequestLogoutUseCase.kt
│ │ │ │ ├── RequestWithdrawUseCase.kt
│ │ │ │ ├── UnregisterLocationListenerUseCase.kt
│ │ │ │ ├── UpdateLikeStatusUseCase.kt
│ │ │ │ ├── UpdateNicknameUseCase.kt
│ │ │ │ └── WithdrawAccountUseCase.kt
│ │ │ ├── mock
│ │ │ └── MockVideoData.kt
│ │ │ ├── network
│ │ │ └── WeatherService.kt
│ │ │ ├── ui
│ │ │ ├── addvideo
│ │ │ │ ├── AddVideoBottomSheet.kt
│ │ │ │ ├── AddVideoUiState.kt
│ │ │ │ ├── AddVideoViewModel.kt
│ │ │ │ ├── camera
│ │ │ │ │ └── CameraActivity.kt
│ │ │ │ └── upload
│ │ │ │ │ ├── UploadStep1Fragment.kt
│ │ │ │ │ ├── UploadStep2Fragment.kt
│ │ │ │ │ └── UploadViewModel.kt
│ │ │ ├── home
│ │ │ │ ├── HomeFragment.kt
│ │ │ │ └── HomeViewModel.kt
│ │ │ ├── login
│ │ │ │ ├── LoginActivity.kt
│ │ │ │ └── LoginViewModel.kt
│ │ │ ├── mygallery
│ │ │ │ ├── MyGalleryAdapter.kt
│ │ │ │ ├── MyGalleryFragment.kt
│ │ │ │ └── MyGalleryViewModel.kt
│ │ │ ├── mypage
│ │ │ │ ├── MyPageFragment.kt
│ │ │ │ ├── MyPageViewModel.kt
│ │ │ │ ├── UpdateNicknameActivity.kt
│ │ │ │ └── UpdateNicknameViewModel.kt
│ │ │ ├── othersgallery
│ │ │ │ ├── OtherGalleryAdapter.kt
│ │ │ │ ├── OthersGalleryFragment.kt
│ │ │ │ └── OthersGalleryViewModel.kt
│ │ │ ├── playvideo
│ │ │ │ ├── PlayVideoActivity.kt
│ │ │ │ ├── PlayVideoBottomSheet.kt
│ │ │ │ └── PlayVideoViewModel.kt
│ │ │ └── sensor
│ │ │ │ ├── GolfBallView.kt
│ │ │ │ └── SensorActivity.kt
│ │ │ └── util
│ │ │ ├── Extensions.kt
│ │ │ ├── FileIOExtenstions.kt
│ │ │ ├── GalleryDiffCallBack.kt
│ │ │ ├── GalleryState.kt
│ │ │ ├── LiveDataUtils.kt
│ │ │ ├── PagingConst.kt
│ │ │ ├── PlayResultConst.kt
│ │ │ ├── ProgressDialog.kt
│ │ │ ├── PuzzleDialog.kt
│ │ │ ├── Query.kt
│ │ │ ├── SortType.kt
│ │ │ ├── StateManager.kt
│ │ │ ├── VideoMetaDataUtil.kt
│ │ │ └── WeatherConverter.kt
│ └── res
│ │ ├── drawable-hdpi
│ │ ├── flag.png
│ │ ├── golf_ball.png
│ │ ├── grass_rectangle.png
│ │ ├── home_background_image.png
│ │ └── home_icon.png
│ │ ├── drawable-mdpi
│ │ ├── flag.png
│ │ ├── golf_ball.png
│ │ ├── grass_rectangle.png
│ │ ├── home_background_image.png
│ │ └── home_icon.png
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable-xhdpi
│ │ ├── flag.png
│ │ ├── golf_ball.png
│ │ ├── grass_rectangle.png
│ │ ├── home_background_image.png
│ │ └── home_icon.png
│ │ ├── drawable-xxhdpi
│ │ ├── flag.png
│ │ ├── golf_ball.png
│ │ ├── grass_rectangle.png
│ │ ├── home_background_image.png
│ │ └── home_icon.png
│ │ ├── drawable-xxxhdpi
│ │ ├── flag.png
│ │ ├── golf_ball.png
│ │ ├── grass_rectangle.png
│ │ ├── home_background_image.png
│ │ └── home_icon.png
│ │ ├── drawable
│ │ ├── add_gallery_icon.xml
│ │ ├── add_photo_icon.xml
│ │ ├── all_location_icon.xml
│ │ ├── all_memo_background.xml
│ │ ├── all_outlined_box.xml
│ │ ├── all_popup_background.xml
│ │ ├── all_search_icon.xml
│ │ ├── all_search_view_background.xml
│ │ ├── all_toolbar_background.xml
│ │ ├── alpha_gradient_background.xml
│ │ ├── camera_button.xml
│ │ ├── camera_button_recording.xml
│ │ ├── cursor_background.xml
│ │ ├── home_refresh_icon.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── main_addvideoicon_24dp.xml
│ │ ├── main_bottommenucolorselector.xml
│ │ ├── main_homeicon_24dp.xml
│ │ ├── main_mygalleryicon_24dp.xml
│ │ ├── main_mypageicon_24dp.xml
│ │ ├── main_othersgalleryicon_24dp.xml
│ │ ├── my_gallery_add_icon_outlined.xml
│ │ ├── play_calendar_icon.xml
│ │ ├── play_comment_icon.xml
│ │ ├── play_go_back.xml
│ │ ├── play_like_not_selected.xml
│ │ ├── play_like_selected.xml
│ │ ├── play_setting.xml
│ │ ├── splash_background.9.png
│ │ ├── text_container_background.xml
│ │ ├── upload2_golfcourse_24dp.xml
│ │ └── upload2_time_icon_24dp.xml
│ │ ├── font
│ │ ├── noto_sans_kr_medium.otf
│ │ └── noto_sans_kr_regular.otf
│ │ ├── layout
│ │ ├── activity_camera.xml
│ │ ├── activity_login.xml
│ │ ├── activity_main.xml
│ │ ├── activity_playvideo.xml
│ │ ├── activity_sensor.xml
│ │ ├── activity_update_nickname.xml
│ │ ├── bottomsheet_addvideo.xml
│ │ ├── bottomsheet_playvideo.xml
│ │ ├── dialog_progress.xml
│ │ ├── fragment_home.xml
│ │ ├── fragment_mygallery.xml
│ │ ├── fragment_mypage.xml
│ │ ├── fragment_othersgallery.xml
│ │ ├── fragment_upload_step1.xml
│ │ ├── fragment_upload_step2.xml
│ │ ├── item_gallery_recycler.xml
│ │ ├── item_information.xml
│ │ ├── item_popup_list.xml
│ │ ├── item_weather_detail.xml
│ │ └── loading_layout.xml
│ │ ├── menu
│ │ ├── main_bottomnavigation.xml
│ │ └── playvideo_menu.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── navigation
│ │ └── main_bottomnavigation.xml
│ │ ├── raw
│ │ ├── loading_golf.json
│ │ ├── loading_progress.json
│ │ └── network_fail.json
│ │ ├── values-en
│ │ └── strings.xml
│ │ ├── values-night
│ │ └── colors.xml
│ │ ├── values-sw600dp
│ │ └── dimens.xml
│ │ ├── values
│ │ ├── array.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── fonts.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── shapes.xml
│ │ ├── strings.xml
│ │ ├── styles.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ └── test
│ └── java
│ └── com
│ └── juniori
│ └── puzzle
│ ├── ExampleUnitTest.kt
│ ├── data
│ └── firebase
│ │ └── dto
│ │ └── VideoItemConvertTest.kt
│ └── ui
│ ├── home
│ └── HomeViewModelTest.kt
│ ├── login
│ └── LoginViewModelTest.kt
│ ├── mygallery
│ ├── MyGalleryPagingTest.kt
│ └── MyGalleryViewModelTest.kt
│ └── mypage
│ └── UpdateNicknameUseCaseTest.kt
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 |
4 |
5 | 
6 |
7 |
8 | # 프로젝트 개요
9 |
10 | ### 간직하고 싶은 나만의 퍼팅 모음
11 |
12 | 골프에서 짧지만 중요한 순간 중 하나인 퍼팅을 기록해 다른 사람과 공유하고 볼 수 있는 공간을 제공합니다.
13 |
14 | # 주요 기능
15 |
16 | ### 홈
17 |
18 | |초기 화면|그린 기울기 구하기|
19 | |:--:|:--:|
20 | |
|
|
21 |
22 | - 환영 문구가 매번 달라집니다.
23 | - 현재 위치의 날씨를 볼 수 있습니다.
24 | - 그린 기울기 구하기를 통해 원하는 위치의 땅의 높낮이를 알 수 있습니다.
25 |
26 | ### 나의 갤러리 및 플레이
27 |
28 | |초기 화면|공유 상태 전환|삭제하기|
29 | |:--:|:--:|:--:|
30 | |
|
|
|
31 |
32 | - 나의 퍼팅 동영상을 볼 수 있습니다.
33 | - 검색을 통해 원하는 골프장의 영상만 모아 볼 수 있습니다.
34 | - 영상을 삭제할 수 있습니다.
35 | - 영상을 나만 볼수도, 다른 사람들에게 보여줄수도 있습니다.
36 |
37 | ### 다른 사람 갤러리
38 |
39 | |초기 화면|검색|
40 | |:--:|:--:|
41 | |
|
|
42 |
43 | - 다른 사람의 퍼팅 동영상을 볼 수 있습니다.
44 | - 검색을 통해 원하는 골프장의 영상만 모아 볼 수 있습니다.
45 | - 최신순, 좋아요 순으로 정렬해서 볼 수 있습니다.
46 |
47 | ### 영상 플레이
48 |
49 | |영상 조회 및 좋아요|영상 정보 조회|스와이프|
50 | |:--:|:--:|:--:|
51 | |
|
|
|
52 |
53 | - 해당 영상에 좋아요를 누를 수 있습니다.
54 | - 해당 영상의 정보를 볼 수 있습니다.
55 | - 스와이프를 해서 간편하게 다른 영상으로 넘어갈수 있습니다.
56 |
57 | ### 촬영
58 |
59 | |갤러리 선택 후 업로드|촬영 후 업로드|
60 | |:--:|:--:|
61 | |
|
|
62 |
63 | - 앱 내에서 직접 영상을 찍을 수 있습니다.
64 | - 갤러리에서 원하는 동영상을 선택해 올릴 수 있습니다.
65 | - 동영상 정보를 작성할 수 있습니다.
66 |
67 | ### 마이페이지
68 |
69 | |닉네임 변경|로그아웃 및 회원탈퇴|
70 | |:--:|:--:|
71 | |
|
|
72 |
73 | - 닉네임을 변경할 수 있습니다.
74 | - 로그아웃, 탈퇴를 할 수 있습니다.
75 |
76 | # 기술적 도전 및 이슈
77 |
78 | [기술적 도전 보러가기](https://github.com/boostcampwm-2022/android07-Puzzle/wiki/%EA%B8%B0%EC%88%A0-%EC%A1%B0%EC%82%AC)
79 |
80 | # 팀원 소개
81 | |K002|K043|K052|K059|K060|
82 | |:--:|:--:|:--:|:--:|:--:|
83 | |김근성|장주원|주재완|홍상호|황유란|
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/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/androidTest/java/com/juniori/puzzle/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.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.juniori.puzzle", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
26 |
30 |
35 |
39 |
43 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
7 | import androidx.navigation.fragment.NavHostFragment
8 | import androidx.navigation.ui.setupWithNavController
9 | import com.google.android.material.bottomnavigation.BottomNavigationItemView
10 | import com.juniori.puzzle.databinding.ActivityMainBinding
11 | import dagger.hilt.android.AndroidEntryPoint
12 |
13 | @AndroidEntryPoint
14 | class MainActivity : AppCompatActivity() {
15 |
16 | private lateinit var binding: ActivityMainBinding
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | installSplashScreen()
20 | super.onCreate(savedInstanceState)
21 |
22 | binding = ActivityMainBinding.inflate(layoutInflater)
23 | setContentView(binding.root)
24 |
25 | initNavigationComponent()
26 | }
27 |
28 | private fun initNavigationComponent() {
29 | val navController = NavHostFragment.findNavController(
30 | supportFragmentManager.findFragmentById(R.id.fragmentcontainerview) as NavHostFragment
31 | )
32 | binding.bottomnavigationview.setupWithNavController(navController)
33 | // 추가하기 메뉴를 눌렀을 때 현재 프래그먼트를 유지하면서 다이얼로그를 보여준다.
34 | findViewById(R.id.bottomsheet_main_addvideo).setOnClickListener {
35 | navController.navigate(R.id.bottomsheet_main_addvideo)
36 | }
37 |
38 | navController.addOnDestinationChangedListener { _, destination, _ ->
39 | binding.bottomnavigationview.visibility = when (destination.id) {
40 | R.id.fragment_upload_step1, R.id.fragment_upload_step2 -> View.GONE
41 | else -> View.VISIBLE
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/PuzzleApp.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 |
6 | @HiltAndroidApp
7 | class PuzzleApp : Application() {
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/SingleLiveEvent.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle
2 |
3 | import android.util.Log
4 | import androidx.annotation.MainThread
5 | import androidx.annotation.Nullable
6 | import androidx.lifecycle.LifecycleOwner
7 | import androidx.lifecycle.MutableLiveData
8 | import androidx.lifecycle.Observer
9 | import java.util.concurrent.atomic.AtomicBoolean;
10 |
11 |
12 | class SingleLiveEvent : MutableLiveData() {
13 |
14 | companion object {
15 | private const val TAG = "SingleLiveEvent"
16 | }
17 |
18 | val mPending: AtomicBoolean = AtomicBoolean(false)
19 |
20 | override fun observe(owner: LifecycleOwner, observer: Observer) {
21 | if (hasActiveObservers()) {
22 | Log.w(TAG,"Multiple observers registered but only one will be notified of changes.")
23 | }
24 |
25 | // Observe the internal MutableLiveData
26 | super.observe(owner, Observer { t ->
27 | if (mPending.compareAndSet(true, false)) {
28 | observer.onChanged(t)
29 | }
30 | })
31 | }
32 |
33 | @MainThread
34 | override fun setValue(t: T?) {
35 | mPending.set(true)
36 | super.setValue(t)
37 | }
38 |
39 | /**
40 | * Used for cases where T is Void, to make calls cleaner.
41 | */
42 | @MainThread
43 | fun call() {
44 | value = null
45 | }
46 |
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/adapter/PuzzleBindingAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.adapter
2 |
3 | import android.widget.TextView
4 | import androidx.constraintlayout.widget.ConstraintLayout
5 | import androidx.core.view.isVisible
6 | import androidx.databinding.BindingAdapter
7 | import androidx.recyclerview.widget.ListAdapter
8 | import androidx.recyclerview.widget.RecyclerView
9 | import com.google.android.material.button.MaterialButton
10 | import com.juniori.puzzle.R
11 | import com.juniori.puzzle.data.Resource
12 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
13 | import java.util.Calendar
14 | import java.util.Date
15 |
16 | private val calendar = Calendar.getInstance()
17 |
18 | @BindingAdapter("setAdapter")
19 | fun setAdapter(view: RecyclerView, itemList: List) {
20 | val adapter = view.adapter as ListAdapter
21 | adapter.submitList(itemList)
22 | }
23 |
24 | @BindingAdapter("setLikeCount")
25 | fun setLikeCount(view: MaterialButton, updateFlow: Resource?) {
26 | if (updateFlow is Resource.Success) {
27 | view.text = updateFlow.result.likedCount.toString()
28 | }
29 | }
30 |
31 | @BindingAdapter("setFullDate")
32 | fun setFullDate(view: TextView, date: Date) {
33 | calendar.time = date
34 |
35 | view.text = String.format(
36 | view.context.getString(R.string.full_date_format),
37 | calendar.get(Calendar.YEAR),
38 | calendar.get(Calendar.MONTH) + 1,
39 | calendar.get(Calendar.DATE),
40 | calendar.get(Calendar.HOUR_OF_DAY),
41 | calendar.get(Calendar.MINUTE)
42 | )
43 | }
44 |
45 | @BindingAdapter("setTime")
46 | fun setTime(view: TextView, date: Date) {
47 | calendar.time = date
48 | val amText = view.context.getString(R.string.time_AM_date_format)
49 | val pmText = view.context.getString(R.string.time_PM_date_format)
50 | val hour = calendar.get(Calendar.HOUR_OF_DAY)
51 | view.text = if (hour > 12) {
52 | String.format(pmText, hour - 12)
53 | } else if (hour == 12) {
54 | String.format(pmText, hour)
55 | } else {
56 | String.format(amText, hour)
57 | }
58 | }
59 |
60 | @BindingAdapter("setDisplayName")
61 | fun setDisplayName(view: TextView, name: String) {
62 | view.text = if (name.isNotEmpty()) {
63 | String.format(view.context.getString(R.string.display_name_format), name)
64 | } else {
65 | String.format(
66 | view.context.getString(R.string.display_name_format),
67 | view.context.getString(R.string.display_anonymous)
68 | )
69 | }
70 | }
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/adapter/WeatherRecyclerViewAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.adapter
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.juniori.puzzle.databinding.ItemWeatherDetailBinding
9 | import com.juniori.puzzle.domain.entity.WeatherEntity
10 |
11 | class WeatherRecyclerViewAdapter :
12 | ListAdapter(diffUtil) {
13 | inner class WeatherViewHolder(val binding: ItemWeatherDetailBinding) :
14 | RecyclerView.ViewHolder(binding.root) {
15 |
16 | fun bind(weatherItem: WeatherEntity) {
17 | binding.item = weatherItem
18 | }
19 | }
20 |
21 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WeatherViewHolder {
22 | return WeatherViewHolder(
23 | ItemWeatherDetailBinding.inflate(
24 | LayoutInflater.from(parent.context),
25 | parent,
26 | false
27 | )
28 | )
29 | }
30 |
31 | override fun onBindViewHolder(holder: WeatherViewHolder, position: Int) {
32 | return holder.bind(currentList[position])
33 | }
34 |
35 | companion object {
36 | private val diffUtil = object : DiffUtil.ItemCallback() {
37 | override fun areItemsTheSame(oldItem: WeatherEntity, newItem: WeatherEntity): Boolean {
38 | return oldItem.date == newItem.date
39 | }
40 |
41 | override fun areContentsTheSame(
42 | oldItem: WeatherEntity,
43 | newItem: WeatherEntity
44 | ): Boolean {
45 | return oldItem == newItem
46 | }
47 | }
48 | }
49 |
50 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/Resource.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data
2 |
3 | sealed class Resource {
4 | data class Success(val result: R) : Resource()
5 | data class Failure(val exception: Exception) : Resource()
6 | object Loading : Resource()
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/firebase/FirestoreService.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.firebase
2 |
3 | import com.juniori.puzzle.data.firebase.dto.RunQueryRequestDTO
4 | import com.juniori.puzzle.data.firebase.dto.RunQueryResponseDTO
5 | import com.juniori.puzzle.data.firebase.dto.UserDetail
6 | import com.juniori.puzzle.data.firebase.dto.UserItem
7 | import com.juniori.puzzle.data.firebase.dto.VideoDetail
8 | import com.juniori.puzzle.data.firebase.dto.VideoItem
9 | import retrofit2.http.Body
10 | import retrofit2.http.DELETE
11 | import retrofit2.http.GET
12 | import retrofit2.http.PATCH
13 | import retrofit2.http.POST
14 | import retrofit2.http.Path
15 | import retrofit2.http.Query
16 |
17 | interface FirestoreService {
18 | @DELETE("databases/(default)/documents/videoReal/{documentId}")
19 | suspend fun deleteVideoItemDocument(
20 | @Path("documentId") documentId: String,
21 | )
22 |
23 | @PATCH("databases/(default)/documents/videoReal/{documentId}")
24 | suspend fun patchVideoItemDocument(
25 | @Path("documentId") documentId: String,
26 | @Body fields: Map
27 | ): VideoItem
28 |
29 | @POST("databases/(default)/documents/videoReal")
30 | suspend fun createVideoItemDocument(
31 | @Query("documentId") documentId: String,
32 | @Body fields: Map
33 | ): VideoItem
34 |
35 | @POST("databases/(default)/documents:runQuery")
36 | suspend fun getFirebaseItemByQuery(
37 | @Body fields: RunQueryRequestDTO
38 | ): List
39 |
40 | @GET("databases/(default)/documents/userReal/{documentId}")
41 | suspend fun getUserItemDocument(
42 | @Path("documentId") documentId: String,
43 | ): UserItem
44 |
45 | @PATCH("databases/(default)/documents/userReal/{documentId}")
46 | suspend fun patchUserItemDocument(
47 | @Path("documentId") documentId: String,
48 | @Body fields: Map
49 | ): UserItem
50 |
51 | @POST("databases/(default)/documents/userReal")
52 | suspend fun createUserItemDocument(
53 | @Query("documentId") documentId: String,
54 | @Body fields: Map
55 | ): UserItem
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/firebase/StorageDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.firebase
2 |
3 | import okhttp3.MediaType
4 | import okhttp3.RequestBody
5 | import javax.inject.Inject
6 |
7 | class StorageDataSource @Inject constructor(
8 | private val service: StorageService
9 | ) {
10 | suspend fun deleteVideo(
11 | name: String
12 | ): Result {
13 | return try {
14 | val result = service.delete("video/$name")
15 | if (result.code() >= 400) {
16 | Result.failure(Exception(result.message()))
17 | } else {
18 | Result.success(result.code())
19 | }
20 | } catch (e: Exception) {
21 | Result.failure(e)
22 | }
23 | }
24 |
25 | suspend fun deleteThumbnail(
26 | name: String
27 | ): Result {
28 | return try {
29 | val result = service.delete("thumb/$name")
30 | if (result.code() >= 400) {
31 | Result.failure(Exception(result.message()))
32 | } else {
33 | Result.success(result.code())
34 | }
35 | } catch (e: Exception) {
36 | Result.failure(e)
37 | }
38 | }
39 |
40 | suspend fun insertVideo(
41 | name: String,
42 | fileByteArray: ByteArray
43 | ): Result {
44 | return try {
45 | service.insert(
46 | "video/$name", body = RequestBody.create(
47 | MediaType.parse("video/mp4"),
48 | fileByteArray
49 | )
50 | )
51 | } catch (e: Exception) {
52 | Result.failure(e)
53 | }
54 | }
55 |
56 | suspend fun insertThumbnail(
57 | name: String,
58 | fileByteArray: ByteArray
59 | ): Result {
60 | return try {
61 | service.insert(
62 | "thumb/$name", body = RequestBody.create(
63 | MediaType.parse("image/jpeg"),
64 | fileByteArray
65 | )
66 | )
67 | } catch (e: Exception) {
68 | Result.failure(e)
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/firebase/StorageService.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.firebase
2 |
3 | import okhttp3.RequestBody
4 | import retrofit2.Response
5 | import retrofit2.http.Body
6 | import retrofit2.http.DELETE
7 | import retrofit2.http.POST
8 | import retrofit2.http.Path
9 | import retrofit2.http.Query
10 |
11 | interface StorageService {
12 | @DELETE("o/{name}")
13 | suspend fun delete(
14 | @Path("name") name: String,
15 | ): Response
16 |
17 | @POST("o")
18 | suspend fun insert(
19 | @Query("name") name: String,
20 | @Query("uploadType") uploadType: String = "media",
21 | @Body body: RequestBody
22 | ): Result
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/firebase/dto/FirestoreDTO.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.firebase.dto
2 |
3 | import com.google.gson.annotations.SerializedName
4 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
5 |
6 | data class RunQueryRequestDTO(
7 | val structuredQuery: StructuredQuery
8 | )
9 |
10 | data class RunQueryResponseDTO(
11 | @SerializedName("document") val videoItem: VideoItem?,
12 | @SerializedName("readTime") val readTime: String
13 | )
14 |
15 | fun List.getVideoInfoEntity(): List =
16 | filter { it.videoItem != null }.map {
17 | it.videoItem?.getVideoInfoEntity()
18 | ?: throw Exception("getVideoItem from ResponseDTO Failed")
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/firebase/dto/FirestoreValues.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.firebase.dto
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | data class StringValue(
6 | @SerializedName("stringValue") val stringValue: String
7 | )
8 |
9 | data class IntegerValue(
10 | @SerializedName("integerValue") val integerValue: Long
11 | )
12 |
13 | data class BooleanValue(
14 | @SerializedName("booleanValue") val booleanValue: Boolean
15 | )
16 |
17 | data class ArrayValue(
18 | @SerializedName("arrayValue") val arrayValue: StringValues
19 | )
20 |
21 | data class StringValues(
22 | @SerializedName("values") val values: List?
23 | )
24 |
25 | fun List.toStringValues() = StringValues(this.map { StringValue(it) })
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/firebase/dto/StructuredQuery.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.firebase.dto
2 |
3 | data class StructuredQuery(
4 | val from: List = listOf(
5 | CollectionSelector(
6 | collectionId = "videoReal",
7 | allDescendants = true
8 | )
9 | ),
10 | val where: Filters,
11 | val orderBy: List? = null,
12 | val offset: Int?,
13 | val limit: Int?
14 | )
15 |
16 | data class CollectionSelector(
17 | val collectionId: String,
18 | val allDescendants: Boolean = false
19 | )
20 |
21 | sealed interface Filters
22 |
23 | data class Where(
24 | val compositeFilter: CompositeFilter
25 | ) : Filters
26 |
27 | data class CompositeFilter(
28 | val filters: List,
29 | val op: String
30 | )
31 |
32 | data class Filter(
33 | val fieldFilter: FieldFilter
34 | ) : Filters
35 |
36 | sealed interface FieldFilter
37 |
38 | data class BooleanFieldFilter(
39 | val field: FieldReference,
40 | val op: String,
41 | val value: BooleanValue
42 | ) : FieldFilter
43 |
44 | data class StringFieldFilter(
45 | val field: FieldReference,
46 | val op: String,
47 | val value: StringValue
48 | ) : FieldFilter
49 |
50 | data class IntegerFieldFilter(
51 | val field: FieldReference,
52 | val op: String,
53 | val value: IntegerValue
54 | ) : FieldFilter
55 |
56 | data class FieldReference(
57 | val fieldPath: String
58 | )
59 |
60 | data class Order(
61 | val field: FieldReference,
62 | val direction: String
63 | )
64 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/firebase/dto/UserItem.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.firebase.dto
2 |
3 | import com.google.gson.annotations.SerializedName
4 | import com.juniori.puzzle.domain.entity.UserInfoEntity
5 |
6 | data class UserItem(
7 | @SerializedName("name") val uid: String,
8 | @SerializedName("fields") val userDetail: UserDetail,
9 | @SerializedName("createTime") val createTime: String? = null,
10 | @SerializedName("updateTime") val updateTime: String? = null
11 | ) {
12 | fun getUserInfoEntity(): UserInfoEntity {
13 | return userDetail.toUserInfoEntity(uid.substringAfter("userReal/"))
14 | }
15 | }
16 |
17 | data class UserDetail(
18 | @SerializedName("user_display_name") val nickname: StringValue,
19 | @SerializedName("profile_image") val profileImage: StringValue,
20 | ) {
21 | fun toUserInfoEntity(documentId: String): UserInfoEntity {
22 | return UserInfoEntity(
23 | documentId,
24 | nickname.stringValue,
25 | profileImage.stringValue,
26 | null
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/firebase/dto/VideoItem.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.firebase.dto
2 |
3 | import com.google.gson.annotations.SerializedName
4 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
5 |
6 | data class VideoItem(
7 | @SerializedName("name") val videoName: String,
8 | @SerializedName("fields") val videoDetail: VideoDetail,
9 | @SerializedName("createTime") val createTime: String? = null,
10 | @SerializedName("updateTime") val updateTime: String? = null
11 | ) {
12 | fun getVideoInfoEntity(): VideoInfoEntity {
13 | return videoDetail.toVideoInfoEntity(videoName.substringAfter("videoReal/"))
14 | }
15 | }
16 |
17 | data class VideoDetail(
18 | @SerializedName("owner_uid") val ownerUid: StringValue,
19 | @SerializedName("video_url") val videoUrl: StringValue,
20 | @SerializedName("thumb_url") val thumbUrl: StringValue,
21 | @SerializedName("is_private") val isPrivate: BooleanValue,
22 | @SerializedName("like_count") val likeCount: IntegerValue,
23 | @SerializedName("liked_user_list") val likedUserList: ArrayValue,
24 | @SerializedName("update_time") val updateTime: IntegerValue,
25 | @SerializedName("location") val location: StringValue,
26 | @SerializedName("location_keyword") val locationKeyword: ArrayValue,
27 | @SerializedName("memo") val memo: StringValue,
28 | ) {
29 | fun toVideoInfoEntity(documentId: String): VideoInfoEntity {
30 | return VideoInfoEntity(
31 | documentId,
32 | ownerUid.stringValue,
33 | videoUrl.stringValue,
34 | thumbUrl.stringValue,
35 | isPrivate.booleanValue,
36 | likeCount.integerValue.toInt(),
37 | likedUserList.arrayValue.values?.map { it.stringValue } ?: listOf(),
38 | updateTime.integerValue,
39 | location.stringValue,
40 | locationKeyword.arrayValue.values?.map {it.stringValue} ?: listOf(),
41 | memo.stringValue
42 | )
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/location/LocationDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.location
2 |
3 | import android.location.Address
4 | import androidx.core.location.LocationListenerCompat
5 |
6 | interface LocationDataSource {
7 | fun registerLocationListener(listener: LocationListenerCompat): Boolean
8 | fun unregisterLocationListener()
9 | fun getCurrentAddress(lat: Double, long: Double): List
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/location/LocationDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.location
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.location.Address
6 | import android.location.Geocoder
7 | import android.location.LocationManager
8 | import androidx.core.location.LocationListenerCompat
9 | import dagger.hilt.android.qualifiers.ApplicationContext
10 | import javax.inject.Inject
11 |
12 | class LocationDataSourceImpl @Inject constructor(
13 | @ApplicationContext context: Context
14 | ) : LocationDataSource {
15 | private val locationManager: LocationManager =
16 | context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
17 | private val geoCoder = Geocoder(context)
18 | private var locationListener: LocationListenerCompat? = null
19 |
20 | @SuppressLint("MissingPermission")
21 | override fun registerLocationListener(listener: LocationListenerCompat): Boolean {
22 | return if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
23 |
24 | if (locationListener == null) {
25 | locationListener = listener
26 | locationManager.requestLocationUpdates(
27 | LocationManager.NETWORK_PROVIDER,
28 | LOCATION_MIN_TIME_INTERVAL,
29 | LOCATION_MIN_DISTANCE_INTERVAL,
30 | locationListener!!
31 | )
32 | }
33 | true
34 | } else {
35 | false
36 | }
37 | }
38 |
39 | @SuppressLint("MissingPermission")
40 | override fun unregisterLocationListener() {
41 | locationListener?.let {
42 | locationManager.removeUpdates(it)
43 | locationListener = null
44 | }
45 | }
46 |
47 | override fun getCurrentAddress(lat: Double, long: Double): List {
48 | return try {
49 | geoCoder.getFromLocation(lat, long, ADDRESS_MAX_RESULT)
50 | } catch (e: Exception) {
51 | emptyList()
52 | }
53 | }
54 |
55 | companion object {
56 | private const val LOCATION_MIN_TIME_INTERVAL = 3000L
57 | private const val LOCATION_MIN_DISTANCE_INTERVAL = 30f
58 | private const val ADDRESS_MAX_RESULT = 1
59 | }
60 |
61 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/location/LocationInfo.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.location
2 |
3 | data class LocationInfo(
4 | val lat:Double,
5 | val lon:Double
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/location/LocationRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.location
2 |
3 | import android.location.Address
4 | import androidx.core.location.LocationListenerCompat
5 | import com.juniori.puzzle.data.Resource
6 | import com.juniori.puzzle.data.weather.WeatherDataSource
7 | import com.juniori.puzzle.domain.entity.WeatherEntity
8 | import com.juniori.puzzle.domain.repository.LocationRepository
9 | import javax.inject.Inject
10 |
11 | class LocationRepositoryImpl @Inject constructor(
12 | private val locationDataSource: LocationDataSource,
13 | private val weatherDataSource: WeatherDataSource,
14 | ) : LocationRepository {
15 | override fun registerLocationListener(listener: LocationListenerCompat): Boolean {
16 | return locationDataSource.registerLocationListener(listener)
17 | }
18 |
19 | override fun unregisterLocationListener() {
20 | locationDataSource.unregisterLocationListener()
21 | }
22 |
23 | override fun getAddressInfo(lat: Double, long: Double): List {
24 | return locationDataSource.getCurrentAddress(lat, long)
25 | }
26 |
27 | override suspend fun getWeatherInfo(lat: Double, long: Double): Resource> {
28 | return weatherDataSource.getWeather(lat, long)
29 | }
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/video/VideoRepositoryMockImpl.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.video
2 |
3 | import com.juniori.puzzle.data.Resource
4 | import com.juniori.puzzle.domain.entity.UserInfoEntity
5 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
6 | import com.juniori.puzzle.domain.repository.VideoRepository
7 | import com.juniori.puzzle.util.SortType
8 | import javax.inject.Inject
9 |
10 | class VideoRepositoryMockImpl @Inject constructor(private val videoList: List) :
11 | VideoRepository {
12 | override suspend fun getMyVideoList(uid: String, index: Int): Resource> {
13 | return Resource.Success(
14 | videoList.filter { videoInfoEntity -> videoInfoEntity.ownerUid == uid }
15 | )
16 | }
17 |
18 | override suspend fun getSearchedMyVideoList(
19 | uid: String,
20 | index: Int,
21 | keyword: String
22 | ): Resource> {
23 | return Resource.Success(
24 | videoList.filter { videoInfoEntity -> videoInfoEntity.ownerUid == uid }
25 | .filter { videoInfoEntity -> videoInfoEntity.location == keyword }
26 | )
27 | }
28 |
29 | override suspend fun getSocialVideoList(
30 | index: Int,
31 | sortType: SortType,
32 | latestData: Long?
33 | ): Resource> {
34 | TODO("Not yet implemented")
35 | }
36 |
37 | override suspend fun getSearchedSocialVideoList(
38 | index: Int,
39 | sortType: SortType,
40 | keyword: String,
41 | latestData: Long?
42 | ): Resource> {
43 | TODO("Not yet implemented")
44 | }
45 |
46 | override suspend fun updateLikeStatus(
47 | documentInfo: VideoInfoEntity,
48 | uid: String,
49 | isLiked: Boolean
50 | ): Resource {
51 | TODO("Not yet implemented")
52 | }
53 |
54 | override suspend fun deleteVideo(documentId: String): Resource {
55 | TODO("Not yet implemented")
56 | }
57 |
58 | override suspend fun changeVideoScope(documentInfo: VideoInfoEntity): Resource {
59 | TODO("Not yet implemented")
60 | }
61 |
62 | override suspend fun uploadVideo(
63 | uid: String,
64 | videoName: String,
65 | isPrivate: Boolean,
66 | location: String,
67 | memo: String,
68 | videoByteArray: ByteArray,
69 | imageByteArray: ByteArray
70 | ): Resource {
71 | TODO("Not yet implemented")
72 | }
73 |
74 | override suspend fun getUserInfoByUidUseCase(uid: String): Resource {
75 | TODO("Not yet implemented")
76 | }
77 |
78 | override suspend fun postUserInfoInFirestore(
79 | uid: String,
80 | nickname: String,
81 | profileImage: String
82 | ): Resource {
83 | TODO("Not yet implemented")
84 | }
85 |
86 | override suspend fun updateServerNickname(userInfoEntity: UserInfoEntity): Resource {
87 | TODO("Not yet implemented")
88 | }
89 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/weather/WeatherDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.weather
2 |
3 | import com.juniori.puzzle.data.Resource
4 | import com.juniori.puzzle.domain.entity.WeatherEntity
5 |
6 | interface WeatherDataSource {
7 | suspend fun getWeather(lat: Double, lon: Double): Resource>
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/weather/WeatherDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.weather
2 |
3 | import com.juniori.puzzle.data.Resource
4 | import com.juniori.puzzle.domain.entity.WeatherEntity
5 | import com.juniori.puzzle.network.WeatherService
6 | import com.juniori.puzzle.util.WEATHER_SERVICE_KEY
7 | import com.juniori.puzzle.util.toItem
8 | import java.util.*
9 | import javax.inject.Inject
10 |
11 | class WeatherDataSourceImpl @Inject constructor(
12 | private val service: WeatherService
13 | ) : WeatherDataSource {
14 |
15 | private val language = when (Locale.getDefault().language) {
16 | "ko" -> {
17 | "kr"
18 | }
19 | "en" -> {
20 | "en"
21 | }
22 | else -> {
23 | "ko"
24 | }
25 | }
26 |
27 | override suspend fun getWeather(lat: Double, lon: Double): Resource> {
28 | if (lat < -90 || lat > 90 || lon < -180 || lon > 180) return Resource.Failure(Exception())
29 |
30 | return try {
31 | val response = service.getWeather(lat, lon, WEATHER_SERVICE_KEY, language)
32 | val result = response.body()?.toItem() ?: emptyList()
33 | if (result.size >= 3) {
34 | Resource.Success(result)
35 | } else {
36 | Resource.Failure(Exception())
37 | }
38 | } catch (e: Exception) {
39 | Resource.Failure(Exception())
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/data/weather/WeatherResponse.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.weather
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | data class WeatherResponse(
6 | val cod: String,
7 | val message: Int,
8 | val cnt: Int,
9 | val list: List,
10 | val city: WeatherCityResponse
11 | )
12 |
13 | data class WeatherListResponse(
14 | val dt: Long,
15 | val main: WeatherMainResponse,
16 | val weather: List,
17 | val cloud: WeatherCloudResponse,
18 | val wind: WeatherWindResponse,
19 | val visibility: Int,
20 | val pop: Float,
21 | val rain: WeatherRainResponse,
22 | val sys: WeatherSysResponse,
23 | @SerializedName("dt_txt") val dtTxt: String
24 | )
25 |
26 | data class WeatherMainResponse(
27 | val temp: Float,
28 | @SerializedName("feels_like") val feelsLike: Float,
29 | @SerializedName("temp_min") val tempMin: Float,
30 | @SerializedName("temp_max") val tempMax: Float,
31 | val pressure: Int,
32 | @SerializedName("sea_level") val seaLevel: Int,
33 | @SerializedName("grnd_level") val groundLevel: Int,
34 | val humidity: Int,
35 | @SerializedName("temp_kf") val tempKf: Float,
36 |
37 | )
38 |
39 | data class WeatherWeatherResponse(
40 | val id: Int,
41 | val main: String,
42 | val description: String,
43 | val icon: String
44 | )
45 |
46 | data class WeatherCloudResponse(
47 | val all: Int
48 | )
49 |
50 | data class WeatherWindResponse(
51 | val speed: Float,
52 | val deg: Int,
53 | val gust: Float
54 | )
55 |
56 | data class WeatherRainResponse(
57 | @SerializedName("3h") val threeH: Float
58 | )
59 |
60 | data class WeatherSysResponse(
61 | val pod: String
62 | )
63 |
64 | data class WeatherCityResponse(
65 | val id: Int,
66 | val name: String,
67 | val coord: WeatherCoordResponse,
68 | val country: String,
69 | val population: Int,
70 | val timezone: Int,
71 | val sunrise: Long,
72 | val sunset: Long
73 | )
74 |
75 | data class WeatherCoordResponse(
76 | val lat: Float,
77 | val lon: Float
78 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/di/AuthModule.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.di
2 |
3 | import com.google.firebase.auth.FirebaseAuth
4 | import com.juniori.puzzle.domain.repository.AuthRepository
5 | import com.juniori.puzzle.data.auth.AuthRepositoryImpl
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.components.SingletonComponent
10 |
11 | @InstallIn(SingletonComponent::class)
12 | @Module
13 | object AuthModule {
14 |
15 | @Provides
16 | fun provideFirebaseAuth(): FirebaseAuth = FirebaseAuth.getInstance()
17 |
18 | @Provides
19 | fun providesAuthRepository(impl: AuthRepositoryImpl): AuthRepository = impl
20 |
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/di/DialogModule.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.di
2 |
3 | import android.content.Context
4 | import com.juniori.puzzle.util.StateManager
5 | import dagger.Module
6 | import dagger.Provides
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.android.components.ActivityComponent
9 | import dagger.hilt.android.qualifiers.ActivityContext
10 |
11 | @Module
12 | @InstallIn(ActivityComponent::class)
13 | object DialogModule {
14 |
15 | @Provides
16 | fun providesDialog(@ActivityContext context: Context) = StateManager(context)
17 |
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/di/HiltAnnotation.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.di
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.BINARY)
7 | annotation class MockData
8 |
9 | @Qualifier
10 | @Retention(AnnotationRetention.BINARY)
11 | annotation class RealData
12 |
13 | @Qualifier
14 | @Retention(AnnotationRetention.BINARY)
15 | annotation class Storage
16 |
17 | @Qualifier
18 | @Retention(AnnotationRetention.BINARY)
19 | annotation class Weather
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/di/LocalCacheModule.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.di
2 |
3 | import android.content.Context
4 | import dagger.Module
5 | import dagger.Provides
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.android.qualifiers.ApplicationContext
8 | import dagger.hilt.components.SingletonComponent
9 | import javax.inject.Named
10 | import javax.inject.Singleton
11 |
12 | @Module
13 | @InstallIn(SingletonComponent::class)
14 | object LocalCacheModule {
15 |
16 | const val CACHE_DIR_PATH = "CACHE_DIR_PATH"
17 |
18 | @Singleton
19 | @Provides
20 | @Named(CACHE_DIR_PATH)
21 | fun provideCacheDirPath(@ApplicationContext context: Context): String =
22 | context.cacheDir.path
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/di/LocationModule.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.di
2 |
3 | import com.juniori.puzzle.data.location.LocationDataSource
4 | import com.juniori.puzzle.data.location.LocationDataSourceImpl
5 | import com.juniori.puzzle.data.location.LocationRepositoryImpl
6 | import com.juniori.puzzle.domain.repository.LocationRepository
7 | import dagger.Module
8 | import dagger.Provides
9 | import dagger.hilt.InstallIn
10 | import dagger.hilt.components.SingletonComponent
11 | import javax.inject.Singleton
12 |
13 |
14 | @Module
15 | @InstallIn(SingletonComponent::class)
16 | object LocationModule {
17 |
18 | @Singleton
19 | @Provides
20 | fun providesLocationRepository(impl: LocationRepositoryImpl): LocationRepository = impl
21 |
22 | @Singleton
23 | @Provides
24 | fun providesLocationDataSource(impl: LocationDataSourceImpl): LocationDataSource = impl
25 |
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/di/NetworkModule.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.di
2 |
3 | import com.google.gson.Gson
4 | import com.google.gson.GsonBuilder
5 | import com.juniori.puzzle.data.firebase.FirestoreDataSource
6 | import com.juniori.puzzle.data.firebase.FirestoreService
7 | import com.juniori.puzzle.data.firebase.StorageDataSource
8 | import com.juniori.puzzle.data.firebase.StorageService
9 | import com.juniori.puzzle.util.FIRESTORE_BASE_URL
10 | import com.juniori.puzzle.util.STORAGE_BASE_URL
11 | import dagger.Module
12 | import dagger.Provides
13 | import dagger.hilt.InstallIn
14 | import dagger.hilt.components.SingletonComponent
15 | import okhttp3.OkHttpClient
16 | import retrofit2.Retrofit
17 | import retrofit2.converter.gson.GsonConverterFactory
18 | import java.util.concurrent.TimeUnit
19 | import javax.inject.Named
20 | import javax.inject.Singleton
21 |
22 | @Module
23 | @InstallIn(SingletonComponent::class)
24 | object NetworkModule {
25 | private const val TIME_OUT_MILLIS = 8000L
26 |
27 | @Singleton
28 | @Provides
29 | fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder()
30 | .connectTimeout(TIME_OUT_MILLIS, TimeUnit.MILLISECONDS)
31 | .readTimeout(TIME_OUT_MILLIS, TimeUnit.MILLISECONDS)
32 | .writeTimeout(TIME_OUT_MILLIS, TimeUnit.MILLISECONDS)
33 | .addNetworkInterceptor {
34 | val request = it.request()
35 | .newBuilder()
36 | .build()
37 | it.proceed(request)
38 | }
39 | .build()
40 |
41 | @Singleton
42 | @Provides
43 | fun provideGson(): Gson = GsonBuilder()
44 | .setLenient()
45 | .create()
46 |
47 | @Singleton
48 | @Provides
49 | @Named("Firestore")
50 | fun provideFireStoreRetrofit(okHttpClient: OkHttpClient, gson: Gson): Retrofit =
51 | Retrofit.Builder()
52 | .client(okHttpClient)
53 | .addConverterFactory(GsonConverterFactory.create(gson))
54 | .baseUrl(FIRESTORE_BASE_URL)
55 | .build()
56 |
57 | @Singleton
58 | @Provides
59 | @Named("Storage")
60 | fun provideStorageRetrofit(okHttpClient: OkHttpClient, gson: Gson): Retrofit =
61 | Retrofit.Builder()
62 | .client(okHttpClient)
63 | .addConverterFactory(GsonConverterFactory.create(gson))
64 | .baseUrl(STORAGE_BASE_URL)
65 | .build()
66 |
67 | @Singleton
68 | @Provides
69 | fun provideFirebaseService(@Named("Firestore") retrofit: Retrofit): FirestoreService =
70 | retrofit.create(FirestoreService::class.java)
71 |
72 | @Singleton
73 | @Provides
74 | fun provideFirebaseRepository(service: FirestoreService): FirestoreDataSource =
75 | FirestoreDataSource(service)
76 |
77 | @Singleton
78 | @Provides
79 | fun provideStorageService(@Named("Storage") retrofit: Retrofit): StorageService =
80 | retrofit.create(StorageService::class.java)
81 |
82 | @Singleton
83 | @Provides
84 | fun provideStorageRepository(service: StorageService): StorageDataSource =
85 | StorageDataSource(service)
86 |
87 | }
88 |
89 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/di/VideoModule.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.di
2 |
3 | import com.juniori.puzzle.data.video.VideoRepositoryImpl
4 | import com.juniori.puzzle.data.video.VideoRepositoryMockImpl
5 | import com.juniori.puzzle.domain.repository.VideoRepository
6 | import com.juniori.puzzle.mock.getVideoListMockData
7 | import com.juniori.puzzle.util.VideoMetaDataUtil
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 | @InstallIn(SingletonComponent::class)
15 | @Module
16 | object VideoModule {
17 | private val mockVideoList = getVideoListMockData()
18 |
19 | @MockData
20 | @Provides
21 | fun provideMockRepository(): VideoRepository = VideoRepositoryMockImpl(mockVideoList)
22 |
23 | @Singleton
24 | @Provides
25 | fun provideVideoMetaDataUtil(): VideoMetaDataUtil = VideoMetaDataUtil
26 |
27 | @Singleton
28 | @Provides
29 | fun provideRepository(impl: VideoRepositoryImpl): VideoRepository = impl
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/di/WeatherModule.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.di
2 |
3 | import com.google.gson.Gson
4 | import com.juniori.puzzle.data.weather.WeatherDataSource
5 | import com.juniori.puzzle.data.weather.WeatherDataSourceImpl
6 | import com.juniori.puzzle.network.WeatherService
7 | import com.juniori.puzzle.util.WEATHER_BASE_URL
8 | import dagger.Module
9 | import dagger.Provides
10 | import dagger.hilt.InstallIn
11 | import dagger.hilt.components.SingletonComponent
12 | import okhttp3.OkHttpClient
13 | import retrofit2.Retrofit
14 | import retrofit2.converter.gson.GsonConverterFactory
15 | import javax.inject.Singleton
16 |
17 | @Module
18 | @InstallIn(SingletonComponent::class)
19 | object WeatherModule {
20 |
21 | @Singleton
22 | @Provides
23 | fun providesWeatherDataSource(impl: WeatherDataSourceImpl): WeatherDataSource = impl
24 |
25 | @Singleton
26 | @Provides
27 | @Weather
28 | fun providesWeatherRetrofit(okHttpClient: OkHttpClient, gson: Gson): Retrofit =
29 | Retrofit.Builder()
30 | .client(okHttpClient)
31 | .addConverterFactory(GsonConverterFactory.create(gson))
32 | .baseUrl(WEATHER_BASE_URL)
33 | .build()
34 |
35 | @Singleton
36 | @Provides
37 | fun providesWeatherService(@Weather retrofit: Retrofit): WeatherService =
38 | retrofit.create(WeatherService::class.java)
39 |
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/entity/LocationInfoEntity.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.entity
2 |
3 | data class LocationInfoEntity(
4 | val golfCourseInfo: String,
5 | val weather:WeatherEntity
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/entity/UserInfoEntity.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.entity
2 |
3 | import android.os.Parcelable
4 | import kotlinx.parcelize.Parcelize
5 |
6 | @Parcelize
7 | data class UserInfoEntity(
8 | val uid: String,
9 | val nickname: String,
10 | val profileImage: String,
11 | val videoList: List? = null
12 | ) : Parcelable
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/entity/VideoInfoEntity.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.entity
2 |
3 | import android.os.Parcelable
4 | import kotlinx.parcelize.Parcelize
5 |
6 | @Parcelize
7 | data class VideoInfoEntity(
8 | val documentId: String,
9 | val ownerUid: String,
10 | val videoUrl: String,
11 | val thumbnailUrl: String,
12 | val isPrivate: Boolean,
13 | val likedCount: Int,
14 | val likedUserUidList: List,
15 | val updateTime: Long,
16 | val location: String,
17 | val locationKeyword: List,
18 | val memo: String
19 | ) : Parcelable {
20 | override fun equals(other: Any?): Boolean {
21 | return this.videoUrl == (other as VideoInfoEntity).videoUrl
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/entity/WeatherEntity.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.entity
2 |
3 | import java.util.*
4 |
5 | data class WeatherEntity(
6 | val date: Date,
7 | val temp: Int,
8 | val feelsLike: Int,
9 | val minTemp: Int,
10 | val maxTemp: Int,
11 | val description: String,
12 | val icon: String
13 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/repository/AuthRepository.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.repository
2 |
3 | import com.google.android.gms.auth.api.signin.GoogleSignInAccount
4 | import com.juniori.puzzle.data.Resource
5 | import com.juniori.puzzle.domain.entity.UserInfoEntity
6 |
7 | interface AuthRepository {
8 | fun getCurrentUserInfo(): Resource
9 | suspend fun requestLogin(acct: GoogleSignInAccount): Resource
10 | suspend fun requestLogout(): Resource
11 | suspend fun requestWithdraw(acct: GoogleSignInAccount): Resource
12 | suspend fun updateNickname(newNickname: String): Resource
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/repository/LocationRepository.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.repository
2 |
3 | import android.location.Address
4 | import androidx.core.location.LocationListenerCompat
5 | import com.juniori.puzzle.data.Resource
6 | import com.juniori.puzzle.domain.entity.WeatherEntity
7 |
8 | interface LocationRepository {
9 | fun registerLocationListener(listener: LocationListenerCompat):Boolean
10 | fun unregisterLocationListener()
11 | fun getAddressInfo(lat: Double, long: Double): List
12 | suspend fun getWeatherInfo(lat: Double, long: Double): Resource>
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/repository/VideoRepository.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.repository
2 |
3 | import com.juniori.puzzle.data.Resource
4 | import com.juniori.puzzle.domain.entity.UserInfoEntity
5 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
6 | import com.juniori.puzzle.util.SortType
7 |
8 | interface VideoRepository {
9 | suspend fun getMyVideoList(uid: String, index: Int): Resource>
10 | suspend fun getSearchedMyVideoList(uid: String, index: Int, keyword: String): Resource>
11 |
12 | suspend fun getSocialVideoList(
13 | index: Int,
14 | sortType: SortType,
15 | latestData: Long?
16 | ): Resource>
17 |
18 | suspend fun getSearchedSocialVideoList(
19 | index: Int,
20 | sortType: SortType,
21 | keyword: String,
22 | latestData: Long?
23 | ): Resource>
24 |
25 | suspend fun updateLikeStatus(
26 | documentInfo: VideoInfoEntity,
27 | uid: String,
28 | isLiked: Boolean
29 | ): Resource
30 |
31 | suspend fun deleteVideo(documentId: String): Resource
32 | suspend fun changeVideoScope(documentInfo: VideoInfoEntity): Resource
33 | suspend fun uploadVideo(
34 | uid: String,
35 | videoName: String,
36 | isPrivate: Boolean,
37 | location: String,
38 | memo: String,
39 | videoByteArray: ByteArray,
40 | imageByteArray: ByteArray
41 | ): Resource
42 |
43 | suspend fun getUserInfoByUidUseCase(uid: String): Resource
44 | suspend fun postUserInfoInFirestore(
45 | uid: String,
46 | nickname: String,
47 | profileImage: String
48 | ): Resource
49 |
50 | suspend fun updateServerNickname(userInfoEntity: UserInfoEntity): Resource
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/ChangeVideoScopeUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.juniori.puzzle.data.Resource
4 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
5 | import com.juniori.puzzle.domain.repository.VideoRepository
6 | import javax.inject.Inject
7 |
8 | class ChangeVideoScopeUseCase @Inject constructor(private val videoRepository: VideoRepository) {
9 | suspend operator fun invoke(documentInfo: VideoInfoEntity): Resource =
10 | videoRepository.changeVideoScope(documentInfo)
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/DeleteVideoUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.juniori.puzzle.data.Resource
4 | import com.juniori.puzzle.domain.repository.VideoRepository
5 | import javax.inject.Inject
6 |
7 | class DeleteVideoUseCase @Inject constructor(private val videoRepository: VideoRepository) {
8 | suspend operator fun invoke(documentId: String): Resource =
9 | videoRepository.deleteVideo(documentId)
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/GetAddressUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.juniori.puzzle.domain.repository.LocationRepository
4 | import javax.inject.Inject
5 |
6 | class GetAddressUseCase @Inject constructor(
7 | private val repository: LocationRepository
8 | ) {
9 | operator fun invoke(lat: Double, long: Double) = repository.getAddressInfo(lat, long)
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/GetMyVideoListUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.juniori.puzzle.data.Resource
4 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
5 | import com.juniori.puzzle.domain.repository.VideoRepository
6 | import javax.inject.Inject
7 |
8 | class GetMyVideoListUseCase @Inject constructor(private val videoRepository: VideoRepository) {
9 | suspend operator fun invoke(uid: String, index: Int): Resource> =
10 | videoRepository.getMyVideoList(uid, index)
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/GetPublisherInfoUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.juniori.puzzle.data.Resource
4 | import com.juniori.puzzle.domain.entity.UserInfoEntity
5 | import com.juniori.puzzle.domain.repository.VideoRepository
6 | import javax.inject.Inject
7 |
8 | class GetUserInfoByUidUseCase @Inject constructor(private val videoRepository: VideoRepository) {
9 | suspend operator fun invoke(uid: String): Resource =
10 | videoRepository.getUserInfoByUidUseCase(uid)
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/GetSearchedMyVideoUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.juniori.puzzle.domain.repository.VideoRepository
4 | import javax.inject.Inject
5 |
6 | class GetSearchedMyVideoUseCase @Inject constructor(private val videoRepository: VideoRepository) {
7 | suspend operator fun invoke(uid: String, index: Int, keyword: String) = videoRepository.getSearchedMyVideoList(uid, index, keyword)
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/GetSearchedSocialVideoListUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.juniori.puzzle.data.Resource
4 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
5 | import com.juniori.puzzle.domain.repository.VideoRepository
6 | import com.juniori.puzzle.util.SortType
7 | import javax.inject.Inject
8 |
9 | class GetSearchedSocialVideoListUseCase @Inject constructor(private val videoRepository: VideoRepository) {
10 | suspend operator fun invoke(
11 | index: Int,
12 | keyword: String,
13 | order: SortType,
14 | latestData: Long? = null
15 | ): Resource> =
16 | videoRepository.getSearchedSocialVideoList(index, order, keyword, latestData)
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/GetSocialVideoListUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.juniori.puzzle.data.Resource
4 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
5 | import com.juniori.puzzle.domain.repository.VideoRepository
6 | import com.juniori.puzzle.util.SortType
7 | import javax.inject.Inject
8 |
9 | class GetSocialVideoListUseCase @Inject constructor(private val videoRepository: VideoRepository) {
10 | suspend operator fun invoke(
11 | index: Int,
12 | order: SortType,
13 | latestData: Long? = null
14 | ): Resource> =
15 | videoRepository.getSocialVideoList(index, order, latestData)
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/GetUserInfoUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.juniori.puzzle.domain.repository.AuthRepository
4 | import javax.inject.Inject
5 |
6 | class GetUserInfoUseCase @Inject constructor(private val authRepository: AuthRepository) {
7 | operator fun invoke() = authRepository.getCurrentUserInfo()
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/GetVideoFileUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | class GetVideoFileUseCase {
4 |
5 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/GetWeatherUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.juniori.puzzle.domain.repository.LocationRepository
4 | import javax.inject.Inject
5 |
6 | class GetWeatherUseCase @Inject constructor(
7 | private val locationRepository: LocationRepository
8 | ) {
9 | suspend operator fun invoke(lat: Double, long: Double) =
10 | locationRepository.getWeatherInfo(lat, long)
11 |
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/PostUserInfoUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.juniori.puzzle.data.Resource
4 | import com.juniori.puzzle.domain.entity.UserInfoEntity
5 | import com.juniori.puzzle.domain.repository.VideoRepository
6 | import javax.inject.Inject
7 |
8 | class PostUserInfoUseCase @Inject constructor(private val videoRepository: VideoRepository) {
9 | suspend operator fun invoke(
10 | uid: String,
11 | nickname: String,
12 | profileImage: String
13 | ): Resource = videoRepository.postUserInfoInFirestore(
14 | uid, nickname, profileImage
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/PostVideoUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.juniori.puzzle.data.Resource
4 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
5 | import com.juniori.puzzle.domain.repository.VideoRepository
6 | import javax.inject.Inject
7 |
8 | class PostVideoUseCase @Inject constructor(private val videoRepository: VideoRepository) {
9 | suspend operator fun invoke(
10 | uid: String,
11 | videoName: String,
12 | isPrivate: Boolean,
13 | location: String,
14 | memo: String,
15 | videoByteArray: ByteArray,
16 | imageByteArray: ByteArray
17 | ): Resource = videoRepository.uploadVideo(
18 | uid, videoName, isPrivate, location, memo, videoByteArray, imageByteArray
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/RegisterLocationListenerUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import androidx.core.location.LocationListenerCompat
4 | import com.juniori.puzzle.domain.repository.LocationRepository
5 | import javax.inject.Inject
6 |
7 | class RegisterLocationListenerUseCase @Inject constructor(
8 | private val repository: LocationRepository
9 | ) {
10 | operator fun invoke(listener: LocationListenerCompat): Boolean =
11 | repository.registerLocationListener(listener)
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/RequestLoginUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.google.android.gms.auth.api.signin.GoogleSignInAccount
4 | import com.juniori.puzzle.domain.repository.AuthRepository
5 | import javax.inject.Inject
6 |
7 | class RequestLoginUseCase @Inject constructor(private val authRepository: AuthRepository) {
8 | suspend operator fun invoke(account: GoogleSignInAccount) = authRepository.requestLogin(account)
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/RequestLogoutUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.juniori.puzzle.domain.repository.AuthRepository
4 | import javax.inject.Inject
5 |
6 | class RequestLogoutUseCase @Inject constructor(private val authRepository: AuthRepository) {
7 | suspend operator fun invoke() = authRepository.requestLogout()
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/RequestWithdrawUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.google.android.gms.auth.api.signin.GoogleSignInAccount
4 | import com.juniori.puzzle.domain.repository.AuthRepository
5 | import javax.inject.Inject
6 |
7 | class RequestWithdrawUseCase @Inject constructor(private val authRepository: AuthRepository) {
8 | suspend operator fun invoke(acct: GoogleSignInAccount) = authRepository.requestWithdraw(acct)
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/UnregisterLocationListenerUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.juniori.puzzle.domain.repository.LocationRepository
4 | import javax.inject.Inject
5 |
6 | class UnregisterLocationListenerUseCase @Inject constructor(
7 | private val repository: LocationRepository
8 | ) {
9 | operator fun invoke() = repository.unregisterLocationListener()
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/UpdateLikeStatusUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.juniori.puzzle.data.Resource
4 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
5 | import com.juniori.puzzle.domain.repository.VideoRepository
6 | import javax.inject.Inject
7 |
8 | class UpdateLikeStatusUseCase @Inject constructor(private val videoRepository: VideoRepository) {
9 | suspend operator fun invoke(
10 | documentInfo: VideoInfoEntity,
11 | uid: String,
12 | isLiked: Boolean
13 | ): Resource = videoRepository.updateLikeStatus(documentInfo, uid, isLiked)
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/UpdateNicknameUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | import com.juniori.puzzle.data.Resource
4 | import com.juniori.puzzle.domain.entity.UserInfoEntity
5 | import com.juniori.puzzle.domain.repository.AuthRepository
6 | import com.juniori.puzzle.domain.repository.VideoRepository
7 | import kotlinx.coroutines.Dispatchers
8 | import kotlinx.coroutines.withContext
9 | import javax.inject.Inject
10 |
11 | class UpdateNicknameUseCase @Inject constructor(
12 | private val authRepository: AuthRepository,
13 | private val videoRepository: VideoRepository
14 | ){
15 | suspend operator fun invoke(newNickname: String): Resource {
16 | if (newNickname.isBlank()) {
17 | return Resource.Failure(Exception())
18 | }
19 |
20 | val newInfo = withContext(Dispatchers.IO) {
21 | authRepository.updateNickname(newNickname)
22 | }
23 |
24 | return if (newInfo is Resource.Success) {
25 | videoRepository.updateServerNickname(newInfo.result)
26 | } else {
27 | Resource.Failure(Exception())
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/domain/usecase/WithdrawAccountUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.domain.usecase
2 |
3 | class WithdrawAccountUseCase {
4 |
5 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/mock/MockVideoData.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.mock
2 |
3 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
4 |
5 | private val imageList = mutableListOf()
6 |
7 | fun getVideoListMockData(): List {
8 | imageList.add("https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.bas.ac.uk%2Fabout%2Fantarctica%2Fwildlife%2Fpenguins%2Fgentoo-penguin%2F&psig=AOvVaw0Y3OInta9o0_aG9uZa_c3Q&ust=1668739380989000&source=images&cd=vfe&ved=0CBAQjRxqFwoTCMjuieKYtPsCFQAAAAAdAAAAABAE")
9 | imageList.add("https://www.google.com/url?sa=i&url=https%3A%2F%2Febird.org%2Fspecies%2Femppen1&psig=AOvVaw1l91xJkBqgSou5xP6BIm2c&ust=1668743625836000&source=images&cd=vfe&ved=0CBAQjRxqFwoTCPjQmcqotPsCFQAAAAAdAAAAABAE")
10 | imageList.add("https://a-z-animals.com/media/2021/11/King-Penguin.jpg")
11 | imageList.add("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS24Ljtjfgtso5HSKYtXL0GrjkqX8V9-5ENpw&usqp=CAU")
12 | imageList.add("https://www.google.com/imgres?imgurl=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2Fd%2Fd1%2FAnas_platyrhynchos_LC0014.jpg&imgrefurl=https%3A%2F%2Fko.wikipedia.org%2Fwiki%2F%25EC%25B2%25AD%25EB%2591%25A5%25EC%2598%25A4%25EB%25A6%25AC&tbnid=R8WX__ScJDABlM&vet=12ahUKEwiv1LuQrLT7AhWkS_UHHYOhBREQMygAegUIARDRAQ..i&docid=SolPAt4BnclHQM&w=1976&h=1413&q=%EC%B2%AD%EB%91%A5%EC%98%A4%EB%A6%AC&ved=2ahUKEwiv1LuQrLT7AhWkS_UHHYOhBREQMygAegUIARDRAQ")
13 | imageList.add("https://mblogthumb-phinf.pstatic.net/MjAxODAxMDJfMTA3/MDAxNTE0ODgwNTY2OTc0.8LxADt55g2SY3nM6FZFh4AH2Xm5vn7AHsfwH5EfJ_lsg.NDA_6myIp2mrcRtOd35i1U--oaVVek2nbhY7nODTL5og.PNG.zoopark01/image_1492814851514880561326.png?type=w800")
14 | imageList.add("https://post-phinf.pstatic.net/MjAxOTA3MjRfMjcw/MDAxNTYzOTI3ODc2NTUy.DfC_wCMby4GxvJss2Q2gFxeZd6KMAziG_xG0a5VyNSEg.P5v_AulrhVk1OR-ai69TMvVaijsUyNeh6dsIIGQngS0g.JPEG/photo-1544460848-32344b7004ec.jpg?type=w1200")
15 |
16 | return listOf(
17 | VideoInfoEntity("a","aaa", "aaa_300", imageList[0], false, 0, emptyList(), 105, "서대문구A", emptyList(), "젠투펭귄"),
18 | VideoInfoEntity("b","bbb", "bbb_400", imageList[1], true, 0, emptyList(), 101, "서대문구B", emptyList(), "황제펭귄"),
19 | VideoInfoEntity("a","aaa", "aaa_500", imageList[2], false, 0, emptyList(), 103, "마포구", emptyList(), "킹펭귄"),
20 | VideoInfoEntity("d","ddd", "ddd_100", imageList[3], true, 0, emptyList(), 102, "은평구", emptyList(), "턱끈펭귄"),
21 | VideoInfoEntity("e","eee", "eee_700", imageList[4], false, 0, emptyList(), 100, "동대문구", emptyList(), "청둥오리"),
22 | VideoInfoEntity("f","fff", "fff_600", imageList[5], true, 0, emptyList(), 104, "동작구", emptyList(), "노랑오리"),
23 | VideoInfoEntity("a","aaa", "aaa_800", imageList[1], true, 0, emptyList(), 106, "서대문구A", emptyList(), "도시오리"),
24 | VideoInfoEntity("a","aaa", "aaa_900", imageList[2], false, 0, emptyList(), 106, "서대문구B", emptyList(), "어라?"),
25 | VideoInfoEntity("a","aaa", "aaa_950", imageList[3], true, 0, emptyList(), 106, "종로구", emptyList(), "어?"),
26 | VideoInfoEntity("a","aaa", "aaa_800", imageList[1], true, 0, emptyList(), 106, "서대문구A", emptyList(), "도시오리"),
27 | VideoInfoEntity("a","aaa", "aaa_900", imageList[2], false, 0, emptyList(), 106, "서대문구B", emptyList(), "어라?"),
28 | VideoInfoEntity("a","aaa", "aaa_950", imageList[3], true, 0, emptyList(), 106, "종로구", emptyList(), "어?")
29 | )
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/network/WeatherService.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.network
2 |
3 | import com.juniori.puzzle.data.weather.WeatherResponse
4 | import retrofit2.Response
5 | import retrofit2.http.GET
6 | import retrofit2.http.Query
7 |
8 | interface WeatherService {
9 |
10 | @GET("/data/2.5/forecast")
11 | suspend fun getWeather(
12 | @Query("lat") lat: Double,
13 | @Query("lon") lon: Double,
14 | @Query("appid") apiKey: String,
15 | @Query("lang") lang: String = "kr",
16 | @Query("units") units: String = "metric",
17 | @Query("cnt") cnt: Int = 11
18 | ): Response
19 |
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/ui/addvideo/AddVideoUiState.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.ui.addvideo
2 |
3 | enum class AddVideoUiState {
4 | SHOW_DURATION_LIMIT_FEEDBACK, GO_TO_UPLOAD
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/ui/addvideo/AddVideoViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.ui.addvideo
2 |
3 | import androidx.lifecycle.ViewModel
4 | import com.juniori.puzzle.util.VideoMetaDataUtil
5 | import dagger.hilt.android.lifecycle.HiltViewModel
6 | import kotlinx.coroutines.flow.MutableStateFlow
7 | import kotlinx.coroutines.flow.StateFlow
8 | import javax.inject.Inject
9 |
10 | @HiltViewModel
11 | class AddVideoViewModel @Inject constructor(
12 | private val videoMetaDataUtil: VideoMetaDataUtil
13 | ) : ViewModel() {
14 |
15 | var videoFilePath = ""
16 | private set
17 | var thumbnailBytes = ByteArray(0)
18 | private set
19 |
20 | private val _uiState = MutableStateFlow(null)
21 | val uiState: StateFlow get() = _uiState
22 |
23 | fun notifyTakingVideoFinished(videoFilePath: String) {
24 | this.videoFilePath = videoFilePath
25 | thumbnailBytes = videoMetaDataUtil.extractThumbnail(videoFilePath) ?: return
26 | _uiState.value = AddVideoUiState.GO_TO_UPLOAD
27 | }
28 |
29 | fun notifyVideoPicked(videoFilePath: String) {
30 | this.videoFilePath = videoFilePath
31 | val durationInSeconds = videoMetaDataUtil.getVideoDurationInSeconds(videoFilePath) ?: return
32 | if (durationInSeconds > AddVideoBottomSheet.VIDEO_DURATION_LIMIT_SECONDS) {
33 | _uiState.value = AddVideoUiState.SHOW_DURATION_LIMIT_FEEDBACK
34 | } else {
35 | thumbnailBytes = videoMetaDataUtil.extractThumbnail(videoFilePath) ?: return
36 | _uiState.value = AddVideoUiState.GO_TO_UPLOAD
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/ui/login/LoginActivity.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.ui.login
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import androidx.activity.result.ActivityResultLauncher
6 | import androidx.activity.result.contract.ActivityResultContracts
7 | import androidx.activity.viewModels
8 | import androidx.appcompat.app.AppCompatActivity
9 | import androidx.core.view.isVisible
10 | import androidx.lifecycle.Lifecycle
11 | import androidx.lifecycle.lifecycleScope
12 | import androidx.lifecycle.repeatOnLifecycle
13 | import com.google.android.gms.auth.api.signin.GoogleSignIn
14 | import com.google.android.gms.auth.api.signin.GoogleSignInAccount
15 | import com.google.android.gms.auth.api.signin.GoogleSignInOptions
16 | import com.google.android.gms.common.api.ApiException
17 | import com.google.android.gms.tasks.Task
18 | import com.juniori.puzzle.MainActivity
19 | import com.juniori.puzzle.R
20 | import com.juniori.puzzle.data.Resource
21 | import com.juniori.puzzle.databinding.ActivityLoginBinding
22 | import dagger.hilt.android.AndroidEntryPoint
23 | import kotlinx.coroutines.delay
24 | import kotlinx.coroutines.flow.collectLatest
25 | import kotlinx.coroutines.launch
26 |
27 | @AndroidEntryPoint
28 | class LoginActivity : AppCompatActivity() {
29 | private lateinit var binding: ActivityLoginBinding
30 | private val loginViewModel: LoginViewModel by viewModels()
31 |
32 | private val googleSignInClient by lazy {
33 | GoogleSignIn.getClient(
34 | this, GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
35 | .requestIdToken(getString(R.string.default_web_client_id))
36 | .requestEmail()
37 | .build()
38 | )
39 | }
40 |
41 | private val activityResult: ActivityResultLauncher =
42 | registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
43 | if (result.resultCode == RESULT_OK) {
44 | val task: Task =
45 | GoogleSignIn.getSignedInAccountFromIntent(result.data)
46 | runCatching {
47 | val account: GoogleSignInAccount = task.getResult(ApiException::class.java)
48 | loginViewModel.loginUser(account)
49 | }
50 | }
51 | }
52 |
53 | override fun onCreate(savedInstanceState: Bundle?) {
54 | super.onCreate(savedInstanceState)
55 | binding = ActivityLoginBinding.inflate(layoutInflater)
56 | setContentView(binding.root)
57 |
58 | binding.signInBtn.setOnClickListener {
59 | signIn()
60 | }
61 |
62 | lifecycleScope.launch {
63 | repeatOnLifecycle(Lifecycle.State.STARTED) {
64 | loginViewModel.loginFlow.collectLatest {
65 | if (it is Resource.Success) {
66 | binding.signInBtn.isVisible = false
67 | moveToMainActivity()
68 | } else {
69 | binding.signInBtn.isVisible = true
70 | }
71 | }
72 | }
73 | }
74 | }
75 |
76 | private fun signIn() {
77 | val signInIntent = googleSignInClient.signInIntent
78 | activityResult.launch(signInIntent)
79 | }
80 |
81 | private fun moveToMainActivity() {
82 | lifecycleScope.launch {
83 | delay(1000)
84 | val intent = Intent(applicationContext, MainActivity::class.java)
85 | startActivity(intent)
86 | finish()
87 | }
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/ui/login/LoginViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.ui.login
2 |
3 | import android.util.Log
4 | import androidx.lifecycle.ViewModel
5 | import androidx.lifecycle.viewModelScope
6 | import com.google.android.gms.auth.api.signin.GoogleSignInAccount
7 | import com.juniori.puzzle.data.Resource
8 | import com.juniori.puzzle.domain.entity.UserInfoEntity
9 | import com.juniori.puzzle.domain.usecase.GetUserInfoUseCase
10 | import com.juniori.puzzle.domain.usecase.PostUserInfoUseCase
11 | import com.juniori.puzzle.domain.usecase.RequestLoginUseCase
12 | import dagger.hilt.android.lifecycle.HiltViewModel
13 | import kotlinx.coroutines.flow.MutableStateFlow
14 | import kotlinx.coroutines.flow.StateFlow
15 | import kotlinx.coroutines.launch
16 | import javax.inject.Inject
17 |
18 | @HiltViewModel
19 | class LoginViewModel @Inject constructor(
20 | getUserInfoUseCase: GetUserInfoUseCase,
21 | private val requestLoginUseCase: RequestLoginUseCase,
22 | private val postUserInfoUseCase: PostUserInfoUseCase
23 | ) : ViewModel() {
24 | private val _loginFlow = MutableStateFlow?>(null)
25 | val loginFlow: StateFlow?> = _loginFlow
26 |
27 | init {
28 | getUserInfoUseCase().let { currentUser ->
29 | _loginFlow.value = currentUser
30 | }
31 | }
32 |
33 | fun loginUser(account: GoogleSignInAccount) = viewModelScope.launch {
34 | _loginFlow.value = Resource.Loading
35 | val result = requestLoginUseCase(account).apply {
36 | if (this is Resource.Success) {
37 | postUserInfoUseCase(result.uid, result.nickname, result.profileImage)
38 | }
39 | }
40 | _loginFlow.value = result
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/ui/mygallery/MyGalleryAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.ui.mygallery
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.ListAdapter
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.juniori.puzzle.databinding.ItemGalleryRecyclerBinding
8 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
9 | import com.juniori.puzzle.util.GalleryDiffCallBack
10 |
11 |
12 | class MyGalleryAdapter(
13 | val viewModel: MyGalleryViewModel,
14 | private val onClick: (VideoInfoEntity) -> Unit
15 | ) : ListAdapter(
16 | GalleryDiffCallBack()
17 | ) {
18 |
19 | class ViewHolder(
20 | val binding: ItemGalleryRecyclerBinding,
21 | val onClick: (VideoInfoEntity) -> Unit
22 | ) :
23 | RecyclerView.ViewHolder(binding.root) {
24 | fun bind(item: VideoInfoEntity) {
25 | binding.root.setOnClickListener {
26 | onClick(item)
27 | }
28 | binding.data = item
29 | }
30 | }
31 |
32 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
33 | val binding =
34 | ItemGalleryRecyclerBinding.inflate(LayoutInflater.from(parent.context), parent, false)
35 |
36 | return ViewHolder(binding, onClick)
37 | }
38 |
39 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
40 | holder.bind(getItem(position))
41 | if (position == itemCount - LOADING_FLAG_NUM) {
42 | viewModel.getPaging(itemCount)
43 | }
44 | }
45 |
46 | companion object {
47 | const val VISIBLE_ITEM_COUNT = 3
48 | const val LOADING_FLAG_NUM = 1
49 | }
50 |
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/ui/mypage/MyPageViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.ui.mypage
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.google.android.gms.auth.api.signin.GoogleSignInAccount
6 | import com.juniori.puzzle.data.Resource
7 | import com.juniori.puzzle.domain.usecase.GetUserInfoUseCase
8 | import com.juniori.puzzle.domain.usecase.RequestLogoutUseCase
9 | import com.juniori.puzzle.domain.usecase.RequestWithdrawUseCase
10 | import dagger.hilt.android.lifecycle.HiltViewModel
11 | import kotlinx.coroutines.Dispatchers
12 | import kotlinx.coroutines.flow.*
13 | import kotlinx.coroutines.launch
14 | import kotlinx.coroutines.withContext
15 | import javax.inject.Inject
16 |
17 | @HiltViewModel
18 | class MyPageViewModel @Inject constructor(
19 | private val requestLogoutUseCase: RequestLogoutUseCase,
20 | private val requestWithdrawUseCase: RequestWithdrawUseCase,
21 | val getUserInfoUseCase: GetUserInfoUseCase
22 | ) : ViewModel() {
23 | private val _requestLogoutFlow = MutableSharedFlow>()
24 | val requestLogoutFlow: SharedFlow> = _requestLogoutFlow
25 |
26 | private val _requestWithdrawFlow = MutableSharedFlow>()
27 | val requestWithdrawFlow: SharedFlow> = _requestWithdrawFlow
28 |
29 | private val _userNickname = MutableStateFlow("")
30 | val userNickname: StateFlow = _userNickname
31 |
32 | private val _makeLogoutDialogFlow = MutableSharedFlow()
33 | val makeLogoutDialogFlow: SharedFlow = _makeLogoutDialogFlow
34 |
35 | private val _makeWithdrawDialogFlow = MutableSharedFlow()
36 | val makeWithdrawDialogFlow: SharedFlow = _makeWithdrawDialogFlow
37 |
38 | private val _navigateToUpdateNicknamePageFlow = MutableSharedFlow()
39 | val navigateToUpdateNicknameFlow: SharedFlow = _navigateToUpdateNicknamePageFlow
40 |
41 | init {
42 | updateUserInfo()
43 | }
44 |
45 | fun makeLogoutDialog() {
46 | viewModelScope.launch {
47 | _makeLogoutDialogFlow.emit(Unit)
48 | }
49 | }
50 |
51 | fun makeWithdrawDialog() {
52 | viewModelScope.launch {
53 | _makeWithdrawDialogFlow.emit(Unit)
54 | }
55 | }
56 |
57 | fun navigateToUpdateNicknamePage() {
58 | viewModelScope.launch {
59 | _navigateToUpdateNicknamePageFlow.emit(Unit)
60 | }
61 | }
62 |
63 | fun requestLogout() {
64 | viewModelScope.launch {
65 | _requestLogoutFlow.emit(Resource.Loading)
66 | withContext(Dispatchers.IO) {
67 | _requestLogoutFlow.emit(requestLogoutUseCase())
68 | }
69 | }
70 | }
71 |
72 | fun requestWithdraw(acct: GoogleSignInAccount) {
73 | viewModelScope.launch {
74 | _requestWithdrawFlow.emit(Resource.Loading)
75 | withContext(Dispatchers.IO) {
76 | _requestWithdrawFlow.emit(requestWithdrawUseCase(acct))
77 | }
78 | }
79 | }
80 |
81 | fun updateUserInfo() {
82 | viewModelScope.launch {
83 | val data = getUserInfoUseCase()
84 |
85 | if (data is Resource.Success) {
86 | _userNickname.value = data.result.nickname
87 | }
88 | else {
89 | _userNickname.value = ""
90 | }
91 | }
92 | }
93 |
94 | fun updateUserNickname(newNickname: String) {
95 | _userNickname.value = newNickname
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/ui/mypage/UpdateNicknameActivity.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.ui.mypage
2 |
3 | import android.content.Intent
4 | import androidx.appcompat.app.AppCompatActivity
5 | import android.os.Bundle
6 | import android.widget.Toast
7 | import androidx.activity.viewModels
8 | import androidx.lifecycle.lifecycleScope
9 | import com.juniori.puzzle.MainActivity
10 | import com.juniori.puzzle.R
11 | import com.juniori.puzzle.data.Resource
12 | import com.juniori.puzzle.databinding.ActivityUpdateNicknameBinding
13 | import com.juniori.puzzle.domain.entity.UserInfoEntity
14 | import com.juniori.puzzle.util.StateManager
15 | import dagger.hilt.android.AndroidEntryPoint
16 | import javax.inject.Inject
17 |
18 | @AndroidEntryPoint
19 | class UpdateNicknameActivity : AppCompatActivity() {
20 | private val binding: ActivityUpdateNicknameBinding by lazy { ActivityUpdateNicknameBinding.inflate(layoutInflater) }
21 | private val viewModel: UpdateNicknameViewModel by viewModels()
22 | private var currentNickname = ""
23 | @Inject lateinit var stateManager: StateManager
24 |
25 | override fun onCreate(savedInstanceState: Bundle?) {
26 | super.onCreate(savedInstanceState)
27 | setContentView(binding.root)
28 | stateManager.createLoadingDialog(binding.viewContainer)
29 |
30 | lifecycleScope.launchWhenStarted {
31 | viewModel.finalUserInfo.collect { result ->
32 | when(result) {
33 | is Resource.Success -> {
34 | stateManager.dismissLoadingDialog()
35 |
36 | val intent = Intent(this@UpdateNicknameActivity, MainActivity::class.java).apply {
37 | putExtra(MyPageFragment.NEW_NICKNAME, currentNickname)
38 | }
39 |
40 | setResult(RESULT_OK, intent)
41 | finish()
42 | }
43 | is Resource.Failure -> {
44 | stateManager.dismissLoadingDialog()
45 | Toast.makeText(this@UpdateNicknameActivity, getString(R.string.nickname_change_impossible), Toast.LENGTH_SHORT).show()
46 | }
47 | is Resource.Loading -> {
48 | stateManager.showLoadingDialog()
49 | }
50 | }
51 | }
52 | }
53 |
54 | binding.completeButton.setOnClickListener {
55 | currentNickname = binding.nicknameContainer.text.toString()
56 | viewModel.updateUserInfo(currentNickname)
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/ui/mypage/UpdateNicknameViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.ui.mypage
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.juniori.puzzle.data.Resource
6 | import com.juniori.puzzle.domain.entity.UserInfoEntity
7 | import com.juniori.puzzle.domain.usecase.UpdateNicknameUseCase
8 | import dagger.hilt.android.lifecycle.HiltViewModel
9 | import kotlinx.coroutines.flow.MutableSharedFlow
10 | import kotlinx.coroutines.flow.SharedFlow
11 | import kotlinx.coroutines.launch
12 | import javax.inject.Inject
13 |
14 | @HiltViewModel
15 | class UpdateNicknameViewModel @Inject constructor(
16 | private val updateNicknameUseCase: UpdateNicknameUseCase
17 | ): ViewModel() {
18 | private val _finalUserInfo = MutableSharedFlow>()
19 | val finalUserInfo: SharedFlow> = _finalUserInfo
20 |
21 | fun updateUserInfo(newNickname: String) = viewModelScope.launch {
22 | _finalUserInfo.emit(Resource.Loading)
23 | _finalUserInfo.emit(updateNicknameUseCase(newNickname))
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/ui/othersgallery/OtherGalleryAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.ui.othersgallery
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.ListAdapter
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.juniori.puzzle.databinding.ItemGalleryRecyclerBinding
8 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
9 | import com.juniori.puzzle.ui.mygallery.MyGalleryViewModel
10 | import com.juniori.puzzle.util.GalleryDiffCallBack
11 |
12 | class OtherGalleryAdapter(
13 | val viewModel: OthersGalleryViewModel,
14 | private val onClick: (VideoInfoEntity) -> Unit
15 | ) : ListAdapter(
16 | GalleryDiffCallBack()
17 | ) {
18 |
19 | class ViewHolder(
20 | val binding: ItemGalleryRecyclerBinding,
21 | val onClick: (VideoInfoEntity) -> Unit
22 | ) :
23 | RecyclerView.ViewHolder(binding.root) {
24 | fun bind(item: VideoInfoEntity) {
25 | binding.root.setOnClickListener {
26 | onClick(item)
27 | }
28 | binding.data = item
29 | }
30 | }
31 |
32 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
33 | val binding =
34 | ItemGalleryRecyclerBinding.inflate(LayoutInflater.from(parent.context), parent, false)
35 |
36 | return ViewHolder(binding, onClick)
37 | }
38 |
39 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
40 | holder.bind(getItem(position))
41 | if (position == itemCount - LOADING_FLAG_NUM) {
42 | viewModel.getPaging()
43 | }
44 | }
45 |
46 | companion object {
47 | const val VISIBLE_ITEM_COUNT = 3
48 | const val LOADING_FLAG_NUM = 1
49 | }
50 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/ui/playvideo/PlayVideoBottomSheet.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.ui.playvideo
2 |
3 | import android.annotation.SuppressLint
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment
9 | import com.juniori.puzzle.R
10 | import com.juniori.puzzle.databinding.BottomsheetPlayvideoBinding
11 | import com.juniori.puzzle.domain.entity.UserInfoEntity
12 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
13 | import dagger.hilt.android.AndroidEntryPoint
14 | import java.text.SimpleDateFormat
15 |
16 | @AndroidEntryPoint
17 | class PlayVideoBottomSheet : BottomSheetDialogFragment() {
18 |
19 | private var _binding: BottomsheetPlayvideoBinding? = null
20 | private val binding get() = _binding!!
21 | private val videoInfo by lazy {
22 | arguments?.get("videoInfo") as VideoInfoEntity
23 | }
24 | private val publisherInfo by lazy {
25 | arguments?.get("publisherInfo") as UserInfoEntity
26 | }
27 |
28 | override fun onCreateView(
29 | inflater: LayoutInflater,
30 | container: ViewGroup?,
31 | savedInstanceState: Bundle?
32 | ): View {
33 | _binding = BottomsheetPlayvideoBinding.inflate(inflater, container, false)
34 | return binding.root
35 | }
36 |
37 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
38 | super.onViewCreated(view, savedInstanceState)
39 | setItemInformation()
40 | }
41 |
42 | @SuppressLint("SimpleDateFormat")
43 | private fun setItemInformation() {
44 | binding.itemLocation.image = R.drawable.all_location_icon.toString()
45 | binding.itemDate.image = R.drawable.play_calendar_icon.toString()
46 | binding.itemPublisher.image = publisherInfo.profileImage
47 |
48 | binding.itemLocation.content = videoInfo.location
49 | binding.itemDate.content =
50 | SimpleDateFormat("yyyy-MM-dd HH:mm").format(videoInfo.updateTime)
51 | binding.itemPublisher.content = publisherInfo.nickname
52 |
53 | binding.memo = videoInfo.memo
54 | }
55 |
56 | override fun onDestroyView() {
57 | super.onDestroyView()
58 | _binding = null
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/ui/playvideo/PlayVideoViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.ui.playvideo
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.juniori.puzzle.data.Resource
6 | import com.juniori.puzzle.domain.entity.UserInfoEntity
7 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
8 | import com.juniori.puzzle.domain.usecase.ChangeVideoScopeUseCase
9 | import com.juniori.puzzle.domain.usecase.DeleteVideoUseCase
10 | import com.juniori.puzzle.domain.usecase.GetUserInfoByUidUseCase
11 | import com.juniori.puzzle.domain.usecase.GetUserInfoUseCase
12 | import com.juniori.puzzle.domain.usecase.UpdateLikeStatusUseCase
13 | import dagger.hilt.android.lifecycle.HiltViewModel
14 | import kotlinx.coroutines.flow.MutableStateFlow
15 | import kotlinx.coroutines.flow.StateFlow
16 | import kotlinx.coroutines.launch
17 | import javax.inject.Inject
18 |
19 | @HiltViewModel
20 | class PlayVideoViewModel @Inject constructor(
21 | getUserInfoUseCase: GetUserInfoUseCase,
22 | private val updateLikeStatusUseCase: UpdateLikeStatusUseCase,
23 | private val deleteVideoUseCase: DeleteVideoUseCase,
24 | private val changeVideoScopeUseCase: ChangeVideoScopeUseCase,
25 | private val getUserInfoByUidUseCase: GetUserInfoByUidUseCase
26 | ) : ViewModel() {
27 | private val _getLoginInfoFlow = MutableStateFlow?>(null)
28 | val getLoginInfoFlow: StateFlow?> = _getLoginInfoFlow
29 |
30 | private val _getPublisherInfoFlow = MutableStateFlow?>(null)
31 | val getPublisherInfoFlow: StateFlow?> = _getPublisherInfoFlow
32 |
33 | private val _deleteFlow = MutableStateFlow?>(null)
34 | val deleteFlow: StateFlow?> = _deleteFlow
35 |
36 | private val _videoFlow = MutableStateFlow?>(null)
37 | val videoFlow: StateFlow?> = _videoFlow
38 |
39 | private val _likeState = MutableStateFlow(false)
40 | val likeState: StateFlow
41 | get() = _likeState
42 |
43 | init {
44 | _getLoginInfoFlow.value = getUserInfoUseCase()
45 | }
46 |
47 | fun initVideoFlow(currentVideo: VideoInfoEntity) {
48 | viewModelScope.launch {
49 | _videoFlow.emit(Resource.Success(currentVideo))
50 | }
51 | }
52 |
53 | fun getPublisherInfo(uid: String) {
54 | viewModelScope.launch {
55 | _getPublisherInfoFlow.value = getUserInfoByUidUseCase(uid)
56 | }
57 | }
58 |
59 | fun setCurrentLikeStatus(currentVideo: VideoInfoEntity, currentUid: String) {
60 | viewModelScope.launch {
61 | _likeState.emit(currentVideo.likedUserUidList.contains(currentUid))
62 | }
63 | }
64 |
65 | fun changeLikeStatus(currentVideo: VideoInfoEntity, currentUid: String) {
66 | viewModelScope.launch {
67 | _videoFlow.emit(Resource.Loading)
68 | val result = updateLikeStatusUseCase(currentVideo, currentUid, likeState.value)
69 | if (result is Resource.Success) {
70 | _videoFlow.emit(result)
71 | _likeState.emit(likeState.value.not())
72 | }
73 | }
74 | }
75 |
76 | fun deleteVideo(documentId: String) = viewModelScope.launch {
77 | _deleteFlow.emit(Resource.Loading)
78 | _deleteFlow.emit(deleteVideoUseCase(documentId))
79 | }
80 |
81 | fun updateVideoPrivacy(documentInfo: VideoInfoEntity) = viewModelScope.launch {
82 | _videoFlow.emit(Resource.Loading)
83 | _videoFlow.emit(changeVideoScopeUseCase(documentInfo))
84 | }
85 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/ui/sensor/GolfBallView.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.ui.sensor
2 |
3 | import android.content.Context
4 | import android.hardware.Sensor
5 | import android.hardware.SensorEvent
6 | import android.hardware.SensorEventListener
7 | import android.hardware.SensorManager
8 | import android.os.Build
9 | import android.util.AttributeSet
10 | import android.view.Surface
11 | import android.view.WindowInsets
12 | import android.view.WindowManager
13 | import androidx.appcompat.widget.AppCompatImageView
14 | import kotlin.math.max
15 |
16 | class GolfBallView(context: Context, attrs: AttributeSet) : AppCompatImageView(context, attrs),
17 | SensorEventListener {
18 |
19 | private val sensorManager: SensorManager by lazy {
20 | context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
21 | }
22 | private val windowManager: WindowManager by lazy {
23 | context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
24 | }
25 | private val maxSize: Pair by lazy {
26 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
27 | Pair(
28 | windowManager.currentWindowMetrics.bounds.width(),
29 | windowManager.currentWindowMetrics.bounds.height()
30 | )
31 | } else {
32 | Pair(windowManager.defaultDisplay.width, windowManager.defaultDisplay.height)
33 | }
34 | }
35 |
36 | override fun onSensorChanged(event: SensorEvent?) {
37 | if (event?.sensor == sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)) {
38 | event?.let {
39 | var sensorX = x
40 | var sensorY = y
41 | when (windowManager.defaultDisplay.rotation) {
42 | Surface.ROTATION_0 -> {
43 | sensorX = event.values[0]
44 | sensorY = event.values[1]
45 | }
46 | Surface.ROTATION_90 -> {
47 | sensorX = -event.values[1]
48 | sensorY = event.values[0]
49 | }
50 | Surface.ROTATION_180 -> {
51 | sensorX = -event.values[0]
52 | sensorY = -event.values[1]
53 | }
54 | Surface.ROTATION_270 -> {
55 | sensorX = event.values[1]
56 | sensorY = -event.values[0]
57 | }
58 | }
59 |
60 | x -= sensorX
61 | y += sensorY
62 |
63 | var isDrawXNeeded = true
64 | var isDrawYNeeded = true
65 | if (x < 0) {
66 | x = 0f
67 | } else if (x + width > maxSize.first) {
68 | x = (maxSize.first - width).toFloat()
69 | } else {
70 | isDrawXNeeded = false
71 | }
72 |
73 | if (y < 0) {
74 | y = 0f
75 | } else if (y + height > maxSize.second) {
76 | y = (maxSize.second - height).toFloat()
77 | } else {
78 | isDrawYNeeded = false
79 | }
80 |
81 | if (isDrawXNeeded || isDrawYNeeded) requestLayout()
82 | }
83 | }
84 | }
85 |
86 | override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit
87 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/ui/sensor/SensorActivity.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.ui.sensor
2 |
3 | import android.content.Context
4 | import android.hardware.Sensor
5 | import android.hardware.SensorManager
6 | import androidx.appcompat.app.AppCompatActivity
7 | import android.os.Bundle
8 | import com.juniori.puzzle.R
9 | import com.juniori.puzzle.databinding.ActivitySensorBinding
10 |
11 | class SensorActivity : AppCompatActivity() {
12 | private val sensorManager: SensorManager by lazy {
13 | getSystemService(Context.SENSOR_SERVICE) as SensorManager
14 | }
15 | private lateinit var sensor: Sensor
16 | private lateinit var binding: ActivitySensorBinding
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | binding = ActivitySensorBinding.inflate(layoutInflater)
21 | sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
22 | setContentView(binding.root)
23 | }
24 |
25 | override fun onResume() {
26 | super.onResume()
27 | sensorManager.registerListener(binding.sensorBall, sensor, SensorManager.SENSOR_DELAY_UI)
28 | }
29 |
30 | override fun onPause() {
31 | sensorManager.unregisterListener(binding.sensorBall)
32 | super.onPause()
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/util/Extensions.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.util
2 |
3 | import android.annotation.SuppressLint
4 | import android.location.Address
5 | import com.google.android.gms.tasks.Task
6 | import kotlinx.coroutines.ExperimentalCoroutinesApi
7 | import kotlinx.coroutines.suspendCancellableCoroutine
8 | import java.text.SimpleDateFormat
9 | import java.util.*
10 | import kotlin.coroutines.resumeWithException
11 |
12 | @SuppressLint("SimpleDateFormat")
13 | private val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
14 |
15 | @OptIn(ExperimentalCoroutinesApi::class)
16 | suspend fun Task.await(): T {
17 | return suspendCancellableCoroutine { cont ->
18 | addOnCompleteListener {
19 | it.exception?.let { exception ->
20 | cont.resumeWithException(exception)
21 | } ?: cont.resume(it.result, null)
22 | }
23 | }
24 | }
25 |
26 | fun Address.toAddressString(): String {
27 | val list= mutableListOf()
28 | if(adminArea !=null) list.add(adminArea)
29 | if(locality != null){
30 | list.add(locality)
31 | }
32 | else{
33 | list.add(subLocality)
34 | }
35 | if(thoroughfare!=null){
36 | list.add(thoroughfare)
37 | }
38 |
39 | return list.joinToString(" ")
40 | }
41 |
42 | fun String.toDate(): Date = formatter.parse(this) ?: Date()
43 |
44 | fun String.toLocationKeyword(): List {
45 | val result = mutableListOf()
46 | for (len in 1..length) {
47 | for (index in 0..length - len) {
48 | result.add(substring(index, index + len))
49 | }
50 | }
51 | return result
52 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/util/FileIOExtenstions.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.util
2 |
3 | import android.content.ContentResolver
4 | import android.graphics.Bitmap
5 | import android.net.Uri
6 | import java.io.ByteArrayOutputStream
7 | import java.io.File
8 | import java.io.FileOutputStream
9 |
10 | fun ByteArray.saveInFile(filePath: String): Unit? {
11 | return try {
12 | val file = File(filePath)
13 | FileOutputStream(file).use { fileOutputStream ->
14 | fileOutputStream.write(this)
15 | }
16 | } catch (e: Exception) {
17 | null
18 | }
19 | }
20 |
21 | fun Uri.readBytes(contentResolver: ContentResolver): ByteArray? {
22 | return try {
23 | contentResolver.openInputStream(this)?.use { inputStream ->
24 | inputStream.readBytes()
25 | }
26 | } catch (e: Exception) {
27 | null
28 | }
29 | }
30 |
31 | fun Bitmap.compressToBytes(format: Bitmap.CompressFormat, quality: Int): ByteArray? {
32 | return try {
33 | ByteArrayOutputStream().use { outputStream ->
34 | val compressed = this.compress(format, quality, outputStream)
35 | if (compressed) outputStream.toByteArray() else null
36 | }
37 | } catch (e: Exception) {
38 | null
39 | }
40 | }
41 |
42 | fun String.deleteIfFileUri(): Boolean =
43 | try {
44 | File(this).let { file ->
45 | if (file.exists()) file.delete() else false
46 | }
47 | } catch (e: Exception) {
48 | false
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/util/GalleryDiffCallBack.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.util
2 |
3 | import androidx.recyclerview.widget.DiffUtil
4 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
5 |
6 | class GalleryDiffCallBack: DiffUtil.ItemCallback() {
7 | override fun areItemsTheSame(oldItem: VideoInfoEntity, newItem: VideoInfoEntity): Boolean {
8 | return oldItem.videoUrl == newItem.videoUrl
9 | }
10 |
11 | override fun areContentsTheSame(oldItem: VideoInfoEntity, newItem: VideoInfoEntity): Boolean {
12 | return oldItem == newItem
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/util/GalleryState.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.util
2 |
3 | enum class GalleryState {
4 | NONE,
5 | END_PAGING,
6 | NETWORK_ERROR_PAGING,
7 | NETWORK_ERROR_BASE,
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/util/LiveDataUtils.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.util
2 |
3 | import androidx.annotation.VisibleForTesting
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.Observer
6 | import java.util.concurrent.CountDownLatch
7 | import java.util.concurrent.TimeUnit
8 | import java.util.concurrent.TimeoutException
9 |
10 | /* Copyright 2019 Google LLC.
11 | SPDX-License-Identifier: Apache-2.0 */
12 | @VisibleForTesting(otherwise = VisibleForTesting.NONE)
13 | fun LiveData.getOrAwaitValue(
14 | time: Long = 2,
15 | timeUnit: TimeUnit = TimeUnit.SECONDS,
16 | afterObserve: () -> Unit = {}
17 | ): T {
18 | var data: T? = null
19 | val latch = CountDownLatch(1)
20 | val observer = object : Observer {
21 | override fun onChanged(o: T?) {
22 | data = o
23 | latch.countDown()
24 | this@getOrAwaitValue.removeObserver(this)
25 | }
26 | }
27 | this.observeForever(observer)
28 |
29 | try {
30 | afterObserve.invoke()
31 |
32 | // Don't wait indefinitely if the LiveData is not set.
33 | if (!latch.await(time, timeUnit)) {
34 | throw TimeoutException("LiveData value was never set.")
35 | }
36 |
37 | } finally {
38 | this.removeObserver(observer)
39 | }
40 |
41 | @Suppress("UNCHECKED_CAST")
42 | return data as T
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/util/PagingConst.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.util
2 |
3 | object PagingConst {
4 | const val ITEM_CNT = 12
5 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/util/PlayResultConst.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.util
2 |
3 | object PlayResultConst {
4 | const val RESULT_NOTTING = 0
5 | const val RESULT_DELETE = 1
6 | const val RESULT_TO_PRIVATE = 2
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/util/ProgressDialog.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.util
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Dialog
5 | import android.content.Context
6 | import android.graphics.Color
7 | import android.graphics.drawable.ColorDrawable
8 | import android.view.LayoutInflater
9 | import android.view.Window
10 | import com.juniori.puzzle.databinding.DialogProgressBinding
11 |
12 | class ProgressDialog(context: Context) : Dialog(context) {
13 | val binding = DialogProgressBinding.inflate(LayoutInflater.from(context))
14 |
15 | init {
16 | requestWindowFeature(Window.FEATURE_NO_TITLE)
17 | setContentView(binding.root)
18 | setCancelable(false)
19 |
20 | window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
21 | }
22 |
23 | @SuppressLint("SetTextI18n")
24 | fun setProgress(progress: Int) {
25 | binding.progressText.text = "$progress%"
26 | binding.progressBar.setProgress(progress, true)
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/util/PuzzleDialog.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.util
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import android.widget.ArrayAdapter
6 | import android.widget.ListPopupWindow
7 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
8 | import com.juniori.puzzle.R
9 | import dagger.hilt.android.qualifiers.ActivityContext
10 | import javax.inject.Inject
11 |
12 | class PuzzleDialog @Inject constructor(
13 | @ActivityContext private val context: Context
14 | ) {
15 | private var dialog = MaterialAlertDialogBuilder(context, R.style.Theme_Puzzle_Dialog)
16 | private val listPopupWindow = ListPopupWindow(
17 | context,
18 | null,
19 | com.google.android.material.R.attr.listPopupWindowStyle
20 | )
21 |
22 | fun buildAlertDialog(yesCallback: () -> Unit, noCallback: () -> Unit): PuzzleDialog {
23 | dialog.setPositiveButton(R.string.all_yes) { _, _ ->
24 | yesCallback()
25 | }.setNegativeButton(R.string.all_no) { _, _ ->
26 | noCallback()
27 | }.also { dialog = it }
28 | return this
29 | }
30 |
31 | fun buildConfirmationDialog(
32 | confirmCallback: () -> Unit,
33 | cancelCallback: () -> Unit
34 | ): PuzzleDialog {
35 | dialog.setPositiveButton(R.string.all_accept) { _, _ ->
36 | confirmCallback()
37 | }.setNegativeButton(R.string.all_cancel) { _, _ ->
38 | cancelCallback()
39 | }.also { dialog = it }
40 | return this
41 | }
42 |
43 | fun buildListPopup(anchorView: View, items:Array):PuzzleDialog{
44 | listPopupWindow.anchorView = anchorView
45 |
46 | val spinnerAdapter =
47 | ArrayAdapter(context, R.layout.item_popup_list, items)
48 | listPopupWindow.setAdapter(spinnerAdapter)
49 |
50 | return this
51 | }
52 |
53 | fun setListPopupItemListener(listener:android.widget.AdapterView.OnItemClickListener){
54 | listPopupWindow.setOnItemClickListener(listener)
55 | }
56 |
57 | fun setMessage(message: String): PuzzleDialog {
58 | dialog.setMessage(message).also { dialog = it }
59 | return this
60 | }
61 |
62 | fun setTitle(title: String): PuzzleDialog {
63 | dialog.setTitle(title).also { dialog = it }
64 | return this
65 | }
66 |
67 | fun showDialog(): androidx.appcompat.app.AlertDialog = dialog.show()
68 |
69 | fun showPopupList() = listPopupWindow.show()
70 | fun dismissPopupList() = listPopupWindow.dismiss()
71 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/util/SortType.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.util
2 |
3 | enum class SortType(val value: String) {
4 | LIKE("like_count"), NEW("update_time")
5 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/util/StateManager.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.util
2 |
3 | import android.app.AlertDialog
4 | import android.content.Context
5 | import android.graphics.Color
6 | import android.graphics.drawable.ColorDrawable
7 | import android.view.LayoutInflater
8 | import android.view.ViewGroup
9 | import com.juniori.puzzle.R
10 |
11 | class StateManager constructor(
12 | private val context: Context
13 | ) {
14 | private val builder = AlertDialog.Builder(context)
15 | private var dialog: AlertDialog? = null
16 |
17 | fun createLoadingDialog(parent: ViewGroup?) {
18 | if (dialog != null) {
19 | return
20 | }
21 | val view = LayoutInflater.from(context).inflate(R.layout.loading_layout, parent, false)
22 | dialog = builder.setView(view).create().apply {
23 | window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
24 | setCancelable(false)
25 | }
26 | }
27 |
28 | fun showLoadingDialog() {
29 | dialog ?: return
30 | dialog?.show()
31 | }
32 |
33 | fun dismissLoadingDialog() {
34 | dialog ?: return
35 | dialog?.dismiss()
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/util/VideoMetaDataUtil.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.util
2 |
3 | import android.graphics.Bitmap
4 | import android.media.MediaMetadataRetriever
5 |
6 | object VideoMetaDataUtil {
7 |
8 | fun getVideoDurationInSeconds(videoFilePath: String): Long? {
9 | return MediaMetadataRetriever().run {
10 | setDataSource(videoFilePath)
11 | extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
12 | ?.let { milliseconds: String ->
13 | this.release()
14 | milliseconds.toLong() / 1000
15 | }
16 | }
17 | }
18 |
19 | fun extractThumbnail(videoFilePath: String): ByteArray? {
20 | return MediaMetadataRetriever().run {
21 | setDataSource(videoFilePath)
22 | getFrameAtTime(0, MediaMetadataRetriever.OPTION_CLOSEST_SYNC)?.let { bitmap ->
23 | this.release()
24 | bitmap.compressToBytes(Bitmap.CompressFormat.JPEG, 100)
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/juniori/puzzle/util/WeatherConverter.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.util
2 |
3 | import com.juniori.puzzle.data.weather.WeatherResponse
4 | import com.juniori.puzzle.domain.entity.WeatherEntity
5 | import java.util.*
6 | import kotlin.math.roundToInt
7 |
8 | fun WeatherResponse.toItem(): List {
9 | return list.map {
10 | WeatherEntity(
11 | date = it.dtTxt.toDate(),
12 | temp = it.main.temp.roundToInt(),
13 | feelsLike = it.main.feelsLike.roundToInt(),
14 | minTemp = it.main.tempMin.roundToInt(),
15 | maxTemp = it.main.tempMax.roundToInt(),
16 | description = it.weather[0].description,
17 | icon = "${WEATHER_ICON_URL}/${it.weather[0].icon}@2x.png"
18 | )
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/flag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-hdpi/flag.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/golf_ball.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-hdpi/golf_ball.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/grass_rectangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-hdpi/grass_rectangle.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/home_background_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-hdpi/home_background_image.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/home_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-hdpi/home_icon.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/flag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-mdpi/flag.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/golf_ball.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-mdpi/golf_ball.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/grass_rectangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-mdpi/grass_rectangle.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/home_background_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-mdpi/home_background_image.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/home_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-mdpi/home_icon.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/flag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-xhdpi/flag.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/golf_ball.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-xhdpi/golf_ball.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/grass_rectangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-xhdpi/grass_rectangle.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/home_background_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-xhdpi/home_background_image.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/home_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-xhdpi/home_icon.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/flag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-xxhdpi/flag.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/golf_ball.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-xxhdpi/golf_ball.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/grass_rectangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-xxhdpi/grass_rectangle.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/home_background_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-xxhdpi/home_background_image.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/home_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-xxhdpi/home_icon.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/flag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-xxxhdpi/flag.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/golf_ball.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-xxxhdpi/golf_ball.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/grass_rectangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-xxxhdpi/grass_rectangle.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/home_background_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-xxxhdpi/home_background_image.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/home_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable-xxxhdpi/home_icon.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/add_gallery_icon.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/add_photo_icon.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/all_location_icon.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/all_memo_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/all_outlined_box.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/all_popup_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/all_search_icon.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/all_search_view_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/all_toolbar_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/alpha_gradient_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/camera_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/camera_button_recording.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
7 |
8 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/cursor_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/home_refresh_icon.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/main_addvideoicon_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/main_bottommenucolorselector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/main_homeicon_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/main_mygalleryicon_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/main_mypageicon_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/main_othersgalleryicon_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/my_gallery_add_icon_outlined.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/play_calendar_icon.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/play_comment_icon.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/play_go_back.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/play_like_not_selected.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/play_like_selected.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/play_setting.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/splash_background.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/drawable/splash_background.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/text_container_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/upload2_golfcourse_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/upload2_time_icon_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/font/noto_sans_kr_medium.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/font/noto_sans_kr_medium.otf
--------------------------------------------------------------------------------
/app/src/main/res/font/noto_sans_kr_regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/font/noto_sans_kr_regular.otf
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_camera.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
20 |
21 |
29 |
30 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
19 |
20 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_sensor.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_update_nickname.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
26 |
27 |
45 |
46 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/bottomsheet_addvideo.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
23 |
24 |
29 |
30 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/bottomsheet_playvideo.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
21 |
22 |
25 |
26 |
29 |
30 |
33 |
34 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_progress.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
18 |
19 |
29 |
30 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_mygallery.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
23 |
24 |
25 |
34 |
38 |
39 |
40 |
41 |
52 |
53 |
63 |
64 |
73 |
74 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_upload_step1.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
17 |
18 |
26 |
27 |
39 |
40 |
50 |
51 |
63 |
64 |
78 |
79 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_gallery_recycler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
27 |
28 |
29 |
32 |
33 |
46 |
47 |
57 |
58 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_information.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
11 |
12 |
15 |
16 |
17 |
18 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_popup_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_weather_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
17 |
18 |
28 |
29 |
40 |
41 |
51 |
52 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/loading_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main_bottomnavigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/playvideo_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/navigation/main_bottomnavigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
30 |
31 |
36 |
37 |
42 |
43 |
48 |
49 |
52 |
53 |
54 |
58 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/loading_progress.json:
--------------------------------------------------------------------------------
1 | {"v":"5.8.1","fr":30,"ip":0,"op":60,"w":300,"h":300,"nm":"loading_6","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":60,"s":[360]}],"ix":10},"p":{"a":0,"k":[150.00000000000003,150.00000000000003,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[30.000000000000004,30.000000000000004,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[300,300],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.4117647058823529,0.7019607843137254,0.5529411764705883,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":50,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":60,"s":[99]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[1]},{"t":50,"s":[100]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":60,"s":[3]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":30,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150.00000000000003,150.00000000000003,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[30.000000000000004,30.000000000000004,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[300,300],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.4745098039215686,0.8,0.5450980392156862,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":50,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":0,"bm":0}],"markers":[]}
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF398360
4 | #FF489b5d
5 | #FFFFFFFF
6 | #FFc0c7bc
7 | #FF398360
8 | #FFb6b8bd
9 | #FF7b7b7b
10 | #FF418e5e
11 | #BB121212
12 | #FF000000
13 | #FFFFFFFF
14 |
--------------------------------------------------------------------------------
/app/src/main/res/values-sw600dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 40dp
4 | 30dp
5 |
6 |
7 | 20dp
8 | 48dp
9 | 10000dp
10 | 32dp
11 | 32dp
12 | 46dp
13 |
14 |
15 | 14dp
16 | 16dp
17 | 16dp
18 |
19 |
20 | 32dp
21 | 16dp
22 |
23 |
24 | 16dp
25 | 16dp
26 | 16dp
27 |
28 |
29 | -12dp
30 | -30dp
31 |
32 |
33 | 28dp
34 | 23dp
35 | 100dp
36 | 90dp
37 | 600dp
38 | 50dp
39 |
40 |
41 | 9
42 | 1
43 | 3
44 |
--------------------------------------------------------------------------------
/app/src/main/res/values/array.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @string/othergallery_orderby_new
5 | - @string/othergallery_orderby_like
6 |
7 |
8 |
9 | - 공유하기
10 | - 삭제
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF69B38D
4 | #FF79CC8B
5 | #FFFFFFFF
6 | #FFF3FAEE
7 | #FF69B38D
8 | #FFE8EAEF
9 | #FFAAAAAA
10 | #FF71BF8B
11 | #FFFFFFFF
12 | #FFDD5B6E
13 | #FF000000
14 | #FFFFFFFF
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 20dp
4 | 20dp
5 |
6 |
7 | 20dp
8 | 24dp
9 | 10000dp
10 | 16dp
11 | 16dp
12 | 32dp
13 |
14 |
15 | 14dp
16 | 16dp
17 | 8dp
18 |
19 |
20 | 16dp
21 | 8dp
22 |
23 |
24 | 8dp
25 | 8dp
26 | 8dp
27 |
28 |
29 | -12dp
30 | -30dp
31 |
32 |
33 | 28dp
34 | 20dp
35 | 100dp
36 | 60dp
37 | 600dp
38 | 50dp
39 |
40 |
41 | 7
42 | 3
43 | 2
44 |
--------------------------------------------------------------------------------
/app/src/main/res/values/fonts.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
11 |
12 |
16 |
17 |
20 |
21 |
25 |
26 |
29 |
30 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/shapes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
12 |
13 |
17 |
18 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
23 |
24 |
32 |
33 |
34 |
42 |
43 |
50 |
51 |
52 |
55 |
56 |
61 |
62 |
65 |
66 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
27 |
28 |
36 |
37 |
42 |
43 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/java/com/juniori/puzzle/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle
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 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/juniori/puzzle/data/firebase/dto/VideoItemConvertTest.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.data.firebase.dto
2 |
3 | import com.juniori.puzzle.domain.entity.VideoInfoEntity
4 | import org.junit.Assert.*
5 |
6 | import org.junit.Before
7 | import org.junit.Test
8 |
9 | class VideoItemConvertTest {
10 | lateinit var videoDetail: VideoDetail
11 | lateinit var videoItem: VideoItem
12 | lateinit var videoInfoEntity: VideoInfoEntity
13 |
14 | @Before
15 | fun setUp() {
16 | val listValue = StringValues(listOf(StringValue("aaa"), StringValue("bbb"), StringValue("ccc"), StringValue("ddd"), StringValue("eee")))
17 |
18 | videoDetail = VideoDetail(
19 | StringValue("Test_uid"),
20 | StringValue("Video_Url"),
21 | StringValue("Thumb_url"),
22 | BooleanValue(true),
23 | IntegerValue(5),
24 | ArrayValue(listValue),
25 | IntegerValue(987595293094203),
26 | StringValue("Location"),
27 | ArrayValue(listValue),
28 | StringValue("Golf")
29 | )
30 | videoItem = VideoItem("videoReal/VideoName", videoDetail)
31 |
32 | videoInfoEntity = VideoInfoEntity(
33 | "VideoName",
34 | "Test_uid",
35 | "Video_Url",
36 | "Thumb_url",
37 | true,
38 | 5,
39 | listOf("aaa", "bbb", "ccc", "ddd", "eee"),
40 | 987595293094203,
41 | "Location",
42 | listOf("aaa"),
43 | "Golf"
44 | )
45 | }
46 |
47 | @Test
48 | fun normalConvertTest() {
49 | val testVideoInfoEntity = videoItem.getVideoInfoEntity()
50 |
51 | assertEquals(videoInfoEntity, testVideoInfoEntity)
52 | }
53 |
54 | @Test
55 | fun emptyCommentConvertTest() {
56 | val testVideoDetail = videoDetail.copy(likeCount = IntegerValue(0), likedUserList = ArrayValue(StringValues(null)))
57 | val testVideoItem = videoItem.copy(videoDetail = testVideoDetail)
58 |
59 | val testVideoEntity = testVideoItem.getVideoInfoEntity()
60 | val targetVideoEntity = videoInfoEntity.copy(likedCount = 0, likedUserUidList = emptyList())
61 |
62 | assertEquals(targetVideoEntity, testVideoEntity)
63 | }
64 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/juniori/puzzle/ui/mypage/UpdateNicknameUseCaseTest.kt:
--------------------------------------------------------------------------------
1 | package com.juniori.puzzle.ui.mypage
2 |
3 | import com.juniori.puzzle.data.Resource
4 | import com.juniori.puzzle.domain.entity.UserInfoEntity
5 | import com.juniori.puzzle.domain.repository.AuthRepository
6 | import com.juniori.puzzle.domain.repository.VideoRepository
7 | import com.juniori.puzzle.domain.usecase.UpdateNicknameUseCase
8 | import kotlinx.coroutines.*
9 | import kotlinx.coroutines.flow.*
10 | import kotlinx.coroutines.test.resetMain
11 | import kotlinx.coroutines.test.setMain
12 | import org.junit.After
13 | import org.junit.Assert.*
14 |
15 | import org.junit.Before
16 | import org.junit.Test
17 | import org.mockito.Mockito
18 |
19 | class UpdateNicknameUseCaseTest {
20 | lateinit var updateNicknameUseCase: UpdateNicknameUseCase
21 | lateinit var mockAuthRepository: AuthRepository
22 | lateinit var mockVideoRepository: VideoRepository
23 |
24 | private val testNickname = "K052"
25 | private val testUserInfoEntity = UserInfoEntity("UID", "K052", "profileImage")
26 |
27 | @OptIn(DelicateCoroutinesApi::class)
28 | private val mainThreadSurrogate = newSingleThreadContext("UI thread")
29 |
30 | @OptIn(ExperimentalCoroutinesApi::class)
31 | @Before
32 | fun setUp() {
33 | Dispatchers.setMain(mainThreadSurrogate)
34 | }
35 |
36 | @Test
37 | fun normalUpdateUserInfoTest(): Unit = runBlocking {
38 | mockAuthRepository = Mockito.mock(AuthRepository::class.java)
39 | mockVideoRepository = Mockito.mock(VideoRepository::class.java)
40 |
41 | Mockito.`when`(mockAuthRepository.updateNickname(testNickname)).thenReturn(Resource.Success(testUserInfoEntity))
42 | Mockito.`when`(mockVideoRepository.updateServerNickname(testUserInfoEntity)).thenReturn(Resource.Success(testUserInfoEntity))
43 |
44 | updateNicknameUseCase = UpdateNicknameUseCase(mockAuthRepository, mockVideoRepository)
45 | assertEquals(Resource.Success(testUserInfoEntity) ,updateNicknameUseCase(testNickname))
46 | }
47 |
48 | @Test
49 | fun authFailUpdateUserInfoTest(): Unit = runBlocking {
50 | mockAuthRepository = Mockito.mock(AuthRepository::class.java)
51 | mockVideoRepository = Mockito.mock(VideoRepository::class.java)
52 |
53 | Mockito.`when`(mockAuthRepository.updateNickname(testNickname)).thenReturn(Resource.Failure(Exception()))
54 | Mockito.`when`(mockVideoRepository.updateServerNickname(testUserInfoEntity)).thenReturn(Resource.Success(testUserInfoEntity))
55 |
56 | updateNicknameUseCase = UpdateNicknameUseCase(mockAuthRepository, mockVideoRepository)
57 | assertTrue(updateNicknameUseCase(testNickname) is Resource.Failure)
58 | }
59 |
60 | @Test
61 | fun videoFailUpdateUserInfoTest(): Unit = runBlocking {
62 | mockAuthRepository = Mockito.mock(AuthRepository::class.java)
63 | mockVideoRepository = Mockito.mock(VideoRepository::class.java)
64 |
65 | Mockito.`when`(mockAuthRepository.updateNickname(testNickname)).thenReturn(Resource.Success(testUserInfoEntity))
66 | Mockito.`when`(mockVideoRepository.updateServerNickname(testUserInfoEntity)).thenReturn(Resource.Failure(Exception()))
67 |
68 | updateNicknameUseCase = UpdateNicknameUseCase(mockAuthRepository, mockVideoRepository)
69 | assertTrue(updateNicknameUseCase(testNickname) is Resource.Failure)
70 | }
71 |
72 | @OptIn(ExperimentalCoroutinesApi::class)
73 | @After
74 | fun tearDown() {
75 | Dispatchers.resetMain()
76 | mainThreadSurrogate.close()
77 | }
78 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | dependencies {
7 | classpath 'com.google.dagger:hilt-android-gradle-plugin:2.44'
8 | classpath 'com.google.gms:google-services:4.3.14'
9 | }
10 | }
11 |
12 | plugins {
13 | id 'com.android.application' version '7.3.1' apply false
14 | id 'com.android.library' version '7.3.1' apply false
15 | id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
16 | }
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android07-Puzzle/4f60da89808a0ddc00dd6f9c0a0d90b5f9b37157/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Nov 14 11:10:10 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 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "Puzzle"
16 | include ':app'
17 |
--------------------------------------------------------------------------------