├── .DS_Store
├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── google-services.json
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── kiwi
│ │ └── kiwitalk
│ │ └── ExampleInstrumentedTest.kt
│ ├── debug
│ ├── ic_launcher-playstore.png
│ └── res
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── layout_map_fragment_dummy.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
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── map_style.json
│ ├── java
│ │ └── com
│ │ │ └── kiwi
│ │ │ └── kiwitalk
│ │ │ ├── KiwiApplication.kt
│ │ │ ├── NetworkStateManager.kt
│ │ │ ├── di
│ │ │ ├── ApplicationModule.kt
│ │ │ ├── ChatModule.kt
│ │ │ ├── FirebaseModule.kt
│ │ │ ├── KakaoApiModule.kt
│ │ │ └── RepositoryModule.kt
│ │ │ ├── model
│ │ │ └── ClusterMarker.kt
│ │ │ ├── ui
│ │ │ ├── BindingAdapter.kt
│ │ │ ├── chatting
│ │ │ │ └── ChattingActivity.kt
│ │ │ ├── home
│ │ │ │ ├── ChatListFragment.kt
│ │ │ │ ├── ChatListViewAdapter.kt
│ │ │ │ ├── ChatListViewModel.kt
│ │ │ │ ├── HomeActivity.kt
│ │ │ │ ├── ProfileSettingFragment.kt
│ │ │ │ └── ProfileViewModel.kt
│ │ │ ├── keyword
│ │ │ │ ├── SearchKeywordFragment.kt
│ │ │ │ ├── SearchKeywordViewModel.kt
│ │ │ │ └── recyclerview
│ │ │ │ │ ├── KeywordAdapter.kt
│ │ │ │ │ ├── KeywordCategoryAdapter.kt
│ │ │ │ │ └── SelectedKeywordAdapter.kt
│ │ │ ├── login
│ │ │ │ ├── LoginActivity.kt
│ │ │ │ ├── LoginViewModel.kt
│ │ │ │ ├── ProgressDialog.kt
│ │ │ │ ├── SplashActivity.kt
│ │ │ │ └── SplashViewModel.kt
│ │ │ ├── newchat
│ │ │ │ ├── NewChatActivity.kt
│ │ │ │ ├── NewChatFragment.kt
│ │ │ │ ├── NewChatViewModel.kt
│ │ │ │ ├── SearchPlaceFragment.kt
│ │ │ │ └── SearchPlaceViewModel.kt
│ │ │ └── search
│ │ │ │ ├── ChatAdapter.kt
│ │ │ │ ├── ChatInfoViewHolder.kt
│ │ │ │ ├── ChatJoinDialog.kt
│ │ │ │ ├── ClusterMarkerRenderer.kt
│ │ │ │ ├── SearchChatActivity.kt
│ │ │ │ ├── SearchChatMapFragment.kt
│ │ │ │ └── SearchChatMapViewModel.kt
│ │ │ └── util
│ │ │ ├── ChangeExpansion.kt
│ │ │ ├── Const.kt
│ │ │ ├── Event.kt
│ │ │ └── Util.kt
│ └── res
│ │ ├── color
│ │ ├── color_chip_background_keyword.xml
│ │ └── color_chip_text_keyword.xml
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── background_marker_text.xml
│ │ ├── bg_chat_list_unread_count.xml
│ │ ├── ic_add_location.xml
│ │ ├── ic_add_photo.xml
│ │ ├── ic_backarrow_48.xml
│ │ ├── ic_baseline_cloud_sync_24.xml
│ │ ├── ic_chatlist_searchchat.png
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_location_cluster.xml
│ │ ├── ic_location_on.xml
│ │ ├── ic_location_on_click.xml
│ │ ├── ic_profile_base_image.xml
│ │ ├── ic_search.xml
│ │ ├── ic_search_chat_new_chat.png
│ │ ├── logo_splash_dark.png
│ │ ├── logo_splash_light.png
│ │ ├── logo_splash_transparent.png
│ │ └── logo_splash_withtext.png
│ │ ├── layout
│ │ ├── activity_chatting.xml
│ │ ├── activity_home.xml
│ │ ├── activity_login.xml
│ │ ├── activity_new_chat.xml
│ │ ├── activity_search_chat.xml
│ │ ├── activity_splash.xml
│ │ ├── dialog_join_chat.xml
│ │ ├── dialog_login_progress.xml
│ │ ├── dialog_new_chat.xml
│ │ ├── fragment_chat_list.xml
│ │ ├── fragment_new_chat.xml
│ │ ├── fragment_profile_setting.xml
│ │ ├── fragment_search_chat_map.xml
│ │ ├── fragment_search_keyword.xml
│ │ ├── fragment_search_place.xml
│ │ ├── item_chat_list.xml
│ │ ├── item_chat_list_no_image.xml
│ │ ├── item_keyword.xml
│ │ ├── item_keywordcategory.xml
│ │ ├── item_marker.xml
│ │ ├── view_marker_detail.xml
│ │ └── view_marker_preview.xml
│ │ ├── menu
│ │ ├── menu_chat_list.xml
│ │ ├── menu_new_chat.xml
│ │ ├── menu_search_chat_map.xml
│ │ └── menu_search_keyword_toolbar.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── navigation
│ │ ├── home_nav_graph.xml
│ │ ├── new_chat_nav_graph.xml
│ │ └── search_chat_nav_graph.xml
│ │ ├── values-night
│ │ ├── colors.xml
│ │ └── themes.xml
│ │ ├── values
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ ├── style.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ └── test
│ └── java
│ └── com
│ └── kiwi
│ └── kiwitalk
│ └── ExampleUnitTest.kt
├── build.gradle
├── chatmapper
├── .gitignore
├── build.gradle
└── src
│ └── main
│ └── java
│ └── com
│ └── kiwi
│ └── chatmapper
│ ├── ChatConst.kt
│ ├── ChatKey.kt
│ └── ChatMapper.kt
├── data
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── google-services.json
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── kiwi
│ │ └── data
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── kiwi
│ │ └── data
│ │ ├── AppPreference.kt
│ │ ├── Const.kt
│ │ ├── UserDataCallback.kt
│ │ ├── datasource
│ │ ├── local
│ │ │ ├── UserLocalDataSource.kt
│ │ │ └── UserLocalDataSourceImpl.kt
│ │ └── remote
│ │ │ ├── ExitChatRemoteDataSource.kt
│ │ │ ├── ExitChatRemoteDataSourceImpl.kt
│ │ │ ├── NewChatRemoteDataSource.kt
│ │ │ ├── NewChatRemoteDataSourceImpl.kt
│ │ │ ├── SearchChatRemoteDataSource.kt
│ │ │ ├── SearchChatRemoteDataSourceImpl.kt
│ │ │ ├── SearchKeywordRemoteDataSource.kt
│ │ │ ├── SearchPlaceRemoteDataSource.kt
│ │ │ ├── SearchPlaceRemoteDataSourceImpl.kt
│ │ │ ├── SearchService.kt
│ │ │ ├── UserRemoteDataSource.kt
│ │ │ └── UserRemoteDataSourceImpl.kt
│ │ ├── mapper
│ │ └── Mapper.kt
│ │ ├── model
│ │ └── remote
│ │ │ ├── MarkerRemote.kt
│ │ │ ├── NewChatRemote.kt
│ │ │ ├── PlaceListRemote.kt
│ │ │ └── PlaceRemote.kt
│ │ └── repository
│ │ ├── ExitChatRepositoryImpl.kt
│ │ ├── NewChatRepositoryImpl.kt
│ │ ├── ProfileSettingRepositoryImpl.kt
│ │ ├── SearchChatRepositoryImpl.kt
│ │ ├── SearchKeywordRepositoryImpl.kt
│ │ ├── SearchPlaceRepositoryImpl.kt
│ │ └── UserRepositoryImpl.kt
│ └── test
│ └── java
│ └── com
│ └── kiwi
│ └── data
│ └── ExampleUnitTest.kt
├── domain
├── .gitignore
├── build.gradle
└── src
│ └── main
│ └── java
│ └── com
│ └── kiwi
│ └── domain
│ ├── UserUiCallback.kt
│ ├── model
│ ├── ChatInfo.kt
│ ├── Keyword.kt
│ ├── KeywordCategory.kt
│ ├── Marker.kt
│ ├── NewChatInfo.kt
│ ├── PlaceChatInfo.kt
│ ├── PlaceInfo.kt
│ ├── PlaceInfoList.kt
│ └── UserInfo.kt
│ └── repository
│ ├── ExitChatRepository.kt
│ ├── NewChatRepository.kt
│ ├── ProfileSettingRepository.kt
│ ├── SearchChatRepository.kt
│ ├── SearchKeywordRepository.kt
│ ├── SearchPlaceRepository.kt
│ └── UserRepository.kt
├── gradle.properties
├── gradle
└── wrapper
│ └── gradle-wrapper.jar
├── gradlew
├── gradlew.bat
└── settings.gradle
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Gradle files
2 | .gradle/
3 | build/
4 |
5 | # Local configuration file (sdk path, etc)
6 | local.properties
7 |
8 | # Log/OS Files
9 | *.log
10 |
11 | # Android Studio generated files and folders
12 | captures/
13 | .externalNativeBuild/
14 | .cxx/
15 | *.apk
16 | output.json
17 |
18 | # IntelliJ
19 | *.iml
20 | .idea/
21 | misc.xml
22 | deploymentTargetDropDown.xml
23 | render.experimental.xml
24 |
25 | # Keystore files
26 | *.jks
27 | *.keystore
28 |
29 | # Android Profiling
30 | *.hprof
31 |
32 | # Created by https://www.toptal.com/developers/gitignore/api/androidstudio,kotlin
33 | # Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio,kotlin
34 | ### Kotlin ###
35 | # Compiled class file
36 | *.class
37 | # Log file
38 | # BlueJ files
39 | *.ctxt
40 | # Mobile Tools for Java (J2ME)
41 | .mtj.tmp/
42 | # Package Files #
43 | *.jar
44 | *.war
45 | *.nar
46 | *.ear
47 | *.zip
48 | *.tar.gz
49 | *.rar
50 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
51 | hs_err_pid*
52 | replay_pid*
53 | ### AndroidStudio ###
54 | # Covers files to be ignored for android development using Android Studio.
55 | # Built application files
56 | *.ap_
57 | *.aab
58 | # Files for the ART/Dalvik VM
59 | *.dex
60 | # Java class files
61 | # Generated files
62 | bin/
63 | gen/
64 | out/
65 | # Gradle files
66 | .gradle
67 | # Signing files
68 | .signing/
69 | # Local configuration file (sdk path, etc)
70 | # Proguard folder generated by Eclipse
71 | proguard/
72 | # Log Files
73 | # Android Studio
74 | /*/build/
75 | /*/local.properties
76 | /*/out
77 | /*/*/build
78 | /*/*/production
79 |
80 | .navigation/
81 | *.ipr
82 | *~
83 | *.swp
84 | # Android Patch
85 | gen-external-apklibs
86 | # External native build folder generated in Android Studio 2.2 and later
87 | .externalNativeBuild
88 | # NDK
89 | obj/
90 | # IntelliJ IDEA
91 |
92 | *.iws
93 | /out/
94 | # Legacy Eclipse project files
95 | .classpath
96 | .project
97 | .cproject
98 | .settings/
99 | # Mobile Tools for Java (J2ME)
100 | # Package Files #
101 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
102 | ## Plugin-specific files:
103 | # mpeltonen/sbt-idea plugin
104 | .idea_modules/
105 | # JIRA plugin
106 | atlassian-ide-plugin.xml
107 | # Mongo Explorer plugin
108 | .idea/mongoSettings.xml
109 | # Crashlytics plugin (for Android Studio and IntelliJ)
110 | com_crashlytics_export_strings.xml
111 | crashlytics.properties
112 | crashlytics-build.properties
113 | fabric.properties
114 | ### AndroidStudio Patch ###
115 | !/gradle/wrapper/gradle-wrapper.jar
116 | # End of https://www.toptal.com/developers/gitignore/api/androidstudio,kotlin
117 |
118 |
119 | # Mac .DS_Store
120 | .DS_Store
121 |
122 | # 기타 발견된 항목
123 | gradle/wrapper/gradle-wrapper.properties
124 |
125 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 부스트캠프 웹·모바일 7기
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'kotlin-kapt'
5 | id 'com.google.dagger.hilt.android'
6 | id 'com.google.gms.google-services'
7 | id 'androidx.navigation.safeargs.kotlin'
8 | id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
9 | }
10 |
11 | android {
12 | compileSdk 33
13 |
14 | defaultConfig {
15 | applicationId "com.kiwi.kiwitalk"
16 | minSdk 21
17 | targetSdk 33
18 | versionCode 1
19 | versionName "1.0.0"
20 |
21 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
22 | }
23 |
24 | buildTypes {
25 | release {
26 | minifyEnabled false
27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
28 | }
29 | }
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_1_8
32 | targetCompatibility JavaVersion.VERSION_1_8
33 | }
34 | kotlinOptions {
35 | jvmTarget = '1.8'
36 | }
37 | buildFeatures {
38 | dataBinding true
39 | viewBinding true
40 | }
41 | }
42 |
43 | dependencies {
44 | api project(":data")
45 | implementation(project(":domain"))
46 | implementation(project(":chatmapper"))
47 |
48 | implementation 'androidx.core:core-ktx:1.9.0'
49 | implementation 'androidx.appcompat:appcompat:1.5.1'
50 | implementation 'com.google.android.material:material:1.7.0'
51 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
52 | testImplementation 'junit:junit:4.13.2'
53 | androidTestImplementation 'androidx.test.ext:junit:1.1.4'
54 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
55 |
56 | implementation "androidx.activity:activity-ktx:1.6.1"
57 | implementation 'androidx.fragment:fragment-ktx:1.5.4'
58 |
59 | // stream chat
60 | def stream_version = "5.11.6"
61 | implementation "io.getstream:stream-chat-android-ui-components:$stream_version"
62 |
63 | // Hilt
64 | implementation "com.google.dagger:hilt-android:2.44"
65 | kapt "com.google.dagger:hilt-compiler:2.44"
66 |
67 | //FireBase
68 | implementation 'com.google.firebase:firebase-analytics:21.2.0'// 파이어베이스 앱 분석
69 | implementation 'com.google.firebase:firebase-core:21.1.1' //파이어베이스 코어
70 | implementation 'com.google.firebase:firebase-auth:21.1.0' //파이어베이스 인증
71 | implementation 'com.firebaseui:firebase-ui-auth:7.2.0' //파이어베이스 인증 2
72 | implementation 'com.google.firebase:firebase-firestore-ktx:24.4.1' //파이어스토어
73 | implementation 'com.google.firebase:firebase-storage-ktx:20.1.0' //파이어스토리지
74 |
75 | // navigation
76 | def nav_version = "2.5.3"
77 | implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
78 | implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
79 |
80 | // 디바이스 위치
81 | implementation 'com.google.android.gms:play-services-maps:18.1.0'
82 | implementation 'com.google.android.gms:play-services-location:21.0.1'
83 | implementation 'com.google.maps.android:android-maps-utils:2.3.0'
84 | implementation 'com.google.maps.android:maps-ktx:3.4.0'
85 | implementation 'com.google.maps.android:maps-utils-ktx:3.4.0'
86 |
87 | // Glide
88 | implementation 'com.github.bumptech.glide:glide:4.14.2'
89 | annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2'
90 |
91 | // FlexBoxLayout
92 | implementation 'com.google.android.flexbox:flexbox:3.0.0'
93 |
94 |
95 | // retrofit
96 | def retrofit_version = "2.9.0"
97 | implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
98 | implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
99 | implementation "com.squareup.okhttp3:logging-interceptor:4.10.0"
100 | }
101 |
102 | kapt {
103 | correctErrorTypes true
104 | }
--------------------------------------------------------------------------------
/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "611516619499",
4 | "project_id": "kiwi-talk-9249d",
5 | "storage_bucket": "kiwi-talk-9249d.appspot.com"
6 | },
7 | "client": [
8 | {
9 | "client_info": {
10 | "mobilesdk_app_id": "1:611516619499:android:e87f8919813f13e8e27cae",
11 | "android_client_info": {
12 | "package_name": "com.kiwi.app"
13 | }
14 | },
15 | "oauth_client": [
16 | {
17 | "client_id": "611516619499-26ld1il35ee8gil43it4e8q7nl8jbrf8.apps.googleusercontent.com",
18 | "client_type": 3
19 | }
20 | ],
21 | "api_key": [
22 | {
23 | "current_key": "AIzaSyCAsKX4dhxkfR8c_ACsFGIMM8BzbjygxUg"
24 | }
25 | ],
26 | "services": {
27 | "appinvite_service": {
28 | "other_platform_oauth_client": [
29 | {
30 | "client_id": "611516619499-26ld1il35ee8gil43it4e8q7nl8jbrf8.apps.googleusercontent.com",
31 | "client_type": 3
32 | }
33 | ]
34 | }
35 | }
36 | },
37 | {
38 | "client_info": {
39 | "mobilesdk_app_id": "1:611516619499:android:d74297d39b05e56de27cae",
40 | "android_client_info": {
41 | "package_name": "com.kiwi.kiwitalk"
42 | }
43 | },
44 | "oauth_client": [
45 | {
46 | "client_id": "611516619499-26ld1il35ee8gil43it4e8q7nl8jbrf8.apps.googleusercontent.com",
47 | "client_type": 3
48 | }
49 | ],
50 | "api_key": [
51 | {
52 | "current_key": "AIzaSyCAsKX4dhxkfR8c_ACsFGIMM8BzbjygxUg"
53 | }
54 | ],
55 | "services": {
56 | "appinvite_service": {
57 | "other_platform_oauth_client": [
58 | {
59 | "client_id": "611516619499-26ld1il35ee8gil43it4e8q7nl8jbrf8.apps.googleusercontent.com",
60 | "client_type": 3
61 | }
62 | ]
63 | }
64 | }
65 | }
66 | ],
67 | "configuration_version": "1"
68 | }
--------------------------------------------------------------------------------
/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/kiwi/kiwitalk/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.*
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("com.kiwi.kiwitalk", appContext.packageName)
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/debug/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/debug/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/debug/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/debug/res/layout/layout_map_fragment_dummy.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/debug/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/debug/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/debug/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
23 |
24 |
27 |
28 |
33 |
37 |
42 |
47 |
51 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/assets/map_style.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "elementType": "geometry",
4 | "stylers": [
5 | {
6 | "color": "#ebe3cd"
7 | }
8 | ]
9 | },
10 | {
11 | "elementType": "labels.text.fill",
12 | "stylers": [
13 | {
14 | "color": "#523735"
15 | }
16 | ]
17 | },
18 | {
19 | "elementType": "labels.text.stroke",
20 | "stylers": [
21 | {
22 | "color": "#f5f1e6"
23 | }
24 | ]
25 | },
26 | {
27 | "featureType": "administrative",
28 | "elementType": "geometry.stroke",
29 | "stylers": [
30 | {
31 | "color": "#c9b2a6"
32 | }
33 | ]
34 | },
35 | {
36 | "featureType": "administrative.land_parcel",
37 | "elementType": "geometry.stroke",
38 | "stylers": [
39 | {
40 | "color": "#dcd2be"
41 | }
42 | ]
43 | },
44 | {
45 | "featureType": "administrative.land_parcel",
46 | "elementType": "labels.text.fill",
47 | "stylers": [
48 | {
49 | "color": "#ae9e90"
50 | }
51 | ]
52 | },
53 | {
54 | "featureType": "landscape.natural",
55 | "elementType": "geometry",
56 | "stylers": [
57 | {
58 | "color": "#dfd2ae"
59 | }
60 | ]
61 | },
62 | {
63 | "featureType": "poi",
64 | "elementType": "geometry",
65 | "stylers": [
66 | {
67 | "color": "#dfd2ae"
68 | }
69 | ]
70 | },
71 | {
72 | "featureType": "poi",
73 | "elementType": "labels.text",
74 | "stylers": [
75 | {
76 | "visibility": "off"
77 | }
78 | ]
79 | },
80 | {
81 | "featureType": "poi",
82 | "elementType": "labels.text.fill",
83 | "stylers": [
84 | {
85 | "color": "#93817c"
86 | }
87 | ]
88 | },
89 | {
90 | "featureType": "poi.business",
91 | "stylers": [
92 | {
93 | "visibility": "off"
94 | }
95 | ]
96 | },
97 | {
98 | "featureType": "poi.park",
99 | "elementType": "geometry.fill",
100 | "stylers": [
101 | {
102 | "color": "#a5b076"
103 | }
104 | ]
105 | },
106 | {
107 | "featureType": "poi.park",
108 | "elementType": "labels.text.fill",
109 | "stylers": [
110 | {
111 | "color": "#447530"
112 | }
113 | ]
114 | },
115 | {
116 | "featureType": "road",
117 | "elementType": "geometry",
118 | "stylers": [
119 | {
120 | "color": "#f5f1e6"
121 | }
122 | ]
123 | },
124 | {
125 | "featureType": "road",
126 | "elementType": "labels.icon",
127 | "stylers": [
128 | {
129 | "visibility": "off"
130 | }
131 | ]
132 | },
133 | {
134 | "featureType": "road.arterial",
135 | "elementType": "geometry",
136 | "stylers": [
137 | {
138 | "color": "#fdfcf8"
139 | }
140 | ]
141 | },
142 | {
143 | "featureType": "road.highway",
144 | "elementType": "geometry",
145 | "stylers": [
146 | {
147 | "color": "#f8c967"
148 | }
149 | ]
150 | },
151 | {
152 | "featureType": "road.highway",
153 | "elementType": "geometry.stroke",
154 | "stylers": [
155 | {
156 | "color": "#e9bc62"
157 | }
158 | ]
159 | },
160 | {
161 | "featureType": "road.highway.controlled_access",
162 | "elementType": "geometry",
163 | "stylers": [
164 | {
165 | "color": "#e98d58"
166 | }
167 | ]
168 | },
169 | {
170 | "featureType": "road.highway.controlled_access",
171 | "elementType": "geometry.stroke",
172 | "stylers": [
173 | {
174 | "color": "#db8555"
175 | }
176 | ]
177 | },
178 | {
179 | "featureType": "road.local",
180 | "elementType": "labels.text.fill",
181 | "stylers": [
182 | {
183 | "color": "#806b63"
184 | }
185 | ]
186 | },
187 | {
188 | "featureType": "transit",
189 | "stylers": [
190 | {
191 | "visibility": "off"
192 | }
193 | ]
194 | },
195 | {
196 | "featureType": "transit.line",
197 | "elementType": "geometry",
198 | "stylers": [
199 | {
200 | "color": "#dfd2ae"
201 | }
202 | ]
203 | },
204 | {
205 | "featureType": "transit.line",
206 | "elementType": "labels.text.fill",
207 | "stylers": [
208 | {
209 | "color": "#8f7d77"
210 | }
211 | ]
212 | },
213 | {
214 | "featureType": "transit.line",
215 | "elementType": "labels.text.stroke",
216 | "stylers": [
217 | {
218 | "color": "#ebe3cd"
219 | }
220 | ]
221 | },
222 | {
223 | "featureType": "transit.station",
224 | "elementType": "geometry",
225 | "stylers": [
226 | {
227 | "color": "#dfd2ae"
228 | }
229 | ]
230 | },
231 | {
232 | "featureType": "water",
233 | "elementType": "geometry.fill",
234 | "stylers": [
235 | {
236 | "color": "#b9d3c2"
237 | }
238 | ]
239 | },
240 | {
241 | "featureType": "water",
242 | "elementType": "labels.text.fill",
243 | "stylers": [
244 | {
245 | "color": "#92998d"
246 | }
247 | ]
248 | }
249 | ]
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/KiwiApplication.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 |
6 | @HiltAndroidApp
7 | class KiwiApplication : Application() {
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/NetworkStateManager.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk
2 |
3 | import android.app.AlertDialog
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.net.ConnectivityManager
7 | import android.net.Network
8 | import android.net.NetworkCapabilities
9 | import android.net.NetworkRequest
10 |
11 |
12 |
13 | class NetworkStateManager(context: Context) : ConnectivityManager.NetworkCallback() {
14 |
15 | private var context : Context? = null
16 | private var networkRequest: NetworkRequest? = null
17 | private var connectivityManager : ConnectivityManager? = null
18 | private val msgBuilder: AlertDialog.Builder
19 | private val msgDlg: AlertDialog
20 |
21 | init {
22 | this.context = context
23 | this.networkRequest =
24 | NetworkRequest.Builder()
25 | .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
26 | .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build()
27 | this.msgBuilder = AlertDialog.Builder(context)
28 | .setTitle("Network State Error")
29 | .setMessage("네트워크 상태를 확인해주세요.")
30 | .setPositiveButton("취소") { _, _ ->
31 | }
32 | .setCancelable(false)
33 | this.msgDlg = msgBuilder.create()
34 | }
35 |
36 | fun register() {
37 | connectivityManager = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
38 | connectivityManager?.registerNetworkCallback(networkRequest?:return, this)
39 | }
40 |
41 | fun unregister() {
42 | connectivityManager?.unregisterNetworkCallback(this)
43 | msgDlg.dismiss()
44 | }
45 |
46 | private fun networkShowDialog() {
47 | msgDlg.show()
48 | }
49 |
50 | override fun onLost(network: Network) {
51 | super.onLost(network)
52 | networkShowDialog()
53 | }
54 |
55 |
56 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/di/ApplicationModule.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.di
2 |
3 | import android.content.Context
4 | import android.net.ConnectivityManager
5 | import android.os.Build
6 | import androidx.annotation.RequiresApi
7 | import com.google.android.gms.auth.api.signin.GoogleSignIn
8 | import com.google.android.gms.auth.api.signin.GoogleSignInClient
9 | import com.google.android.gms.auth.api.signin.GoogleSignInOptions
10 | import com.kiwi.data.AppPreference
11 | import com.kiwi.kiwitalk.util.Const
12 | import dagger.Module
13 | import dagger.Provides
14 | import dagger.hilt.InstallIn
15 | import dagger.hilt.android.qualifiers.ApplicationContext
16 | import dagger.hilt.components.SingletonComponent
17 | import javax.inject.Singleton
18 |
19 | @Module
20 | @InstallIn(SingletonComponent::class)
21 | object ApplicationModule {
22 | @Singleton
23 | @Provides
24 | fun providePreference(@ApplicationContext context: Context): AppPreference =
25 | AppPreference(context.getSharedPreferences(Const.LOGIN_ID_KEY, Context.MODE_PRIVATE))
26 |
27 | @Singleton
28 | @Provides
29 | fun provideGoogleApiClient(@ApplicationContext context: Context): GoogleSignInClient {
30 | val googleSignOptions: GoogleSignInOptions =
31 | GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
32 | .requestIdToken("611516619499-v3b7482t6qbldpn0ens6rgp45i6fo577.apps.googleusercontent.com")
33 | .requestEmail()
34 | .build()
35 | return GoogleSignIn.getClient(context, googleSignOptions)
36 | }
37 |
38 | @RequiresApi(Build.VERSION_CODES.M)
39 | @Singleton
40 | @Provides
41 | fun provideConnectivityManager(@ApplicationContext context: Context): ConnectivityManager =
42 | context.getSystemService(ConnectivityManager::class.java)
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/di/ChatModule.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.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 io.getstream.chat.android.client.ChatClient
10 | import io.getstream.chat.android.client.logger.ChatLogLevel
11 | import io.getstream.chat.android.offline.plugin.configuration.Config
12 | import io.getstream.chat.android.offline.plugin.factory.StreamOfflinePluginFactory
13 | import javax.inject.Singleton
14 |
15 | @Module
16 | @InstallIn(SingletonComponent::class)
17 | class ChatModule {
18 | @Provides
19 | @Singleton
20 | fun provideChatClient(@ApplicationContext context: Context): ChatClient {
21 | return ChatClient.Builder("b5m96pppmwh5", context)
22 | .withPlugin(getOfflinePlugin(context))
23 | .logLevel(ChatLogLevel.ALL)
24 | .build()
25 | }
26 |
27 | @Singleton
28 | private fun getOfflinePlugin(@ApplicationContext context: Context) =
29 | StreamOfflinePluginFactory(
30 | config = Config(),
31 | appContext = context,
32 | )
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/di/FirebaseModule.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.di
2 |
3 | import com.google.firebase.firestore.FirebaseFirestore
4 | import com.google.firebase.firestore.ktx.firestore
5 | import com.google.firebase.ktx.Firebase
6 | import com.google.firebase.storage.FirebaseStorage
7 | import com.google.firebase.storage.ktx.storage
8 | import dagger.Module
9 | import dagger.Provides
10 | import dagger.hilt.InstallIn
11 | import dagger.hilt.components.SingletonComponent
12 | import javax.inject.Singleton
13 |
14 | @Module
15 | @InstallIn(SingletonComponent::class)
16 | class FirebaseModule {
17 | @Provides
18 | @Singleton
19 | fun provideFireStore(): FirebaseFirestore {
20 | return Firebase.firestore
21 | }
22 |
23 | @Provides
24 | @Singleton
25 | fun provideFireStorage(): FirebaseStorage {
26 | return Firebase.storage
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/di/KakaoApiModule.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.di
2 |
3 | import android.util.Log
4 | import com.kiwi.kiwitalk.util.Const.BASE_URL
5 | import com.kiwi.kiwitalk.util.Const.KAKAO_AUTH
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.components.SingletonComponent
10 | import okhttp3.Interceptor
11 | import okhttp3.OkHttpClient
12 | import okhttp3.Response
13 | import okhttp3.logging.HttpLoggingInterceptor
14 | import retrofit2.Retrofit
15 | import retrofit2.converter.gson.GsonConverterFactory
16 | import java.io.IOException
17 | import javax.inject.Singleton
18 |
19 | @Module
20 | @InstallIn(SingletonComponent::class)
21 | object KakaoApiModule {
22 |
23 | @Provides
24 | @Singleton
25 | fun provideHttpLoggingInterceptor() = HttpLoggingInterceptor { message ->
26 | Log.d("API", message)
27 | }.apply {
28 | setLevel(HttpLoggingInterceptor.Level.BODY)
29 | }
30 |
31 | @Provides
32 | @Singleton
33 | fun provideKakaoApiInterceptor(): ApiInterceptor = ApiInterceptor()
34 |
35 | @Provides
36 | @Singleton
37 | fun provideOkHttpClient(
38 | httpLoggingInterceptor: HttpLoggingInterceptor,
39 | apiInterceptor: ApiInterceptor,
40 | ): OkHttpClient = OkHttpClient.Builder()
41 | .addInterceptor(apiInterceptor)
42 | .addInterceptor(httpLoggingInterceptor)
43 | .build()
44 |
45 | @Provides
46 | @Singleton
47 | fun getClient(
48 | client: OkHttpClient,
49 | ): Retrofit = Retrofit.Builder()
50 | .baseUrl(BASE_URL)
51 | .addConverterFactory(GsonConverterFactory.create())
52 | .client(client)
53 | .build()
54 |
55 | class ApiInterceptor : Interceptor {
56 | @Throws(IOException::class)
57 | override fun intercept(chain: Interceptor.Chain): Response {
58 | val request = chain.request().newBuilder()
59 | .addHeader(KAKAO_AUTH, "KakaoAK c42505e275dcf6c9770aa45d2db6cc49")
60 | .build()
61 | return chain.proceed(request)
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/di/RepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.di
2 |
3 | import com.kiwi.data.datasource.local.UserLocalDataSource
4 | import com.kiwi.data.datasource.local.UserLocalDataSourceImpl
5 | import com.kiwi.data.datasource.remote.*
6 | import com.kiwi.data.repository.*
7 | import com.kiwi.domain.repository.*
8 | import dagger.Binds
9 | import dagger.Module
10 | import dagger.hilt.InstallIn
11 | import dagger.hilt.components.SingletonComponent
12 | import javax.inject.Singleton
13 |
14 | @Module
15 | @InstallIn(SingletonComponent::class)
16 | abstract class RepositoryModule {
17 | @Singleton
18 | @Binds //Interface 주입, abstract 사용
19 | abstract fun bindsSearchChatRepository(repository: SearchChatRepositoryImpl): SearchChatRepository
20 |
21 | @Singleton
22 | @Binds
23 | abstract fun bindsSearchChatRemoteDataSource(dataSource: SearchChatRemoteDataSourceImpl): SearchChatRemoteDataSource
24 |
25 | @Binds
26 | @Singleton
27 | abstract fun bindsSearchKeywordRepository(searchKeywordRepositoryImpl: SearchKeywordRepositoryImpl): SearchKeywordRepository
28 |
29 | @Binds
30 | @Singleton
31 | abstract fun bindsSearchPlaceRepository(searchPlaceRepositoryImpl: SearchPlaceRepositoryImpl): SearchPlaceRepository
32 |
33 | @Binds
34 | @Singleton
35 | abstract fun bindsSearchPlaceRemoteDataSource(searchPlaceRemoteDataSourceImpl: SearchPlaceRemoteDataSourceImpl): SearchPlaceRemoteDataSource
36 |
37 | @Binds
38 | @Singleton
39 | abstract fun bindsNewChatRemoteDataSource(newChatRemoteDataSourceImpl: NewChatRemoteDataSourceImpl): NewChatRemoteDataSource
40 |
41 | @Binds
42 | @Singleton
43 | abstract fun bindsNewChatRepository(newChatRepositoryImpl: NewChatRepositoryImpl): NewChatRepository
44 |
45 | @Binds
46 | @Singleton
47 | abstract fun bindsProfileSettingRepository(profileSettingRepositoryImpl: ProfileSettingRepositoryImpl): ProfileSettingRepository
48 |
49 | @Binds
50 | @Singleton
51 | abstract fun bindExitChatDataSource(exitChatRemoteDataSourceImpl: ExitChatRemoteDataSourceImpl): ExitChatRemoteDataSource
52 |
53 | @Binds
54 | @Singleton
55 | abstract fun bindExitChatRepository(exitChatRepositoryImpl: ExitChatRepositoryImpl): ExitChatRepository
56 |
57 | @Binds
58 | @Singleton
59 | abstract fun bindUserLocalDatasource(dataImpl: UserLocalDataSourceImpl): UserLocalDataSource
60 |
61 | @Binds
62 | @Singleton
63 | abstract fun bindUserRemoteDatasource(dataImpl: UserRemoteDataSourceImpl): UserRemoteDataSource
64 |
65 | @Binds
66 | @Singleton
67 | abstract fun bindUserRepository(repoImpl: UserRepositoryImpl): UserRepository
68 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/model/ClusterMarker.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.model
2 |
3 | import com.google.android.gms.maps.model.LatLng
4 | import com.google.maps.android.clustering.ClusterItem
5 | import com.kiwi.domain.model.Marker
6 |
7 | data class ClusterMarker(
8 | val x: Double,
9 | val y: Double,
10 | private val markerSnippet: String?,
11 | private val markerTitle: String?,
12 | val keywords: List,
13 | val cid: String
14 | ) : ClusterItem {
15 | constructor(
16 | latLng: LatLng, snippet: String?, title: String?, keywords: List, cid: String
17 | ) : this(latLng.latitude, latLng.longitude, snippet, title, keywords, cid)
18 |
19 | private val latLng get() = LatLng(x, y)
20 | override fun getPosition(): LatLng = latLng
21 | override fun getSnippet(): String? = markerSnippet
22 | override fun getTitle(): String? = markerTitle
23 |
24 | companion object {
25 | fun Marker.toClusterMarker(): ClusterMarker = ClusterMarker(
26 | x = this.x,
27 | y = this.y,
28 | markerSnippet = cid,
29 | markerTitle = toString(),
30 | keywords = keywords,
31 | cid = cid
32 | )
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/BindingAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui
2 |
3 | import android.view.View
4 | import android.widget.ImageView
5 | import android.widget.TextView
6 | import androidx.databinding.BindingAdapter
7 | import com.bumptech.glide.Glide
8 | import com.kiwi.kiwitalk.R
9 | import java.util.*
10 |
11 | @BindingAdapter("loadImageByUri")
12 | fun setImage(imageView: ImageView, uri: String?) {
13 | Glide.with(imageView.context)
14 | .load(uri)
15 | .placeholder(R.drawable.ic_baseline_cloud_sync_24)
16 | .error(R.drawable.logo_splash_light)
17 | .fitCenter()
18 | .into(imageView)
19 | }
20 |
21 | @BindingAdapter("setUnreadCount")
22 | fun setUnreadCount(textView: TextView, count: Int?) {
23 | when (count) {
24 | 0, null -> textView.visibility = View.INVISIBLE
25 | in 1..999 -> {
26 | textView.visibility = View.VISIBLE
27 | textView.text = count.toString()
28 | }
29 | else -> {
30 | textView.visibility = View.VISIBLE
31 | textView.text = textView.context.getText(R.string.tv_chatList_unreadCountMax)
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/chatting/ChattingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.chatting
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import com.kiwi.kiwitalk.R
6 | import io.getstream.chat.android.ui.message.MessageListActivity
7 | import io.getstream.chat.android.ui.message.MessageListFragment
8 |
9 | class ChattingActivity : MessageListActivity() {
10 | override fun createMessageListFragment(cid: String, messageId: String?): MessageListFragment {
11 | return MessageListFragment.newInstance(cid) {
12 | setFragment(MessageListFragment())
13 | customTheme(R.style.Theme_KiwiTalk_NoActionBar)
14 | showHeader(false)
15 | messageId(messageId)
16 | }
17 | }
18 |
19 | companion object {
20 | private const val EXTRA_CID: String = "extra_cid"
21 | private const val EXTRA_MESSAGE_ID: String = "extra_message_id"
22 |
23 | @JvmStatic
24 | @JvmOverloads
25 | fun createIntent(context: Context, cid: String, messageId: String? = null): Intent {
26 | return Intent(context, MessageListActivity::class.java).apply {
27 | putExtra(EXTRA_CID, cid)
28 | putExtra(EXTRA_MESSAGE_ID, messageId)
29 | }
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/home/ChatListFragment.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.home
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.util.Log
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.appcompat.app.AlertDialog
10 | import androidx.fragment.app.Fragment
11 | import androidx.fragment.app.viewModels
12 | import androidx.navigation.Navigation
13 | import com.google.android.material.snackbar.Snackbar
14 | import com.kiwi.kiwitalk.R
15 | import com.kiwi.kiwitalk.databinding.FragmentChatListBinding
16 | import com.kiwi.kiwitalk.ui.chatting.ChattingActivity
17 | import com.kiwi.kiwitalk.ui.login.LoginActivity
18 | import com.kiwi.kiwitalk.ui.search.SearchChatActivity
19 | import dagger.hilt.android.AndroidEntryPoint
20 | import io.getstream.chat.android.client.models.Channel
21 | import io.getstream.chat.android.ui.channel.list.viewmodel.ChannelListViewModel
22 | import io.getstream.chat.android.ui.channel.list.viewmodel.factory.ChannelListViewModelFactory
23 |
24 | @AndroidEntryPoint
25 | class ChatListFragment : Fragment() {
26 | private val channelListViewModel: ChannelListViewModel
27 | by viewModels { ChannelListViewModelFactory() }
28 | private val chatListViewModel: ChatListViewModel by viewModels()
29 |
30 | private var _binding: FragmentChatListBinding? = null
31 | private val binding get() = _binding!!
32 |
33 | override fun onCreateView(
34 | inflater: LayoutInflater, container: ViewGroup?,
35 | savedInstanceState: Bundle?
36 | ): View {
37 | _binding = FragmentChatListBinding.inflate(layoutInflater, container, false)
38 | return binding.root
39 | }
40 |
41 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
42 | super.onViewCreated(view, savedInstanceState)
43 | initRecyclerView()
44 | initToolbar()
45 |
46 | binding.fabCreateChat.setOnClickListener {
47 | startActivity(Intent(requireContext(), SearchChatActivity::class.java))
48 | }
49 | }
50 |
51 | private fun initRecyclerView() {
52 | val adapter = ChatListViewAdapter(object : ChatListViewAdapter.OnChatClickListener {
53 | override fun onChatClick(channel: Channel) {
54 | startActivity(ChattingActivity.createIntent(requireContext(), channel.cid))
55 | }
56 |
57 | override fun onChatLongClick(channel: Channel) {
58 | showExitChatDialog(channel.cid)
59 | }
60 | })
61 |
62 | channelListViewModel.state.observe(viewLifecycleOwner) {
63 | if (it.channels.isEmpty()) {
64 | binding.tvChatListEmpty.visibility = View.VISIBLE
65 | binding.rvChatList.visibility = View.INVISIBLE
66 | } else {
67 | binding.tvChatListEmpty.visibility = View.INVISIBLE
68 | binding.rvChatList.visibility = View.VISIBLE
69 | adapter.submitList(it.channels)
70 | }
71 | }
72 | binding.rvChatList.adapter = adapter
73 | }
74 |
75 | private fun initToolbar() {
76 | val navigation = Navigation.findNavController(binding.root)
77 | binding.chatListToolbar.setOnMenuItemClickListener {
78 | when (it.itemId) {
79 | R.id.item_chatList_logout -> {
80 | chatListViewModel.signOut()
81 | true
82 | }
83 | R.id.item_chatList_actionToProffileSetting -> {
84 | navigation.navigate(R.id.action_chatListFragment_to_profileSettingFragment)
85 | true
86 | }
87 | else -> false
88 | }
89 | }
90 |
91 | chatListViewModel.signOutState.observe(viewLifecycleOwner) { result ->
92 | Log.d("K001|ChatListFrag", "result : $result")
93 | if (result == true) {
94 | navigateToLogin()
95 | } else {
96 | Snackbar.make(
97 | requireContext(),
98 | requireView(),
99 | "로그아웃에 실패했습니다",
100 | Snackbar.LENGTH_SHORT
101 | ).show()
102 | }
103 | }
104 | }
105 |
106 | override fun onDestroyView() {
107 | super.onDestroyView()
108 | _binding = null
109 | }
110 |
111 | private fun showExitChatDialog(cid: String) {
112 | AlertDialog.Builder(requireContext())
113 | .setTitle("채팅방 나가기")
114 | .setMessage("채팅방을 나가시겠습니까?")
115 | .setPositiveButton("확인") { _, _ ->
116 | chatListViewModel.exitChat(cid)
117 | }
118 | .setNegativeButton("취소") { dialog, _ ->
119 | dialog.dismiss()
120 | }.create()
121 | .show()
122 | }
123 |
124 | private fun navigateToLogin() {
125 | activity?.finishAffinity()
126 | startActivity(Intent(requireContext(), LoginActivity::class.java))
127 | }
128 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/home/ChatListViewAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.home
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.kiwi.chatmapper.ChatMapper
9 | import com.kiwi.domain.model.Keyword
10 | import com.kiwi.kiwitalk.databinding.ItemChatListBinding
11 | import com.kiwi.kiwitalk.ui.keyword.recyclerview.SelectedKeywordAdapter
12 | import io.getstream.chat.android.client.models.Channel
13 |
14 | class ChatListViewAdapter(private val onClickListener: OnChatClickListener) :
15 | ListAdapter(ChatDiffUtil) {
16 |
17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
18 | val binding = ItemChatListBinding.inflate(
19 | LayoutInflater.from(parent.context), parent, false
20 | )
21 | val holder = ChatViewHolder(binding)
22 | binding.root.setOnClickListener {
23 | val position = holder.bindingAdapterPosition
24 | if (position != RecyclerView.NO_POSITION) {
25 | onClickListener.onChatClick(getItem(position))
26 | }
27 | }
28 | binding.root.setOnLongClickListener {
29 | val position = holder.bindingAdapterPosition
30 | if (position != RecyclerView.NO_POSITION) {
31 | onClickListener.onChatLongClick(getItem(position))
32 | }
33 | true
34 | }
35 | return holder
36 | }
37 |
38 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
39 | when (holder) {
40 | is ChatViewHolder -> holder.bind(getItem(position))
41 | }
42 | }
43 |
44 | class ChatViewHolder(private val binding: ItemChatListBinding) :
45 | RecyclerView.ViewHolder(binding.root) {
46 | fun bind(item: Channel) {
47 | binding.chat = item
48 | val keywordAdapter = SelectedKeywordAdapter()
49 | val keywords = ChatMapper.getKeywords(item.extraData)
50 | keywordAdapter.submitList(keywords.map { Keyword(it, 0) })
51 | binding.tvRvChatListKeyword.adapter = keywordAdapter
52 | }
53 | }
54 |
55 | interface OnChatClickListener {
56 | fun onChatClick(channel: Channel)
57 |
58 | fun onChatLongClick(channel: Channel)
59 | }
60 |
61 | companion object {
62 | val ChatDiffUtil = object : DiffUtil.ItemCallback() {
63 | override fun areItemsTheSame(oldItem: Channel, newItem: Channel): Boolean {
64 | return oldItem.cid == newItem.cid
65 | }
66 |
67 | override fun areContentsTheSame(oldItem: Channel, newItem: Channel): Boolean {
68 | return oldItem.cid == newItem.cid && oldItem.updatedAt == newItem.updatedAt &&
69 | (oldItem.unreadCount == newItem.unreadCount || newItem.unreadCount == null || oldItem.unreadCount == null) &&
70 | oldItem.memberCount == newItem.memberCount
71 | }
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/home/ChatListViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.home
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.viewModelScope
7 | import com.kiwi.domain.repository.ExitChatRepository
8 | import com.kiwi.domain.repository.UserRepository
9 | import dagger.hilt.android.lifecycle.HiltViewModel
10 | import kotlinx.coroutines.flow.catch
11 | import kotlinx.coroutines.launch
12 | import javax.inject.Inject
13 |
14 | @HiltViewModel
15 | class ChatListViewModel @Inject constructor(
16 | private val exitChatRepository: ExitChatRepository,
17 | private val userRepository: UserRepository,
18 | ) : ViewModel() {
19 | private val TAG = "K001|ChatListViewModel"
20 |
21 | private val _signOutState = MutableLiveData()
22 | val signOutState: LiveData
23 | get() = _signOutState
24 |
25 | fun exitChat(cid: String) {
26 | viewModelScope.launch {
27 | exitChatRepository.exitChat(cid)
28 | }
29 | }
30 |
31 | fun signOut() {
32 | viewModelScope.launch {
33 | userRepository.signOut().catch {
34 | }.collect {
35 | _signOutState.postValue(it)
36 | }
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/home/HomeActivity.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.home
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import com.kiwi.kiwitalk.R
6 | import dagger.hilt.android.AndroidEntryPoint
7 |
8 | @AndroidEntryPoint
9 | class HomeActivity : AppCompatActivity() {
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | super.onCreate(savedInstanceState)
12 | setContentView(R.layout.activity_home)
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/home/ProfileViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.home
2 |
3 | import android.net.Uri
4 | import android.util.Log
5 | import androidx.lifecycle.LiveData
6 | import androidx.lifecycle.MutableLiveData
7 | import androidx.lifecycle.ViewModel
8 | import com.google.firebase.ktx.Firebase
9 | import com.google.firebase.storage.ktx.storage
10 | import com.kiwi.domain.model.Keyword
11 | import com.kiwi.domain.model.UserInfo
12 | import com.kiwi.domain.repository.UserRepository
13 | import com.kiwi.kiwitalk.util.Const
14 | import com.kiwi.kiwitalk.util.Event
15 | import dagger.hilt.android.lifecycle.HiltViewModel
16 | import io.getstream.chat.android.client.ChatClient
17 | import io.getstream.chat.android.client.models.User
18 | import javax.inject.Inject
19 |
20 | @HiltViewModel
21 | class ProfileViewModel @Inject constructor(
22 | private val userRepository: UserRepository,
23 | ) : ViewModel() {
24 |
25 | //2way binding
26 | val myName = MutableLiveData()
27 | val myDescription = MutableLiveData()
28 |
29 | private val _profileImage = MutableLiveData()
30 | val profileImage: LiveData = _profileImage
31 |
32 | private val _myKeywords = MutableLiveData>()
33 | val myKeywords: LiveData>
34 | get() = _myKeywords
35 |
36 | private val _isAllConditionPass = MutableLiveData>()
37 | val isAllConditionPass: LiveData>
38 | get() = _isAllConditionPass
39 |
40 | init {
41 | getMyProfile()
42 | }
43 |
44 | private fun getMyProfile() {
45 | userRepository.getUserInfo().let { userInfo ->
46 | myName.value = userInfo.name
47 | myDescription.value = userInfo.description
48 | _myKeywords.value = userInfo.keywords
49 | _profileImage.value = userInfo.imageUrl.ifBlank { null }
50 | }
51 | }
52 |
53 | fun setMySelectedKeyword(selectKeywordList: List?) {
54 | selectKeywordList?.let {
55 | _myKeywords.value = it
56 | }
57 | }
58 |
59 | fun setUpdateProfile() {
60 | val uri = profileImage.value
61 | val id = userRepository.getUserInfo().id
62 | if (uri == null || uri.startsWith("https://")) {
63 | updateUser(
64 | UserInfo(
65 | id = id,
66 | name = myName.value ?: Const.EMPTY_STRING,
67 | keywords = myKeywords.value ?: listOf(),
68 | imageUrl = uri ?: Const.EMPTY_STRING,
69 | description = myDescription.value ?: Const.EMPTY_STRING
70 | )
71 | )
72 | } else {
73 | val ref = Firebase.storage.reference.child("profile/${id}")
74 | ref.putFile(Uri.parse(uri)).addOnSuccessListener {
75 | it.storage.downloadUrl.addOnCompleteListener { url ->
76 | updateUser(
77 | UserInfo(
78 | id = id,
79 | name = myName.value ?: Const.EMPTY_STRING,
80 | keywords = myKeywords.value ?: listOf(),
81 | imageUrl = url.result.toString(),
82 | description = myDescription.value ?: Const.EMPTY_STRING
83 | )
84 | )
85 | }
86 | }.addOnFailureListener {
87 | Log.d("NewChatDataSource", "putFile Failure: ${it.cause}")
88 | }
89 | }
90 | }
91 |
92 | private fun updateUser(userInfo: UserInfo) {
93 | userRepository.updateUser(userInfo)
94 | }
95 |
96 | fun setChatImage(uri: String) {
97 | _profileImage.value = uri
98 | }
99 |
100 | fun checkInput() {
101 | _isAllConditionPass.value = Event(checkName())
102 | }
103 |
104 | private fun checkName(): Boolean {
105 | val nameStrng = myName.value
106 | return nameStrng?.contains(Regex("[^가-힣a-zA-Z]"))?.not() ?: false
107 | }
108 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/keyword/SearchKeywordViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.keyword
2 |
3 | import android.util.Log
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.MutableLiveData
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.viewModelScope
8 | import com.kiwi.domain.model.Keyword
9 | import com.kiwi.domain.model.KeywordCategory
10 | import com.kiwi.domain.repository.SearchKeywordRepository
11 | import dagger.hilt.android.lifecycle.HiltViewModel
12 | import kotlinx.coroutines.launch
13 | import javax.inject.Inject
14 |
15 | @HiltViewModel
16 | class SearchKeywordViewModel @Inject constructor(
17 | private val searchKeywordRepository: SearchKeywordRepository
18 | ) : ViewModel() {
19 |
20 | private val _allKeywords = MutableLiveData>()
21 | val allKeywords: LiveData>
22 | get() = _allKeywords
23 |
24 |
25 | private val _selectedKeywords = MutableLiveData>()
26 | val selectedKeyword: LiveData>
27 | get() = _selectedKeywords
28 |
29 | private var tempSelectedKeywords: List? = null
30 |
31 | init {
32 | getAllKeywords()
33 | }
34 |
35 | private fun getAllKeywords() {
36 | viewModelScope.launch {
37 | allKeywords.value ?: searchKeywordRepository.getAllKeyWord()
38 | .let {
39 | _allKeywords.value = it
40 | Log.d("FIRESTORE_CALL_KEYWORD", "getKeywords: VM 1 ${it}")
41 | }
42 | }
43 | Log.d("FIRESTORE_CALL_KEYWORD", "getKeywords: VM 2")
44 | }
45 |
46 | fun deleteAllSelectedKeyword(){
47 | _selectedKeywords.value = listOf()
48 | }
49 |
50 | fun setSelectedKeywords(text: String) {
51 | Keyword(text).let { keyword ->
52 | if (_selectedKeywords.value == null) {
53 | _selectedKeywords.value = listOf(keyword)
54 | } else {
55 | val mutableSelectedList = _selectedKeywords.value!!.toMutableList()
56 | if (mutableSelectedList.contains(keyword)) mutableSelectedList.remove(keyword)
57 | else mutableSelectedList.add(keyword)
58 | _selectedKeywords.value = mutableSelectedList
59 | }
60 | }
61 | }
62 |
63 | private fun getKeyword(text: String): Keyword? {
64 | allKeywords.value?.forEach { keywordCategory ->
65 | keywordCategory.keywords.forEach { keyword ->
66 | if (keyword.name == text) return keyword
67 | }
68 | }
69 | return null
70 | }
71 |
72 | fun saveBeforeKeywords() {
73 | selectedKeyword.value?.let {
74 | tempSelectedKeywords = it.toMutableList()
75 | }
76 | }
77 |
78 | fun SaveSelectedKeywordOrNot(saveOrNot: Boolean) {
79 | Log.d(
80 | "SaveSelected",
81 | "SaveSelectedKeywordOrNot: ${saveOrNot} before: $tempSelectedKeywords"
82 | )
83 | if (!saveOrNot) {
84 | _selectedKeywords.value = tempSelectedKeywords ?: listOf()
85 | }
86 | tempSelectedKeywords = null
87 | }
88 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/keyword/recyclerview/KeywordAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.keyword.recyclerview
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.kiwi.domain.model.Keyword
8 | import com.kiwi.kiwitalk.databinding.ItemKeywordBinding
9 |
10 | /**
11 | * TODO innerClass 없애기
12 | */
13 |
14 | class KeywordAdapter(
15 | val keywordList: MutableList,
16 | private val keywordClickListener: (View) -> Unit,
17 | private val selectedKeywordList: List? = null
18 | ) : RecyclerView.Adapter() {
19 |
20 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): KeywordViewHolder {
21 | val binding = ItemKeywordBinding.inflate(LayoutInflater.from(parent.context))
22 | return KeywordViewHolder(binding)
23 | }
24 |
25 | override fun onBindViewHolder(holder: KeywordViewHolder, position: Int) {
26 | holder.bind(position)
27 | }
28 |
29 | override fun getItemCount(): Int {
30 | return keywordList.size
31 | }
32 |
33 | //TODO 재활용을 위해 일반 클래스로 빼내기
34 | inner class KeywordViewHolder(val binding: ItemKeywordBinding) :
35 | RecyclerView.ViewHolder(binding.root) {
36 | fun bind(position: Int) {
37 | with(binding.chipKeyword) {
38 | text = keywordList[position].name
39 | setOnClickListener(keywordClickListener)
40 | selectedKeywordList?.let { list ->
41 | if (list.contains(keywordList[position])) this.isChecked = true
42 | }
43 | }
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/keyword/recyclerview/KeywordCategoryAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.keyword.recyclerview
2 |
3 | import android.content.Context
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.recyclerview.widget.RecyclerView
8 | import com.google.android.flexbox.FlexboxLayoutManager
9 | import com.kiwi.domain.model.Keyword
10 | import com.kiwi.domain.model.KeywordCategory
11 | import com.kiwi.kiwitalk.databinding.ItemKeywordcategoryBinding
12 |
13 | class KeywordCategoryAdapter(
14 | var keywordCategoryList: List?,
15 | private val keywordClickListener: (View) -> Unit,
16 | private val selectedKeywordList: List? = null
17 | ) : RecyclerView.Adapter() {
18 |
19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): KeywordCategoryViewHolder {
20 | val binding = ItemKeywordcategoryBinding.inflate(LayoutInflater.from(parent.context))
21 | return KeywordCategoryViewHolder(binding, parent.context)
22 | }
23 |
24 | override fun onBindViewHolder(holder: KeywordCategoryViewHolder, position: Int) {
25 | keywordCategoryList?.let {
26 | holder.bind(it[position].keywords, position)
27 | }
28 | }
29 |
30 | override fun getItemCount(): Int {
31 | return if (keywordCategoryList == null) 0 else keywordCategoryList!!.size
32 | }
33 |
34 | inner class KeywordCategoryViewHolder(
35 | val binding: ItemKeywordcategoryBinding,
36 | val contextForLayoutManager: Context,
37 | ) : RecyclerView.ViewHolder(binding.root) {
38 | fun bind(keywordList: MutableList, position: Int) {
39 | keywordCategoryList?.let {
40 | binding.tvKeywordCategoryCategoryName.text = it[position].name
41 | binding.rvKeywordCategoryKeywordList.layoutManager =
42 | FlexboxLayoutManager(contextForLayoutManager).apply {
43 |
44 | }
45 | binding.rvKeywordCategoryKeywordList.adapter =
46 | KeywordAdapter(
47 | it[position].keywords,
48 | keywordClickListener,
49 | selectedKeywordList
50 | )
51 | }
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/keyword/recyclerview/SelectedKeywordAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.keyword.recyclerview
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.kiwi.domain.model.Keyword
9 | import com.kiwi.kiwitalk.databinding.ItemKeywordBinding
10 |
11 | class SelectedKeywordAdapter() :
12 | ListAdapter(diffUtil) {
13 |
14 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SelectedKeywordViewHolder {
15 | val binding = ItemKeywordBinding.inflate(LayoutInflater.from(parent.context), parent, false)
16 | return SelectedKeywordViewHolder(binding)
17 | }
18 |
19 | override fun onBindViewHolder(holder: SelectedKeywordViewHolder, position: Int) {
20 | holder.bind(currentList[position])
21 | }
22 |
23 | inner class SelectedKeywordViewHolder(val binding: ItemKeywordBinding) :
24 | RecyclerView.ViewHolder(binding.root) {
25 | fun bind(keyword: Keyword) {
26 | with(binding.chipKeyword) {
27 | text = keyword.name
28 | isChecked = true
29 | isClickable = false
30 | }
31 | }
32 | }
33 |
34 | companion object {
35 | val diffUtil = object : DiffUtil.ItemCallback() {
36 | override fun areItemsTheSame(
37 | oldItem: Keyword,
38 | newItem: Keyword
39 | ): Boolean {
40 | return oldItem.name == newItem.name
41 | }
42 |
43 | override fun areContentsTheSame(
44 | oldItem: Keyword,
45 | newItem: Keyword
46 | ): Boolean {
47 | return oldItem == newItem
48 | }
49 | }
50 | }
51 | }
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/login/LoginActivity.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.login
2 |
3 | import android.content.Intent
4 | import android.os.Build
5 | import android.os.Bundle
6 | import android.util.Log
7 | import androidx.activity.result.ActivityResultLauncher
8 | import androidx.activity.result.contract.ActivityResultContracts
9 | import androidx.activity.viewModels
10 | import androidx.annotation.RequiresApi
11 | import androidx.appcompat.app.AppCompatActivity
12 | import com.google.android.gms.auth.api.Auth
13 | import com.google.android.gms.auth.api.signin.GoogleSignInResult
14 | import com.google.android.gms.auth.api.signin.GoogleSignInStatusCodes
15 | import com.google.android.material.snackbar.Snackbar
16 | import com.kiwi.kiwitalk.databinding.ActivityLoginBinding
17 | import com.kiwi.kiwitalk.ui.home.HomeActivity
18 | import com.kiwi.kiwitalk.util.Const
19 | import dagger.hilt.android.AndroidEntryPoint
20 |
21 | @AndroidEntryPoint
22 | class LoginActivity : AppCompatActivity() {
23 | private val viewModel: LoginViewModel by viewModels()
24 | private lateinit var binding: ActivityLoginBinding
25 | private lateinit var activityResultLauncher: ActivityResultLauncher
26 | private lateinit var loginProgressDialog: ProgressDialog
27 |
28 | @RequiresApi(Build.VERSION_CODES.S)
29 | override fun onCreate(savedInstanceState: Bundle?) {
30 | super.onCreate(savedInstanceState)
31 | binding = ActivityLoginBinding.inflate(layoutInflater)
32 | setContentView(binding.root)
33 |
34 | viewModel.loginState.observe(this) {
35 | if (it) {
36 | showPopUpMessage(LOGIN_SUCCESS)
37 | navigateToHome()
38 | }
39 | }
40 |
41 | activityResultLauncher =
42 | registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
43 | loginWithGoogleCredential(
44 | Auth.GoogleSignInApi.getSignInResultFromIntent(it.data)
45 | )
46 | }
47 |
48 | binding.btnGoogleSignup.setOnClickListener {
49 | val intent = viewModel.googleApiClient.signInIntent
50 | activityResultLauncher.launch(intent)
51 | }
52 | }
53 |
54 | @RequiresApi(Build.VERSION_CODES.M)
55 | private fun loginWithGoogleCredential(result: GoogleSignInResult?) {
56 | try {
57 | result ?: return
58 | Log.d(TAG, result.status.toString())
59 |
60 | loginProgressDialog =
61 | ProgressDialog(this).apply { show() }
62 |
63 | when (result.status.statusCode) {
64 | GoogleSignInStatusCodes.SUCCESS -> result.signInAccount?.apply {
65 | viewModel.signUp(
66 | id = id!!,
67 | name = displayName ?: Const.EMPTY_STRING,
68 | imageUrl = photoUrl.toString()
69 | )
70 | }
71 | GoogleSignInStatusCodes.DEVELOPER_ERROR -> throw Exception("SHA키 등록 여부 확인")
72 | GoogleSignInStatusCodes.NETWORK_ERROR -> showPopUpMessage(NO_NETWORK)
73 | 12501 -> throw Exception("디바이스가 Google Play 서비스를 포함하는지 확인")
74 | }
75 | loginProgressDialog.cancel()
76 | } catch (e: Exception) {
77 | Log.d(TAG, e.toString())
78 | }
79 | }
80 |
81 | private fun navigateToHome() {
82 | val intent = Intent(this, HomeActivity::class.java)
83 | finishAffinity()
84 | startActivity(intent)
85 | }
86 |
87 | private fun showPopUpMessage(text: String) {
88 | Snackbar.make(binding.root, text, Snackbar.LENGTH_SHORT).show()
89 | }
90 |
91 | companion object {
92 | private const val TAG = "k001|LoginActivity"
93 | private const val LOGIN_SUCCESS = "로그인 성공"
94 | private const val NO_NETWORK = "인터넷 연결을 확인해주세요"
95 | }
96 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/login/LoginViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.login
2 |
3 | import android.util.Log
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.MutableLiveData
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.viewModelScope
8 | import com.google.android.gms.auth.api.signin.GoogleSignInClient
9 | import com.kiwi.domain.UserUiCallback
10 | import com.kiwi.domain.repository.UserRepository
11 | import dagger.hilt.android.lifecycle.HiltViewModel
12 | import kotlinx.coroutines.launch
13 | import javax.inject.Inject
14 |
15 | @HiltViewModel
16 | class LoginViewModel @Inject constructor(
17 | val googleApiClient: GoogleSignInClient,
18 | private val userRepository: UserRepository
19 | ) : ViewModel() {
20 | private val _loginState = MutableLiveData(false)
21 | val loginState: LiveData = _loginState
22 |
23 | fun signUp(id: String, name: String, imageUrl: String) {
24 | viewModelScope.launch {
25 | userRepository.tryLogin(id, name, imageUrl, object : UserUiCallback {
26 | override fun onSuccess() {
27 | _loginState.value = true
28 | }
29 |
30 | override fun onFailure(e: Throwable) {
31 | Log.d(TAG, "Login fail")
32 | e.printStackTrace()
33 | }
34 | })
35 | }
36 | }
37 |
38 | companion object {
39 | private const val TAG = "k001|LoginVM"
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/login/ProgressDialog.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.login
2 |
3 | import android.app.Dialog
4 | import android.content.Context
5 | import android.os.Bundle
6 | import com.kiwi.kiwitalk.databinding.DialogLoginProgressBinding
7 |
8 | class ProgressDialog(context: Context) : Dialog(context) {
9 | override fun onCreate(savedInstanceState: Bundle?) {
10 | super.onCreate(savedInstanceState)
11 | val binding = DialogLoginProgressBinding.inflate(layoutInflater)
12 | setContentView(binding.root)
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/login/SplashActivity.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.login
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.util.Log
7 | import androidx.activity.viewModels
8 | import androidx.appcompat.app.AppCompatActivity
9 | import com.kiwi.domain.UserUiCallback
10 | import com.kiwi.kiwitalk.NetworkStateManager
11 | import com.kiwi.kiwitalk.databinding.ActivitySplashBinding
12 | import com.kiwi.kiwitalk.ui.home.HomeActivity
13 | import dagger.hilt.android.AndroidEntryPoint
14 |
15 | @SuppressLint("CustomSplashScreen")
16 | @AndroidEntryPoint
17 | class SplashActivity : AppCompatActivity() {
18 | private val viewModel: SplashViewModel by viewModels()
19 | private lateinit var binding: ActivitySplashBinding
20 | private val networkStateManager by lazy { NetworkStateManager(this) }
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | binding = ActivitySplashBinding.inflate(layoutInflater)
25 | setContentView(binding.root)
26 | }
27 |
28 | override fun onStart() {
29 | super.onStart()
30 | networkStateManager.register()
31 | viewModel.loginWithLocalToken(object : UserUiCallback {
32 | override fun onSuccess() {
33 | navigateToHomeActivity()
34 | }
35 |
36 | override fun onFailure(e: Throwable) {
37 | Log.d("K001|Splash", e.toString())
38 | navigateToLoginActivity()
39 | }
40 | })
41 | }
42 |
43 | private fun navigateToLoginActivity() {
44 | finishAffinity()
45 | startActivity(Intent(this, LoginActivity::class.java))
46 | }
47 |
48 | private fun navigateToHomeActivity() {
49 | finishAffinity()
50 | startActivity(Intent(this, HomeActivity::class.java))
51 | }
52 |
53 | override fun onStop() {
54 | super.onStop()
55 | networkStateManager.unregister()
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/login/SplashViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.login
2 |
3 | import androidx.lifecycle.ViewModel
4 | import com.kiwi.domain.UserUiCallback
5 | import com.kiwi.domain.repository.UserRepository
6 | import dagger.hilt.android.lifecycle.HiltViewModel
7 | import javax.inject.Inject
8 |
9 | @HiltViewModel
10 | class SplashViewModel @Inject constructor(
11 | private val userRepository: UserRepository,
12 | ): ViewModel() {
13 |
14 | fun loginWithLocalToken(userUiCallback: UserUiCallback){
15 | userRepository.isRemoteLoginRequired(userUiCallback)
16 | }
17 |
18 | companion object {
19 | private const val TAG = "k001|SplashVM"
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/newchat/NewChatActivity.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.newchat
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import com.kiwi.kiwitalk.databinding.ActivityNewChatBinding
6 | import dagger.hilt.android.AndroidEntryPoint
7 |
8 | @AndroidEntryPoint
9 | class NewChatActivity : AppCompatActivity() {
10 |
11 | private lateinit var binding: ActivityNewChatBinding
12 |
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | binding = ActivityNewChatBinding.inflate(layoutInflater)
16 | setContentView(binding.root)
17 |
18 |
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/newchat/NewChatViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.newchat
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.viewModelScope
7 | import com.google.android.gms.maps.model.LatLng
8 | import com.kiwi.domain.model.NewChatInfo
9 | import com.kiwi.domain.repository.NewChatRepository
10 | import com.kiwi.data.AppPreference
11 | import com.kiwi.domain.repository.UserRepository
12 | import com.kiwi.kiwitalk.util.Const.EMPTY_STRING
13 | import com.kiwi.kiwitalk.util.Const.LOGIN_ID_KEY
14 | import dagger.hilt.android.lifecycle.HiltViewModel
15 | import kotlinx.coroutines.launch
16 | import javax.inject.Inject
17 |
18 | @HiltViewModel
19 | class NewChatViewModel @Inject constructor(
20 | private val repository: NewChatRepository,
21 | private val userRepository: UserRepository,
22 | ) : ViewModel() {
23 |
24 | private val _chatImage = MutableLiveData()
25 | val chatImage: LiveData = _chatImage
26 |
27 | private val _chatId = MutableLiveData()
28 | val chatId: LiveData = _chatId
29 |
30 | private val _newChatInfo = MutableLiveData()
31 | val newChatInfo: LiveData = _newChatInfo
32 |
33 | private val _address = MutableLiveData()
34 | val address: LiveData = _address
35 |
36 | private val _latLng = MutableLiveData()
37 | val latLng: LiveData = _latLng
38 |
39 |
40 | fun setChatImage(uri: String) {
41 | _chatImage.value = uri
42 | }
43 |
44 | fun setAddress(address: String) {
45 | _address.value = address
46 | }
47 |
48 | fun setLatLng(latLng: LatLng) {
49 | _latLng.value = latLng
50 | }
51 |
52 | fun setNewChat(newChat: NewChatInfo) {
53 | _newChatInfo.value = newChat
54 | }
55 |
56 | fun setChatId() {
57 | _chatId.value = userRepository.getUserInfo().id
58 | }
59 |
60 | fun addNewChat(userid: String, currentTime: String, newChat: NewChatInfo) {
61 | viewModelScope.launch {
62 | repository.addChatUpload(userid,currentTime,newChat)
63 | }
64 | }
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/newchat/SearchPlaceViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.newchat
2 |
3 | import android.util.Log
4 | import androidx.lifecycle.ViewModel
5 | import androidx.lifecycle.viewModelScope
6 | import com.kiwi.domain.model.PlaceInfoList
7 | import com.kiwi.domain.repository.SearchPlaceRepository
8 | import dagger.hilt.android.lifecycle.HiltViewModel
9 | import io.getstream.chat.android.client.utils.toResult
10 | import kotlinx.coroutines.flow.*
11 | import kotlinx.coroutines.launch
12 | import javax.inject.Inject
13 |
14 | @HiltViewModel
15 | class SearchPlaceViewModel @Inject constructor(
16 | private val searchPlaceRepository: SearchPlaceRepository
17 | ) : ViewModel() {
18 |
19 | private val _isPlaceList = MutableSharedFlow()
20 | val isPlaceList: SharedFlow = _isPlaceList
21 |
22 | fun getSearchPlace(lng: String, lat: String, place: String) {
23 | viewModelScope.launch {
24 | searchPlaceRepository.getSearchPlace(lng, lat, place)
25 | .catch {
26 | Log.d("getSearchPlace", "getSearchPlace: ${this.toResult()} $it")
27 | }.collect {
28 | _isPlaceList.emit(it)
29 | }
30 | }
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/search/ChatAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.search
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.RecyclerView
6 | import com.kiwi.domain.model.ChatInfo
7 | import com.kiwi.domain.model.Keyword
8 | import com.kiwi.kiwitalk.databinding.ItemChatListNoImageBinding
9 | import com.kiwi.kiwitalk.ui.keyword.recyclerview.SelectedKeywordAdapter
10 |
11 | class ChatAdapter(
12 | private var chatList: List?,
13 | private val chatClickListener: (ChatInfo) -> Unit
14 | ) : RecyclerView.Adapter() {
15 |
16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatInfoViewHolder {
17 | val binding = ItemChatListNoImageBinding.inflate(LayoutInflater.from(parent.context))
18 |
19 | return ChatInfoViewHolder(binding)
20 | }
21 |
22 | override fun onBindViewHolder(holder: ChatInfoViewHolder, position: Int) {
23 | chatList?.let {
24 | val data = it[position]
25 | holder.bind(data)
26 | holder.binding.root.setOnClickListener {
27 | chatClickListener(data)
28 | }
29 | }
30 | }
31 |
32 | override fun getItemCount(): Int {
33 | return chatList?.size ?: 0
34 | }
35 |
36 | fun submitList(list: List) {
37 | chatList = list
38 | notifyDataSetChanged()
39 | }
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/search/ChatInfoViewHolder.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.search
2 |
3 | import androidx.recyclerview.widget.RecyclerView
4 | import com.kiwi.domain.model.ChatInfo
5 | import com.kiwi.domain.model.Keyword
6 | import com.kiwi.kiwitalk.databinding.ItemChatListNoImageBinding
7 | import com.kiwi.kiwitalk.ui.keyword.recyclerview.SelectedKeywordAdapter
8 |
9 | class ChatInfoViewHolder(
10 | val binding: ItemChatListNoImageBinding
11 | ) : RecyclerView.ViewHolder(binding.root) {
12 | fun bind(chatInfo: ChatInfo) {
13 | binding.item = chatInfo
14 | val adapter = SelectedKeywordAdapter()
15 | binding.adapter = adapter
16 | adapter.submitList(chatInfo.keywords.map {
17 | Keyword(it, 0)
18 | }.toMutableList())
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/search/ChatJoinDialog.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.search
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.databinding.DataBindingUtil
8 | import androidx.fragment.app.DialogFragment
9 | import com.kiwi.domain.model.ChatInfo
10 | import com.kiwi.domain.model.Keyword
11 | import com.kiwi.kiwitalk.R
12 | import com.kiwi.kiwitalk.databinding.DialogJoinChatBinding
13 | import com.kiwi.kiwitalk.ui.keyword.recyclerview.SelectedKeywordAdapter
14 |
15 | class ChatJoinDialog(
16 | private val chatDialogAction: ChatDialogAction,
17 | private val chatInfo: ChatInfo,
18 | ) : DialogFragment() {
19 |
20 | private var _binding: DialogJoinChatBinding? = null
21 | private val binding get() = _binding!!
22 |
23 | override fun onCreateView(
24 | inflater: LayoutInflater,
25 | container: ViewGroup?,
26 | savedInstanceState: Bundle?
27 | ): View {
28 | _binding = DataBindingUtil.inflate(inflater, R.layout.dialog_join_chat, container, false)
29 | binding.chatInfo = chatInfo
30 | binding.adapter = SelectedKeywordAdapter().apply {
31 | submitList(chatInfo.keywords.map {
32 | Keyword(it, 0)
33 | }.toMutableList())
34 | }
35 | binding.btnDialogClose.setOnClickListener {
36 | chatDialogAction.onClickCancleButton()
37 | dismiss()
38 | }
39 | binding.btnDialogJoin.setOnClickListener {
40 | chatDialogAction.onClickJoinButton(chatInfo.cid)
41 | dismiss()
42 | }
43 | return binding.root
44 | }
45 |
46 | override fun onDestroy() {
47 | super.onDestroy()
48 | _binding = null
49 | }
50 |
51 | }
52 |
53 | interface ChatDialogAction {
54 | fun onClickJoinButton(cid: String)
55 | fun onClickCancleButton()
56 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/search/ClusterMarkerRenderer.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.search
2 |
3 | import android.content.Context
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.widget.TextView
7 | import com.google.android.gms.maps.GoogleMap
8 | import com.google.android.gms.maps.model.BitmapDescriptorFactory
9 | import com.google.android.gms.maps.model.Marker
10 | import com.google.android.gms.maps.model.MarkerOptions
11 | import com.google.maps.android.clustering.Cluster
12 | import com.google.maps.android.clustering.ClusterManager
13 | import com.google.maps.android.clustering.view.DefaultClusterRenderer
14 | import com.kiwi.kiwitalk.R
15 | import com.kiwi.kiwitalk.model.ClusterMarker
16 | import com.kiwi.kiwitalk.util.Util.createDrawableFromView
17 |
18 | class ClusterMarkerRenderer(val context: Context, map: GoogleMap, clusterManager: ClusterManager)
19 | : DefaultClusterRenderer(context, map, clusterManager) {
20 |
21 |
22 | override fun onBeforeClusterItemRendered(item: ClusterMarker, markerOptions: MarkerOptions) {
23 | val view: View = LayoutInflater.from(context).inflate(R.layout.item_marker, null)
24 | markerOptions.icon(BitmapDescriptorFactory.fromBitmap(createDrawableFromView(view)))
25 | }
26 |
27 | override fun onClusterRendered(cluster: Cluster, marker: Marker) {
28 | val view: View = LayoutInflater.from(context).inflate(R.layout.item_marker, null)
29 | val markerCount = view.findViewById(R.id.tv_marker_count)
30 | markerCount.text = cluster.size.toString()
31 | markerCount.visibility = View.VISIBLE
32 | marker.setIcon(BitmapDescriptorFactory.fromBitmap(createDrawableFromView(view)))
33 | }
34 |
35 | override fun shouldRenderAsCluster(cluster: Cluster): Boolean {
36 | return cluster.size > 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/search/SearchChatActivity.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.search
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import com.kiwi.kiwitalk.R
6 | import dagger.hilt.android.AndroidEntryPoint
7 |
8 | @AndroidEntryPoint
9 | class SearchChatActivity : AppCompatActivity() {
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | super.onCreate(savedInstanceState)
12 | setContentView(R.layout.activity_search_chat)
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/ui/search/SearchChatMapViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.ui.search
2 |
3 | import android.location.Location
4 | import android.util.Log
5 | import androidx.core.location.component1
6 | import androidx.core.location.component2
7 | import androidx.lifecycle.LiveData
8 | import androidx.lifecycle.MutableLiveData
9 | import androidx.lifecycle.ViewModel
10 | import androidx.lifecycle.viewModelScope
11 | import com.kiwi.domain.model.ChatInfo
12 | import com.kiwi.domain.model.Keyword
13 | import com.kiwi.domain.model.Marker
14 | import com.kiwi.domain.model.PlaceChatInfo
15 | import com.kiwi.domain.repository.SearchChatRepository
16 | import dagger.hilt.android.lifecycle.HiltViewModel
17 | import io.getstream.chat.android.client.utils.toResult
18 | import kotlinx.coroutines.flow.MutableSharedFlow
19 | import kotlinx.coroutines.flow.SharedFlow
20 | import kotlinx.coroutines.flow.catch
21 | import kotlinx.coroutines.flow.onCompletion
22 | import kotlinx.coroutines.launch
23 | import javax.inject.Inject
24 |
25 | @HiltViewModel
26 | class SearchChatMapViewModel @Inject constructor(
27 | private val searchChatRepository: SearchChatRepository,
28 | ) : ViewModel() {
29 | private val _markerList = MutableSharedFlow()
30 | val markerList: SharedFlow = _markerList
31 |
32 | private val _placeChatInfo = MutableLiveData()
33 | val placeChatInfo: LiveData = _placeChatInfo
34 |
35 | private val _location = MutableLiveData()
36 | val location: LiveData = _location
37 |
38 | private val _isLoadingMarkerList = MutableLiveData(false)
39 | val isLoadingMarkerList: LiveData
40 | get() = _isLoadingMarkerList
41 |
42 | var dialogData: ChatInfo? = null
43 | var lastBottomSheetState: Int? = null
44 |
45 | fun appendUserToChat(cid: String) {
46 | searchChatRepository.appendUserToChat(cid)
47 | }
48 |
49 | fun getPlaceInfo(cidList: List) {
50 | viewModelScope.launch {
51 | _placeChatInfo.value = searchChatRepository.getPlaceChatList(cidList)
52 | }
53 | }
54 |
55 | fun getMarkerList(keywords: List?) {
56 | if (keywords.isNullOrEmpty()) return
57 | val (lat, lng) = location.value ?: return
58 | _isLoadingMarkerList.value = true
59 | viewModelScope.launch {
60 | searchChatRepository.getMarkerList(keywords, lat, lng)
61 | .catch {
62 | Log.d("SearchChatViewModel", "getMarkerList: ${this.toResult()} $it")
63 | }.onCompletion {
64 | _isLoadingMarkerList.value = false
65 | }.collect {
66 | Log.d("SearchChatViewModel", "getMarkerList: collect $it")
67 | _markerList.emit(it)
68 | }
69 | }
70 | }
71 |
72 | fun setDeviceLocation(newLocation: Location) {
73 | _location.value = newLocation
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/util/ChangeExpansion.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.util
2 |
3 | import android.location.Geocoder
4 |
5 | object ChangeExpansion {
6 | fun Geocoder.changeLatLngToAddress(
7 | latitude: Double,
8 | longitude: Double,
9 | address: (android.location.Address?) -> Unit
10 | ) {
11 | try {
12 | @Suppress("DEPRECATION")
13 | address(getFromLocation(latitude, longitude, 1)?.firstOrNull())
14 | } catch (e: Exception) {
15 | address(null)
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/util/Const.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.util
2 |
3 | object Const {
4 | const val SPACE = " "
5 | const val LOGIN_ID_KEY = "login_history_key"
6 | const val LOGIN_NAME_KEY = "login_name"
7 | const val LOGIN_URL_KEY = "login_url"
8 | const val EMPTY_STRING = ""
9 | const val BASE_URL = "https://dapi.kakao.com/"
10 | const val KAKAO_AUTH = "Authorization"
11 | const val ADDRESS_ERROR = "현재 좌표에서 주소를 가져올 수 없습니다."
12 | const val PERMISSION_CODE = 99
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/util/Event.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.util
2 |
3 | /**
4 | * Code from : https://github.com/android/architecture-samples/
5 | */
6 |
7 |
8 | /**
9 | * Used as a wrapper for data that is exposed via a LiveData that represents an event.
10 | */
11 | open class Event(private val content: T) {
12 |
13 | var hasBeenHandled = false
14 | private set // Allow external read but not write
15 |
16 | /**
17 | * Returns the content and prevents its use again.
18 | */
19 | fun getContentIfNotHandled(): T? {
20 | return if (hasBeenHandled) {
21 | null
22 | } else {
23 | hasBeenHandled = true
24 | content
25 | }
26 | }
27 |
28 | /**
29 | * Returns the content, even if it's already been handled.
30 | */
31 | fun peekContent(): T = content
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kiwi/kiwitalk/util/Util.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk.util
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import android.graphics.Canvas
6 | import android.os.*
7 | import android.util.DisplayMetrics
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import androidx.core.content.ContextCompat
11 | import com.google.android.gms.maps.model.BitmapDescriptor
12 | import com.google.android.gms.maps.model.BitmapDescriptorFactory
13 |
14 |
15 | object Util {
16 | @Suppress("DEPRECATION")
17 | fun generateVibrator(context: Context) {
18 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
19 | val vibratorManager =
20 | context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
21 |
22 | val vibrationEffect = VibrationEffect.createOneShot(
23 | 200L,
24 | 50
25 | )
26 | val combinedVibration = CombinedVibration.createParallel(vibrationEffect)
27 | vibratorManager.vibrate(combinedVibration)
28 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
29 | val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
30 | val effect = VibrationEffect.createOneShot(
31 | 200L, 50
32 | )
33 | vibrator.vibrate(effect)
34 | } else {
35 | val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
36 | vibrator.vibrate(200L)
37 | }
38 | }
39 |
40 | fun changeVectorToBitmapDescriptor(context: Context, vectorResId: Int): BitmapDescriptor? {
41 | return ContextCompat.getDrawable(context, vectorResId)?.run {
42 | setBounds(0, 0, intrinsicWidth, intrinsicHeight)
43 | val bitmap =
44 | Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888)
45 | draw(Canvas(bitmap))
46 | BitmapDescriptorFactory.fromBitmap(bitmap)
47 | }
48 | }
49 |
50 | fun createDrawableFromView(view: View): Bitmap {
51 | val displayMetrics = DisplayMetrics()
52 | view.layoutParams = ViewGroup.LayoutParams(
53 | ViewGroup.LayoutParams.WRAP_CONTENT,
54 | ViewGroup.LayoutParams.WRAP_CONTENT
55 | )
56 | view.measure(displayMetrics.widthPixels, displayMetrics.heightPixels)
57 | view.layout(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)
58 | val bitmap = Bitmap.createBitmap(
59 | view.measuredWidth,
60 | view.measuredHeight,
61 | Bitmap.Config.ARGB_8888
62 | )
63 | val canvas = Canvas(bitmap)
64 | view.draw(canvas)
65 | return bitmap
66 | }
67 |
68 | }
--------------------------------------------------------------------------------
/app/src/main/res/color/color_chip_background_keyword.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/color/color_chip_text_keyword.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/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/background_marker_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_chat_list_unread_count.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_location.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_photo.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_backarrow_48.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_cloud_sync_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_chatlist_searchchat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/main/res/drawable/ic_chatlist_searchchat.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_location_cluster.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_location_on.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_location_on_click.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_profile_base_image.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search_chat_new_chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/main/res/drawable/ic_search_chat_new_chat.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/logo_splash_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/main/res/drawable/logo_splash_dark.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/logo_splash_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/main/res/drawable/logo_splash_light.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/logo_splash_transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/main/res/drawable/logo_splash_transparent.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/logo_splash_withtext.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/main/res/drawable/logo_splash_withtext.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_chatting.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_new_chat.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_search_chat.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_login_progress.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_new_chat.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
26 |
27 |
28 |
36 |
37 |
46 |
47 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_chat_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
18 |
19 |
22 |
23 |
30 |
31 |
38 |
39 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_search_keyword.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
21 |
22 |
30 |
31 |
42 |
43 |
53 |
54 |
62 |
63 |
64 |
65 |
73 |
74 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_search_place.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
18 |
31 |
32 |
43 |
44 |
55 |
56 |
57 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_chat_list_no_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
15 |
16 |
17 |
20 |
21 |
32 |
33 |
37 |
38 |
49 |
50 |
60 |
61 |
73 |
74 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_keyword.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_keywordcategory.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_marker.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_marker_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
13 |
14 |
15 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_marker_preview.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
18 |
19 |
24 |
25 |
37 |
38 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_chat_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_new_chat.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_search_chat_map.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_search_keyword_toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/navigation/home_nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
16 |
17 |
22 |
25 |
28 |
29 |
33 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/navigation/new_chat_nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
12 |
17 |
20 |
23 |
24 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/navigation/search_chat_nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
15 |
16 |
20 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #FFFFFFFF
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
11 | #FFCDDB4D
12 | #FF99AA12
13 | #606C00
14 | #FFD9E851
15 |
16 | #FFA46D46
17 | #FF71411C
18 | #FFD99462
19 |
20 | #FFCCCCCC
21 | #FFBFBFBF
22 | #FFA6A6A6
23 | #FF808080
24 | #FF5E5E5E
25 |
26 | #FF5E5E5E
27 | #FFFF0000
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 180dp
4 | 5dp
5 | 10dp
6 | 16dp
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | KiwiTalk
3 |
4 | Hello blank fragment
5 |
6 |
7 | 채팅방 이미지
8 | 참여한 채팅방이 없습니다.
9 |
10 | 지역
11 | 키워드
12 |
13 | 채팅방 찾기
14 | 새로운 채팅방 생성
15 | 현재 채팅방 수 : %d
16 |
17 | 채팅방 목록
18 | 프로필 수정
19 | 로그아웃
20 | 선택 키워드
21 | 999+
22 | %1$d / %2$d
23 |
24 | 닫기
25 | 입장
26 |
27 | 채팅방
28 | 키워드
29 | 위치
30 | 설명
31 |
32 |
33 | 채팅방 이미지 추가 버튼
34 | 채팅방 이름
35 | 채팅방 설명
36 | 최대 인원 설정
37 | 최대 설정 가능한 인원은 100명입니다
38 | 키워드 등록
39 | 키워드 검색
40 | 장소 등록
41 | 장소 검색
42 | 채팅방 생성
43 |
44 |
45 | 검색어를 입력해 주세요.
46 | 검색
47 | 위치 저장 버튼
48 | 돋보기를 눌러 검색하세요!
49 | 채팅방 더보기
50 | 선택된 키워드
51 |
--------------------------------------------------------------------------------
/app/src/main/res/values/style.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
22 |
23 |
30 |
31 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/java/com/kiwi/kiwitalk/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.kiwitalk
2 |
3 | import org.junit.Assert.assertEquals
4 | import org.junit.Test
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | dependencies {
4 | classpath 'com.google.gms:google-services:4.3.14'
5 | classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.3"
6 | }
7 |
8 |
9 | }
10 |
11 | plugins {
12 | id 'com.android.application' version '7.2.2' apply false
13 | id 'com.android.library' version '7.2.2' apply false
14 | id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
15 | id 'org.jetbrains.kotlin.jvm' version '1.7.20' apply false
16 | id 'com.google.dagger.hilt.android' version "2.44" apply false
17 | id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' version '2.0.1' apply false
18 | }
19 |
20 |
21 |
22 | task clean(type: Delete) {
23 | delete rootProject.buildDir
24 | }
--------------------------------------------------------------------------------
/chatmapper/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/chatmapper/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java-library'
3 | id 'org.jetbrains.kotlin.jvm'
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_1_7
8 | targetCompatibility = JavaVersion.VERSION_1_7
9 | }
10 |
11 | dependencies{
12 | }
--------------------------------------------------------------------------------
/chatmapper/src/main/java/com/kiwi/chatmapper/ChatConst.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.chatmapper
2 |
3 | internal object ChatConst {
4 | const val EMPTY_STRING = ""
5 | const val STRING_SPACE = " "
6 | val ADDRESS_FILTER_LIST = listOf('시', '도', '구', '군', '동', '읍', '면')
7 | }
--------------------------------------------------------------------------------
/chatmapper/src/main/java/com/kiwi/chatmapper/ChatKey.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.chatmapper
2 |
3 | object ChatKey {
4 | const val CHAT_KEYWORDS = "keywords"
5 | const val CHAT_ADDRESS = "address"
6 | const val CHAT_MAX_MEMBER_COUNT = "max_member_count"
7 | const val CHAT_DESCRIPTION = "description"
8 | }
--------------------------------------------------------------------------------
/chatmapper/src/main/java/com/kiwi/chatmapper/ChatMapper.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.chatmapper
2 |
3 | import com.kiwi.chatmapper.ChatConst.ADDRESS_FILTER_LIST
4 | import com.kiwi.chatmapper.ChatConst.EMPTY_STRING
5 | import com.kiwi.chatmapper.ChatConst.STRING_SPACE
6 | import com.kiwi.chatmapper.ChatKey.CHAT_ADDRESS
7 | import com.kiwi.chatmapper.ChatKey.CHAT_DESCRIPTION
8 | import com.kiwi.chatmapper.ChatKey.CHAT_KEYWORDS
9 | import com.kiwi.chatmapper.ChatKey.CHAT_MAX_MEMBER_COUNT
10 | import java.util.*
11 |
12 | object ChatMapper {
13 | private const val SEC = 60
14 | private const val MIN = 60
15 | private const val HOUR = 24
16 | private const val DAY = 30
17 | private const val MONTH = 12
18 |
19 | fun getDateOfLastMassage(lastDate: Date?): String {
20 | val regTime = lastDate?.time
21 | val curTime = System.currentTimeMillis()
22 |
23 | if (regTime == null) return EMPTY_STRING
24 |
25 | var diffTime = (curTime - regTime) / 1000
26 | return if (diffTime < SEC) {
27 | "방금 전"
28 | } else if (SEC.let { diffTime /= it; diffTime } < MIN) {
29 | "${diffTime}분 전"
30 | } else if (MIN.let { diffTime /= it; diffTime } < HOUR) {
31 | "${diffTime}시간 전"
32 | } else if (HOUR.let { diffTime /= it; diffTime } < DAY) {
33 | "${diffTime}일 전"
34 | } else if (DAY.let { diffTime /= it; diffTime } < MONTH) {
35 | "${diffTime}달 전"
36 | } else {
37 | "${diffTime}년 전"
38 | }
39 | }
40 |
41 | fun getMaxMemberCount(extraData: Map): Int {
42 | return (extraData[CHAT_MAX_MEMBER_COUNT] as? String ?: "0").toInt()
43 | }
44 |
45 | fun getTrimAddress(extraData: Map): String {
46 | val fullAddress = extraData[CHAT_ADDRESS]
47 | return if (fullAddress !is String) {
48 | EMPTY_STRING
49 | } else {
50 | fullAddress.split(STRING_SPACE)
51 | .filter { ADDRESS_FILTER_LIST.contains(it.last()) }
52 | .joinToString(STRING_SPACE)
53 | }
54 | }
55 |
56 | fun getKeywords(extraData: Map): List {
57 | val list = extraData[CHAT_KEYWORDS] as? List<*>
58 | return list?.filterIsInstance().orEmpty()
59 | }
60 |
61 | fun getDescription(extraData: Map): String {
62 | return (extraData[CHAT_DESCRIPTION] as? String) ?: EMPTY_STRING
63 | }
64 | }
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'com.google.gms.google-services'
5 | }
6 |
7 | android {
8 | compileSdk 33
9 |
10 | defaultConfig {
11 | minSdk 21
12 | targetSdk 33
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles "consumer-rules.pro"
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | compileOptions {
25 | sourceCompatibility JavaVersion.VERSION_1_8
26 | targetCompatibility JavaVersion.VERSION_1_8
27 | }
28 | kotlinOptions {
29 | jvmTarget = '1.8'
30 | }
31 | }
32 |
33 | dependencies {
34 | implementation project(path: ':domain')
35 | implementation project(':chatmapper')
36 |
37 | implementation 'androidx.core:core-ktx:1.9.0'
38 | implementation 'androidx.appcompat:appcompat:1.5.1'
39 | implementation 'com.google.android.material:material:1.7.0'
40 | testImplementation 'junit:junit:4.13.2'
41 | androidTestImplementation 'androidx.test.ext:junit:1.1.4'
42 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
43 |
44 | // Firebase
45 | implementation platform ('com.google.firebase:firebase-bom:31.0.3')
46 | implementation 'com.google.firebase:firebase-firestore-ktx' //파이어베이스
47 | implementation 'com.google.firebase:firebase-storage-ktx' //파이어스토리지
48 |
49 | // Inject(For Hilt)
50 | implementation "javax.inject:javax.inject:1"
51 |
52 |
53 | // retrofit
54 | def retrofit_version = "2.9.0"
55 | implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
56 | implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
57 | implementation "com.squareup.okhttp3:logging-interceptor:4.10.0"
58 |
59 | // stream chat : Client only
60 | def stream_version = "5.11.6"
61 | implementation "io.getstream:stream-chat-android-client:$stream_version"
62 | }
--------------------------------------------------------------------------------
/data/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/data/consumer-rules.pro
--------------------------------------------------------------------------------
/data/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "611516619499",
4 | "project_id": "kiwi-talk-9249d",
5 | "storage_bucket": "kiwi-talk-9249d.appspot.com"
6 | },
7 | "client": [
8 | {
9 | "client_info": {
10 | "mobilesdk_app_id": "1:611516619499:android:e87f8919813f13e8e27cae",
11 | "android_client_info": {
12 | "package_name": "com.kiwi.data"
13 | }
14 | },
15 | "oauth_client": [
16 | {
17 | "client_id": "611516619499-26ld1il35ee8gil43it4e8q7nl8jbrf8.apps.googleusercontent.com",
18 | "client_type": 3
19 | }
20 | ],
21 | "api_key": [
22 | {
23 | "current_key": "AIzaSyCAsKX4dhxkfR8c_ACsFGIMM8BzbjygxUg"
24 | }
25 | ],
26 | "services": {
27 | "appinvite_service": {
28 | "other_platform_oauth_client": [
29 | {
30 | "client_id": "611516619499-26ld1il35ee8gil43it4e8q7nl8jbrf8.apps.googleusercontent.com",
31 | "client_type": 3
32 | }
33 | ]
34 | }
35 | }
36 | },
37 | {
38 | "client_info": {
39 | "mobilesdk_app_id": "1:611516619499:android:d74297d39b05e56de27cae",
40 | "android_client_info": {
41 | "package_name": "com.kiwi.kiwitalk"
42 | }
43 | },
44 | "oauth_client": [
45 | {
46 | "client_id": "611516619499-26ld1il35ee8gil43it4e8q7nl8jbrf8.apps.googleusercontent.com",
47 | "client_type": 3
48 | }
49 | ],
50 | "api_key": [
51 | {
52 | "current_key": "AIzaSyCAsKX4dhxkfR8c_ACsFGIMM8BzbjygxUg"
53 | }
54 | ],
55 | "services": {
56 | "appinvite_service": {
57 | "other_platform_oauth_client": [
58 | {
59 | "client_id": "611516619499-26ld1il35ee8gil43it4e8q7nl8jbrf8.apps.googleusercontent.com",
60 | "client_type": 3
61 | }
62 | ]
63 | }
64 | }
65 | }
66 | ],
67 | "configuration_version": "1"
68 | }
--------------------------------------------------------------------------------
/data/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/data/src/androidTest/java/com/kiwi/data/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data
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.kiwi.data.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/data/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/AppPreference.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data
2 |
3 | import android.content.SharedPreferences
4 |
5 | class AppPreference constructor(private val prefs: SharedPreferences) {
6 | fun getString(key: String, defValue: String): String {
7 | return prefs.getString(key, defValue) ?: defValue
8 | }
9 |
10 | fun setString(key: String, value: String) {
11 | prefs.edit().putString(key, value).apply()
12 | }
13 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/Const.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data
2 |
3 | object Const {
4 | const val CHAT_COLLECTION = "chat_list"
5 | const val EMPTY_STRING = ""
6 |
7 | const val LOGIN_ID_KEY = "login_history_key"
8 | const val LOGIN_NAME_KEY = "login_name"
9 | const val LOGIN_URL_KEY = "login_url"
10 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/UserDataCallback.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data
2 |
3 | import io.getstream.chat.android.client.models.User
4 |
5 | interface UserDataCallback {
6 | fun onSuccess(user: User)
7 | fun onFailure(e: Throwable)
8 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/datasource/local/UserLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.datasource.local
2 |
3 | interface UserLocalDataSource {
4 | fun saveToken(id: String?, name: String?, imageUrl: String?)
5 | fun getToken(): String
6 | fun deleteToken()
7 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/datasource/local/UserLocalDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.datasource.local
2 |
3 | import com.kiwi.data.AppPreference
4 | import com.kiwi.data.Const
5 | import javax.inject.Inject
6 |
7 | class UserLocalDataSourceImpl @Inject constructor(
8 | private val pref: AppPreference,
9 | ) : UserLocalDataSource {
10 |
11 | override fun saveToken(
12 | id: String?, name: String?, imageUrl: String?
13 | ) {
14 | id?.let { pref.setString(Const.LOGIN_ID_KEY, it) }
15 | name?.let { pref.setString(Const.LOGIN_NAME_KEY, it) }
16 | imageUrl?.let { pref.setString(Const.LOGIN_URL_KEY, it) }
17 | }
18 |
19 | override fun deleteToken() {
20 | pref.setString(Const.LOGIN_ID_KEY, Const.EMPTY_STRING)
21 | //pref.setString(Const.LOGIN_NAME_KEY, Const.EMPTY_STRING)
22 | //pref.setString(Const.LOGIN_URL_KEY, Const.EMPTY_STRING)
23 | }
24 |
25 | override fun getToken(): String {
26 | return pref.getString(
27 | Const.LOGIN_ID_KEY, Const.EMPTY_STRING
28 | )
29 | }
30 |
31 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/datasource/remote/ExitChatRemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.datasource.remote
2 |
3 | import io.getstream.chat.android.client.models.Channel
4 |
5 | interface ExitChatRemoteDataSource {
6 | suspend fun exitChat(cid: String): Result
7 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/datasource/remote/ExitChatRemoteDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.datasource.remote
2 |
3 | import io.getstream.chat.android.client.ChatClient
4 | import io.getstream.chat.android.client.models.Channel
5 | import io.getstream.chat.android.client.utils.onError
6 | import io.getstream.chat.android.client.utils.onSuccess
7 | import javax.inject.Inject
8 | import kotlin.coroutines.resume
9 | import kotlin.coroutines.resumeWithException
10 | import kotlin.coroutines.suspendCoroutine
11 |
12 | class ExitChatRemoteDataSourceImpl @Inject constructor(private val chatClient: ChatClient) :
13 | ExitChatRemoteDataSource {
14 | override suspend fun exitChat(cid: String): Result {
15 | // chatClient.channel(cid).removeMembers(listOf(userId)).execute()
16 | val userIdList = listOfNotNull(chatClient.getCurrentUser()?.id)
17 | return suspendCoroutine { continuation ->
18 | chatClient.channel(cid).removeMembers(userIdList).enqueue() { it ->
19 | it.onSuccess {
20 | continuation.resume(Result.success(it))
21 | }.onError {
22 | continuation.resumeWithException(it.cause ?: Throwable())
23 | }
24 | }
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/datasource/remote/NewChatRemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.datasource.remote
2 |
3 | import com.kiwi.data.model.remote.NewChatRemote
4 |
5 | interface NewChatRemoteDataSource {
6 | suspend fun addChatUpload(userId: String, currentTime: String, newChatRemote: NewChatRemote)
7 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/datasource/remote/NewChatRemoteDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.datasource.remote
2 |
3 | import android.net.Uri
4 | import android.util.Log
5 | import com.google.firebase.firestore.FirebaseFirestore
6 | import com.google.firebase.storage.FirebaseStorage
7 | import com.kiwi.chatmapper.ChatKey
8 | import com.kiwi.data.model.remote.NewChatRemote
9 | import io.getstream.chat.android.client.ChatClient
10 | import javax.inject.Inject
11 |
12 | class NewChatRemoteDataSourceImpl @Inject constructor(
13 | private val firestore: FirebaseFirestore,
14 | private val chatClient: ChatClient,
15 | private val storage: FirebaseStorage,
16 | ) : NewChatRemoteDataSource {
17 |
18 | override suspend fun addChatUpload(
19 | userId: String,
20 | currentTime: String,
21 | newChatRemote: NewChatRemote
22 | ) {
23 | uploadImageForAddStreamChat(userId, currentTime, newChatRemote)
24 | addFireBaseChat(userId, currentTime, newChatRemote)
25 | }
26 |
27 | private fun uploadImageForAddStreamChat(
28 | userId: String,
29 | currentTime: String,
30 | newChatRemote: NewChatRemote,
31 | ) {
32 | val cid = userId + currentTime
33 | if (newChatRemote.imageUri.isEmpty()) {
34 | addStreamChat(userId, cid, "", newChatRemote)
35 | } else {
36 | val ref = storage.reference.child("chat/$cid")
37 | ref.putFile(Uri.parse(newChatRemote.imageUri)).addOnSuccessListener {
38 | it.storage.downloadUrl.addOnCompleteListener { url ->
39 | addStreamChat(userId, cid, url.result.toString(), newChatRemote)
40 | }
41 | }.addOnFailureListener {
42 | Log.d("NewChatDataSource", "putFile Failure: $it")
43 | }
44 | }
45 | }
46 |
47 | private fun addStreamChat(
48 | userId: String,
49 | cid: String,
50 | imageUrl: String,
51 | newChatRemote: NewChatRemote,
52 | ) {
53 | val streamData = hashMapOf(
54 | "image" to imageUrl,
55 | "name" to newChatRemote.chatName,
56 | ChatKey.CHAT_DESCRIPTION to newChatRemote.chatDescription,
57 | ChatKey.CHAT_ADDRESS to newChatRemote.address,
58 | ChatKey.CHAT_KEYWORDS to newChatRemote.keywords,
59 | ChatKey.CHAT_MAX_MEMBER_COUNT to newChatRemote.maxMemberCount
60 | )
61 | chatClient.createChannel(
62 | channelType = "messaging",
63 | channelId = cid,
64 | memberIds = listOf(userId),
65 | extraData = streamData
66 | ).enqueue() {
67 | Log.d("NewChatDataSource", "createChannel: $it")
68 | }
69 | }
70 |
71 | private fun addFireBaseChat(userId: String, currentTime: String, newChatRemote: NewChatRemote) {
72 | val fireStoreData = hashMapOf(
73 | "cid" to "messaging:$userId$currentTime",
74 | ChatKey.CHAT_KEYWORDS to newChatRemote.keywords,
75 | "lat" to newChatRemote.lat,
76 | "lng" to newChatRemote.lng
77 | )
78 |
79 | firestore.collection("chat_list").document().set(fireStoreData)
80 | .addOnSuccessListener {
81 | Log.d("addFireBaseChat", "DocumentSnapshot successfully written!")
82 | }
83 | .addOnFailureListener {
84 | Log.d("addFireBaseChat", "Error writing document")
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/datasource/remote/SearchChatRemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.datasource.remote
2 |
3 | import com.kiwi.domain.model.ChatInfo
4 | import com.kiwi.domain.model.Marker
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface SearchChatRemoteDataSource {
8 | fun getMarkerList(keyword: List, x: Double, y: Double): Flow
9 | suspend fun getChatList(cidList: List): List?
10 | fun appendUserToChat(cid: String, userId: String)
11 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/datasource/remote/SearchChatRemoteDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.datasource.remote
2 |
3 | import android.util.Log
4 | import com.google.firebase.firestore.FirebaseFirestore
5 | import com.google.firebase.firestore.ktx.toObjects
6 | import com.kiwi.chatmapper.ChatKey
7 | import com.kiwi.data.Const
8 | import com.kiwi.data.mapper.Mapper.toChatInfo
9 | import com.kiwi.data.mapper.Mapper.toMarker
10 | import com.kiwi.data.model.remote.MarkerRemote
11 | import com.kiwi.domain.model.ChatInfo
12 | import com.kiwi.domain.model.Marker
13 | import io.getstream.chat.android.client.ChatClient
14 | import io.getstream.chat.android.client.api.models.QueryChannelsRequest
15 | import io.getstream.chat.android.client.api.models.querysort.QuerySortByField
16 | import io.getstream.chat.android.client.models.Filters
17 | import kotlinx.coroutines.cancel
18 | import kotlinx.coroutines.channels.awaitClose
19 | import kotlinx.coroutines.flow.Flow
20 | import kotlinx.coroutines.flow.callbackFlow
21 | import javax.inject.Inject
22 |
23 | class SearchChatRemoteDataSourceImpl @Inject constructor(
24 | private val firestore: FirebaseFirestore,
25 | private val chatClient: ChatClient,
26 | ) : SearchChatRemoteDataSource {
27 | override fun getMarkerList(
28 | keyword: List,
29 | x: Double,
30 | y: Double
31 | ): Flow = callbackFlow {
32 | firestore.collection(Const.CHAT_COLLECTION)
33 | .whereArrayContainsAny(ChatKey.CHAT_KEYWORDS, keyword).get()
34 | .addOnSuccessListener { querySnapshot ->
35 | querySnapshot.toObjects().filter { markerRemote ->
36 | markerRemote.lat in x.toRange && markerRemote.lng in y.toRange
37 | }.forEach { markerRemote ->
38 | trySend(markerRemote.toMarker())
39 | }
40 | close()
41 | }.addOnFailureListener {
42 | cancel()
43 | }
44 | awaitClose()
45 | }
46 |
47 | override suspend fun getChatList(cidList: List): List? {
48 | val request = QueryChannelsRequest(
49 | filter = Filters.and(
50 | Filters.`in`("cid", cidList)
51 | ),
52 | offset = 0,
53 | limit = cidList.size,
54 | querySort = QuerySortByField.descByName("memberCount")
55 | ).apply {
56 | watch = true // if true returns the Channel state
57 | state = false // if true listen to changes to this Channel in real time.
58 | limit = cidList.size
59 | }
60 |
61 | val result = chatClient.queryChannels(request).await()
62 | return if (result.isSuccess) {
63 | Log.d(TAG, result.toString())
64 | result.data().map { it.toChatInfo() }
65 | } else {
66 | Log.d(TAG, result.toString())
67 | null
68 | }
69 | }
70 |
71 | override fun appendUserToChat(cid: String, userId: String) {
72 | val targetChannel = chatClient.channel(cid)
73 | targetChannel.addMembers(listOf(userId)).enqueue {
74 | if (it.isSuccess) {
75 | Log.d(TAG, "초대 성공")
76 | } else {
77 | Log.d(TAG, "초대 실패")
78 | }
79 | }
80 | }
81 |
82 | companion object {
83 | private const val TAG = "k001|SearchChatRemote"
84 | private val Double.toRange get() = this - 0.015..this + 0.015 //약 -1.5km~+1.5km
85 | }
86 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/datasource/remote/SearchKeywordRemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.datasource.remote
2 |
3 | import android.util.Log
4 | import com.google.firebase.firestore.FirebaseFirestore
5 | import com.kiwi.chatmapper.ChatKey
6 | import com.kiwi.domain.model.Keyword
7 | import com.kiwi.domain.model.KeywordCategory
8 | import kotlinx.coroutines.tasks.await
9 | import javax.inject.Inject
10 |
11 | class SearchKeywordRemoteDataSource @Inject constructor(
12 | val firestore: FirebaseFirestore
13 | ) {
14 | suspend fun callAllKeyword(): List {
15 | val keywordCategoryList: MutableList = mutableListOf()
16 |
17 | firestore.collection(ChatKey.CHAT_KEYWORDS).get()
18 | .addOnSuccessListener { result ->
19 | result.documents.forEach {
20 | val keywordCategory = KeywordCategory(it.id, mutableListOf())
21 | it.data?.values?.forEach {
22 | (it as HashMap<*, *>).let { keyword ->
23 | keywordCategory.keywords.add(
24 | Keyword(
25 | keyword.get("name").toString(),
26 | (keyword.get("count") as Long).toInt()
27 | )
28 | )
29 | }
30 | }
31 | keywordCategoryList.add(keywordCategory)
32 | Log.d("FIRESTORE_CALL_KEYWORD", "callAllKeyword: sucess data : ${keywordCategory}")
33 | }
34 | Log.d("FIRESTORE_CALL_KEYWORD", "callAllKeyword: sucess resultString : ${keywordCategoryList}")
35 | }
36 | .addOnFailureListener {
37 | Log.d("FIRESTORE_CALL_KEYWORD", "callAllKeyword: fail")
38 | }.await()
39 |
40 | return keywordCategoryList.toList()
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/datasource/remote/SearchPlaceRemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.datasource.remote
2 |
3 | import com.kiwi.data.model.remote.PlaceListRemote
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface SearchPlaceRemoteDataSource {
7 | suspend fun getSearchPlace(lng:String,lat:String,place: String): Flow
8 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/datasource/remote/SearchPlaceRemoteDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.datasource.remote
2 |
3 | import android.accounts.NetworkErrorException
4 | import com.kiwi.data.model.remote.PlaceListRemote
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.flow.flow
7 | import retrofit2.Retrofit
8 | import javax.inject.Inject
9 |
10 | class SearchPlaceRemoteDataSourceImpl @Inject constructor(
11 | client: Retrofit?
12 | ) : SearchPlaceRemoteDataSource {
13 |
14 | private val searchService = client?.create(SearchService::class.java)
15 |
16 | override suspend fun getSearchPlace(
17 | lng: String,
18 | lat: String,
19 | place: String
20 | ): Flow = flow {
21 | val response = searchService?.getSearchKeyword(lng,lat,"15000",place)
22 | val body = response?.body()
23 | if(response?.isSuccessful == true && body != null){
24 | emit(body)
25 | } else {
26 | throw NetworkErrorException("Network Connect Error")
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/datasource/remote/SearchService.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.datasource.remote
2 |
3 | import com.kiwi.data.model.remote.PlaceListRemote
4 | import retrofit2.Call
5 | import retrofit2.Response
6 | import retrofit2.http.GET
7 | import retrofit2.http.Query
8 |
9 | interface SearchService {
10 |
11 | @GET("v2/local/search/keyword.json")
12 | suspend fun getSearchKeyword(
13 | @Query("x") lng: String,
14 | @Query("y") lat: String,
15 | @Query("radius") radius: String,
16 | @Query("query") place: String
17 | ): Response
18 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/datasource/remote/UserRemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.datasource.remote
2 |
3 | import com.kiwi.data.UserDataCallback
4 | import io.getstream.chat.android.client.models.User
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface UserRemoteDataSource {
8 | fun login(token: String, callback: UserDataCallback)
9 | fun updateUser(user: User)
10 | suspend fun signOut(): Flow
11 | fun getCurrentUser(): User?
12 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/datasource/remote/UserRemoteDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.datasource.remote
2 |
3 | import com.kiwi.data.UserDataCallback
4 | import io.getstream.chat.android.client.ChatClient
5 | import io.getstream.chat.android.client.models.User
6 | import kotlinx.coroutines.channels.awaitClose
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.callbackFlow
9 | import javax.inject.Inject
10 |
11 | class UserRemoteDataSourceImpl @Inject constructor(
12 | private val chatClient: ChatClient
13 | ) : UserRemoteDataSource {
14 |
15 | override fun login(token: String, callback: UserDataCallback) {
16 | // repository에서 토큰 검사는 끝냈음
17 | val devToken = chatClient.devToken(token)
18 | val existUser = chatClient.getCurrentUser()
19 |
20 | if (existUser != null) {
21 | callback.onSuccess(existUser)
22 | return
23 | }
24 | val newUser = User(id = token)
25 | chatClient.connectUser(newUser, devToken).enqueue { result ->
26 | if (result.isSuccess) {
27 | callback.onSuccess(result.data().user)
28 | } else {
29 | callback.onFailure(result.error().cause?:Throwable("Error가 null이다."))
30 | }
31 | }
32 | }
33 |
34 | override fun updateUser(user: User) {
35 | chatClient.updateUser(user).enqueue()
36 | }
37 |
38 | override suspend fun signOut(): Flow = callbackFlow {
39 | chatClient.disconnect(flushPersistence = false).enqueue { disconnectResult ->
40 | if (disconnectResult.isSuccess) {
41 | trySend(true)
42 | } else {
43 | disconnectResult.error().cause?.let { throw it }
44 | }
45 | }
46 | awaitClose()
47 | }
48 |
49 | override fun getCurrentUser(): User? {
50 | return chatClient.getCurrentUser()
51 | }
52 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/mapper/Mapper.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.mapper
2 |
3 | import com.kiwi.chatmapper.ChatKey
4 | import com.kiwi.chatmapper.ChatMapper
5 | import com.kiwi.data.model.remote.MarkerRemote
6 | import com.kiwi.data.model.remote.NewChatRemote
7 | import com.kiwi.data.model.remote.PlaceListRemote
8 | import com.kiwi.data.model.remote.PlaceRemote
9 | import com.kiwi.domain.model.*
10 | import io.getstream.chat.android.client.models.Channel
11 | import io.getstream.chat.android.client.models.User
12 |
13 | object Mapper {
14 | fun MarkerRemote.toMarker() = Marker(
15 | cid = cid,
16 | x = lat,
17 | y = lng,
18 | keywords = keywords
19 | )
20 |
21 | fun PlaceListRemote.toPlaceList(): PlaceInfoList {
22 | val result = mutableListOf()
23 | documents.mapIndexed { index, placeRemote ->
24 | result.add(index, placeRemote.toPlace())
25 | }
26 | return PlaceInfoList(list = result)
27 | }
28 |
29 | fun PlaceRemote.toPlace() = PlaceInfo(
30 | placeName = place_name,
31 | addressName = address_name,
32 | roadAddressName = road_address_name,
33 | lng = x,
34 | lat = y
35 | )
36 |
37 | fun Channel.toChatInfo() = ChatInfo(
38 | cid = this.cid,
39 | name = this.name,
40 | keywords = ChatMapper.getKeywords(this.extraData),
41 | description = ChatMapper.getDescription(this.extraData),
42 | memberCount = this.memberCount,
43 | lastMessageAt = ChatMapper.getDateOfLastMassage(this.lastMessageAt),
44 | address = ChatMapper.getTrimAddress(this.extraData),
45 | )
46 |
47 | fun NewChatInfo.toNewChatRemote() = NewChatRemote(
48 | imageUri = this.imageUri,
49 | chatName = this.chatName,
50 | chatDescription = this.chatDescription,
51 | maxMemberCount = this.maxMemberCnt,
52 | keywords = this.keywords,
53 | address = this.address,
54 | lat = this.lat,
55 | lng = this.lng
56 | )
57 |
58 |
59 | fun User.toUserInfo(): UserInfo {
60 | return UserInfo(
61 | id = this.id,
62 | name = this.name,
63 | keywords = ChatMapper.getKeywords(extraData).map { Keyword(it) },
64 | imageUrl = image,
65 | description = ChatMapper.getDescription(extraData)
66 | )
67 | }
68 |
69 | fun UserInfo.toUser(): User {
70 | val user = User(
71 | id = this.id,
72 | name = this.name,
73 | image = this.imageUrl
74 | )
75 | user.extraData[ChatKey.CHAT_KEYWORDS] = this.keywords.map { it.name }
76 | user.extraData[ChatKey.CHAT_DESCRIPTION] = this.description
77 | return user
78 | }
79 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/model/remote/MarkerRemote.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.model.remote
2 |
3 | data class MarkerRemote(
4 | val cid: String = "",
5 | val lat: Double = .0,
6 | val lng: Double = .0,
7 | val keywords: List = emptyList()
8 | )
9 |
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/model/remote/NewChatRemote.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.model.remote
2 |
3 | data class NewChatRemote(
4 | val imageUri: String = "",
5 | val chatName: String,
6 | val chatDescription: String,
7 | val maxMemberCount: String,
8 | val keywords: List,
9 | val address: String,
10 | val lat: Double,
11 | val lng: Double
12 | )
13 |
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/model/remote/PlaceListRemote.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.model.remote
2 |
3 | data class PlaceListRemote(
4 | var documents: List
5 | )
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/model/remote/PlaceRemote.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.model.remote
2 |
3 | data class PlaceRemote (
4 | val place_name: String,
5 | val address_name: String,
6 | val road_address_name: String,
7 | val x: String,
8 | val y: String
9 | )
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/repository/ExitChatRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.repository
2 |
3 | import com.kiwi.data.datasource.remote.ExitChatRemoteDataSource
4 | import com.kiwi.domain.repository.ExitChatRepository
5 | import javax.inject.Inject
6 |
7 | class ExitChatRepositoryImpl @Inject constructor(
8 | private val datasource: ExitChatRemoteDataSource
9 | ) : ExitChatRepository {
10 | override suspend fun exitChat(cid: String): Result {
11 | return datasource.exitChat(cid).map { it.cid }
12 | }
13 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/repository/NewChatRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.repository
2 |
3 | import com.kiwi.data.datasource.remote.NewChatRemoteDataSource
4 | import com.kiwi.data.mapper.Mapper.toNewChatRemote
5 | import com.kiwi.domain.model.NewChatInfo
6 | import com.kiwi.domain.repository.NewChatRepository
7 | import javax.inject.Inject
8 |
9 | class NewChatRepositoryImpl @Inject constructor(
10 | private val dateSource: NewChatRemoteDataSource
11 | ): NewChatRepository {
12 | override suspend fun addChatUpload(
13 | userId: String,
14 | currentTime: String,
15 | newChat: NewChatInfo
16 | ) {
17 | dateSource.addChatUpload(userId,currentTime,newChat.toNewChatRemote())
18 | }
19 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/repository/ProfileSettingRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.repository
2 |
3 | import com.kiwi.domain.repository.ProfileSettingRepository
4 |
5 | class ProfileSettingRepositoryImpl(
6 |
7 | ): ProfileSettingRepository {
8 |
9 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/repository/SearchChatRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.repository
2 |
3 | import android.util.Log
4 | import com.kiwi.data.datasource.local.UserLocalDataSource
5 | import com.kiwi.data.datasource.remote.SearchChatRemoteDataSource
6 | import com.kiwi.domain.model.ChatInfo
7 | import com.kiwi.domain.model.Keyword
8 | import com.kiwi.domain.model.Marker
9 | import com.kiwi.domain.model.PlaceChatInfo
10 | import com.kiwi.domain.repository.SearchChatRepository
11 | import kotlinx.coroutines.flow.Flow
12 | import javax.inject.Inject
13 |
14 | class SearchChatRepositoryImpl @Inject constructor(
15 | private val searchChatDataSource: SearchChatRemoteDataSource,
16 | private val userDataSource: UserLocalDataSource,
17 | ) : SearchChatRepository {
18 | override fun getMarkerList(
19 | keywords: List,
20 | x: Double,
21 | y: Double
22 | ): Flow {
23 | Log.d("SearchChatRepository", "getMarkerList: start flow")
24 | return searchChatDataSource.getMarkerList(keywords.map { it.name }, x, y)
25 | }
26 |
27 | override suspend fun getPlaceChatList(cidList: List): PlaceChatInfo {
28 | val chatList = searchChatDataSource.getChatList(cidList) ?: mutableListOf()
29 | return PlaceChatInfo(chatList)
30 | }
31 |
32 | override fun appendUserToChat(cid: String){
33 | val userId = userDataSource.getToken()
34 | if (userId.isNotEmpty()){
35 | searchChatDataSource.appendUserToChat(cid, userId)
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/repository/SearchKeywordRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.repository
2 |
3 | import com.kiwi.data.datasource.remote.SearchKeywordRemoteDataSource
4 | import com.kiwi.domain.model.KeywordCategory
5 | import com.kiwi.domain.repository.SearchKeywordRepository
6 | import javax.inject.Inject
7 |
8 | class SearchKeywordRepositoryImpl @Inject constructor(
9 | val searchKeywordRemoteDataSource: SearchKeywordRemoteDataSource
10 | ) : SearchKeywordRepository {
11 | override suspend fun getAllKeyWord(): List {
12 | return searchKeywordRemoteDataSource.callAllKeyword()
13 | }
14 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/repository/SearchPlaceRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.repository
2 |
3 | import com.kiwi.data.datasource.remote.SearchPlaceRemoteDataSource
4 | import com.kiwi.data.mapper.Mapper.toPlaceList
5 | import com.kiwi.domain.model.PlaceInfoList
6 | import com.kiwi.domain.repository.SearchPlaceRepository
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.flow
9 | import javax.inject.Inject
10 |
11 | class SearchPlaceRepositoryImpl @Inject constructor(
12 | private val dataSource: SearchPlaceRemoteDataSource
13 | ): SearchPlaceRepository {
14 | override suspend fun getSearchPlace(
15 | lng: String,
16 | lat: String,
17 | place: String
18 | ): Flow = flow {
19 | dataSource.getSearchPlace(lng, lat, place).collect {
20 | emit(it.toPlaceList())
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/kiwi/data/repository/UserRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data.repository
2 |
3 | import com.kiwi.data.UserDataCallback
4 | import com.kiwi.data.datasource.local.UserLocalDataSource
5 | import com.kiwi.data.datasource.remote.UserRemoteDataSource
6 | import com.kiwi.data.mapper.Mapper.toUser
7 | import com.kiwi.data.mapper.Mapper.toUserInfo
8 | import com.kiwi.domain.UserUiCallback
9 | import com.kiwi.domain.model.UserInfo
10 | import com.kiwi.domain.repository.UserRepository
11 | import io.getstream.chat.android.client.models.User
12 | import kotlinx.coroutines.flow.Flow
13 | import kotlinx.coroutines.flow.flow
14 | import javax.inject.Inject
15 |
16 | class UserRepositoryImpl @Inject constructor(
17 | private val userLocalDataSource: UserLocalDataSource,
18 | private val userRemoteDataSource: UserRemoteDataSource,
19 | ) : UserRepository {
20 |
21 | override fun isRemoteLoginRequired(userUiCallback: UserUiCallback) {
22 | val token = userLocalDataSource.getToken()
23 | if (!isValidToken(token)) {
24 | userLocalDataSource.deleteToken()
25 | userUiCallback.onFailure(INVALID_TOKEN)
26 | return
27 | }
28 |
29 | userRemoteDataSource.login(token, object : UserDataCallback {
30 | override fun onSuccess(user: User) {
31 | if (user.id.isNotEmpty()) {
32 | userUiCallback.onSuccess()
33 | } else {
34 | userUiCallback.onFailure(NO_DATA)
35 | }
36 | }
37 |
38 | override fun onFailure(e: Throwable) {
39 | userUiCallback.onFailure(e)
40 | }
41 | })
42 | }
43 |
44 | override fun tryLogin(
45 | token: String,
46 | googleName: String,
47 | imageUrl: String,
48 | userUiCallback: UserUiCallback
49 | ) {
50 | if (!isValidToken(token)) {
51 | throw INVALID_TOKEN
52 | }
53 | userRemoteDataSource.login(token, object : UserDataCallback {
54 | override fun onSuccess(user: User) {
55 | with(user) {
56 | if (id.isEmpty()) {
57 | userUiCallback.onFailure(NO_DATA)
58 | } else if (name.isEmpty()) {
59 | userRemoteDataSource.updateUser(
60 | User(id = this.id, name = googleName, image = imageUrl)
61 | )
62 | lastUser = UserInfo(this.id, googleName, listOf())
63 | } else {
64 | userLocalDataSource.saveToken(id, name, image)
65 | lastUser = this.toUserInfo()
66 | }
67 | }
68 | userUiCallback.onSuccess()
69 | }
70 |
71 | override fun onFailure(e: Throwable) {
72 | userUiCallback.onFailure(e)
73 | }
74 | })
75 | }
76 |
77 | override suspend fun signOut(): Flow = flow {
78 | userRemoteDataSource.signOut().collect {
79 | if (it) userLocalDataSource.deleteToken()
80 | emit(it)
81 | }
82 | }
83 |
84 | private fun isValidToken(token: String): Boolean {
85 | return tokenRegex.matches(token)
86 | }
87 |
88 | override fun getUserInfo(): UserInfo {
89 | userRemoteDataSource.getCurrentUser()?.let {
90 | lastUser = it.toUserInfo()
91 | }
92 | return lastUser.copy()
93 | }
94 |
95 | override fun updateUser(userInfo: UserInfo) {
96 | userRemoteDataSource.updateUser(
97 | userInfo.toUser()
98 | )
99 | }
100 |
101 | companion object {
102 | private const val TAG = "k001|UserRepo"
103 | private val tokenRegex = Regex("[0-9,a-z]{1,21}")
104 | private val INVALID_TOKEN = Exception("Invalid Token")
105 | private val NO_DATA = Exception("Stream에서 Id값이 Empty String으로 반환됨")
106 | private var lastUser: UserInfo = UserInfo("","", listOf())
107 | }
108 | }
--------------------------------------------------------------------------------
/data/src/test/java/com/kiwi/data/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.data
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java-library'
3 | id 'org.jetbrains.kotlin.jvm'
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_1_7
8 | targetCompatibility = JavaVersion.VERSION_1_7
9 | }
10 |
11 | dependencies{
12 | // Coroutine
13 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8'
14 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/UserUiCallback.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain
2 |
3 | interface UserUiCallback {
4 | fun onSuccess()
5 | fun onFailure(e: Throwable)
6 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/model/ChatInfo.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain.model
2 |
3 | data class ChatInfo(
4 | val cid:String,
5 | val name: String,
6 | val keywords: List,
7 | val description: String,
8 | val memberCount: Int,
9 | val lastMessageAt: String,
10 | val address: String,
11 | )
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/model/Keyword.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain.model
2 |
3 | data class Keyword(
4 | val name: String,
5 | val count: Int = 0
6 | )
7 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/model/KeywordCategory.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain.model
2 |
3 | data class KeywordCategory(
4 | var name: String,
5 | var keywords: MutableList
6 | )
7 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/model/Marker.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain.model
2 |
3 | data class Marker(
4 | val cid: String,
5 | val x: Double,
6 | val y: Double,
7 | val keywords: List
8 | )
9 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/model/NewChatInfo.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain.model
2 |
3 | data class NewChatInfo(
4 | val imageUri: String = "",
5 | val chatName: String,
6 | val chatDescription: String,
7 | val maxMemberCnt: String,
8 | val keywords: List,
9 | val address: String,
10 | val lat: Double,
11 | val lng: Double
12 | )
13 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/model/PlaceChatInfo.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain.model
2 |
3 | data class PlaceChatInfo(
4 | val chatList: List,
5 | ) {
6 | fun getChattingNumber(): Int {
7 | return chatList.size
8 | }
9 |
10 | fun getPopularChat(): ChatInfo? {
11 | return chatList.getOrNull(0)
12 | }
13 |
14 | fun getPlaceInfo(): String? {
15 | return chatList.getOrNull(0)?.address
16 | }
17 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/model/PlaceInfo.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain.model
2 |
3 | data class PlaceInfo(
4 | var placeName: String, // 장소명, 업체명
5 | var addressName: String, // 전체 지번 주소
6 | var roadAddressName: String, // 전체 도로명 주소
7 | var lng: String, // X 좌표값 혹은 longitude
8 | var lat: String, // Y 좌표값 혹은 latitude
9 | )
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/model/PlaceInfoList.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain.model
2 |
3 | data class PlaceInfoList(
4 | val list: List?
5 | )
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/model/UserInfo.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain.model
2 |
3 | data class UserInfo(
4 | val id: String,
5 | val name: String,
6 | val keywords : List,
7 | val imageUrl: String = "",
8 | val description: String = ""
9 | )
10 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/repository/ExitChatRepository.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain.repository
2 |
3 | interface ExitChatRepository {
4 | suspend fun exitChat(cid: String): Result
5 | }
6 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/repository/NewChatRepository.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain.repository
2 |
3 | import com.kiwi.domain.model.NewChatInfo
4 |
5 | interface NewChatRepository {
6 | suspend fun addChatUpload(userId: String, currentTime: String, newChat: NewChatInfo)
7 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/repository/ProfileSettingRepository.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain.repository
2 |
3 | interface ProfileSettingRepository {
4 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/repository/SearchChatRepository.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain.repository
2 |
3 | import com.kiwi.domain.model.Marker
4 | import com.kiwi.domain.model.PlaceChatInfo
5 | import com.kiwi.domain.model.Keyword
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | interface SearchChatRepository {
9 | fun getMarkerList(keywords: List, x: Double, y: Double): Flow
10 | suspend fun getPlaceChatList(cidList: List): PlaceChatInfo
11 | fun appendUserToChat(cid: String)
12 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/repository/SearchKeywordRepository.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain.repository
2 |
3 | import com.kiwi.domain.model.KeywordCategory
4 |
5 | interface SearchKeywordRepository {
6 | suspend fun getAllKeyWord(): List
7 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/repository/SearchPlaceRepository.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain.repository
2 |
3 | import com.kiwi.domain.model.PlaceInfoList
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface SearchPlaceRepository {
7 | suspend fun getSearchPlace(lng:String,lat:String,place: String): Flow
8 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/kiwi/domain/repository/UserRepository.kt:
--------------------------------------------------------------------------------
1 | package com.kiwi.domain.repository
2 |
3 | import com.kiwi.domain.UserUiCallback
4 | import com.kiwi.domain.model.UserInfo
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface UserRepository {
8 | fun isRemoteLoginRequired(userUiCallback: UserUiCallback)
9 | fun tryLogin(
10 | token: String,
11 | googleName: String,
12 | imageUrl: String,
13 | userUiCallback: UserUiCallback
14 | )
15 |
16 | suspend fun signOut(): Flow
17 | fun getUserInfo(): UserInfo
18 | fun updateUser(userInfo: UserInfo)
19 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2022/android08-KiwiTalk/f6c3c00bc398b4e35a555fb98d07d2998c1814eb/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | maven { url "https://jitpack.io" }
14 | }
15 | }
16 | rootProject.name = "KiwiTalk"
17 | include ':app'
18 | include ':data'
19 | include ':domain'
20 | include ':chatmapper'
--------------------------------------------------------------------------------