├── .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 |