diff --git a/.gitignore b/.gitignore index aa724b7..91ba8e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,156 @@ -*.iml +# Created by https://www.toptal.com/developers/gitignore/api/androidstudio,kotlin +# Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio,kotlin + +### Kotlin ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### AndroidStudio ### +# Covers files to be ignored for android development using Android Studio. + +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files + +# Generated files +bin/ +gen/ +out/ + +# Gradle files .gradle -/local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml -.DS_Store -/build -/captures -.externalNativeBuild -.cxx +.gradle/ +build/ + +# Signing files +.signing/ + +# Local configuration file (sdk path, etc) local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files + +# Android Studio +/*/build/ +/*/local.properties +/*/out +/*/*/build +/*/*/production +captures/ +.navigation/ +*.ipr +*~ +*.swp + +# Keystore files +*.jks +*.keystore + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Android Patch +gen-external-apklibs + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# NDK +obj/ + +# IntelliJ IDEA +*.iml +*.iws +/out/ + +# User-specific configurations +.idea/caches/ +.idea/libraries/ +.idea/shelf/ +.idea/workspace.xml +.idea/tasks.xml +.idea/.name +.idea/compiler.xml +.idea/copyright/profiles_settings.xml +.idea/encodings.xml +.idea/misc.xml +.idea/modules.xml +.idea/scopes/scope_settings.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml +.idea/datasources.xml +.idea/dataSources.ids +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml +.idea/assetWizardSettings.xml +.idea/gradle.xml +.idea/jarRepositories.xml +.idea/navEditor.xml + +# Legacy Eclipse project files +.classpath +.project +.cproject +.settings/ + +# Mobile Tools for Java (J2ME) + +# Package Files # + +# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) + +## Plugin-specific files: + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Mongo Explorer plugin +.idea/mongoSettings.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### AndroidStudio Patch ### + +!/gradle/wrapper/gradle-wrapper.jar + +# End of https://www.toptal.com/developers/gitignore/api/androidstudio,kotlin \ No newline at end of file diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 7d32cbb..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -Everymoment \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index b589d56..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 0897082..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 971db29..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index bd60fe7..d80a087 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,27 @@ # Team21_Android 21조 안드로이드 -## 코드 리뷰 받고 싶은 부분 (개발 질문): 5주차 +## 코드 리뷰 받고 싶은 부분 (개발 질문): 6주차 + +### todlf +[이슈 #29](https://github.com/kakao-tech-campus-2nd-step3/Team21_Android/issues/29) +[이슈 #30](https://github.com/kakao-tech-campus-2nd-step3/Team21_Android/issues/30) +- FCM으로 알림을 받을 때, 백그라운드에서도 remoteView를 이용한 커스텀 알림이 가능한지 궁금합니다. +- 앱이 종료되었는데 알림이 올 경우, 알림에서 이모지를 눌러서 백엔드에 데이터를 보낼 수 있는지 궁금합니다.(이모지 상호작용 알림은 포그라운드 알림에서 처리하는게 나을까요?) +- 백엔드에서 알림 API를 받아와서 FCM으로 핸드폰으로 알림을 보내주는 걸 구현하려고 하는데, +- 이 과정을 어떤 식으로 처리하는지 궁금합니다.(어떻게 해야할지 감이 안 잡힙니다 ㅠㅠ) +- #29,30 이슈 해결방법이 궁금합니다. + +--- + +### arieum +- 이번주 PR에는 서버주소랑 jwtToken이 하드코딩되어있습니다! 다음 PR에는 추상화해서 올리겠습니다 +- friend_item.xml 에서 모든 뷰들을 layout_gravity를 center로 설정해도 이미지와 텍스트뷰가 가운데정렬이 되지 않습니다. vertical_center로 설정해도 안되는데 어떻게 처리해야하는지 궁금합니다 +- 5주차 코드리뷰 이제 확인해서 그부분도 다음 PR때 반영해서 올리겠습니다 + +--- ### settle54 -[이슈 #20](https://github.com/kakao-tech-campus-2nd-step3/Team21_Android/issues/20) -[이슈 #25](https://github.com/kakao-tech-campus-2nd-step3/Team21_Android/issues/25) -[이슈 #28](https://github.com/kakao-tech-campus-2nd-step3/Team21_Android/issues/28) -- 이슈 20, 이슈25, 이슈28에 대한 해결방법을 알고 싶습니다. -- 불필요하거나 비효율적인 코드가 있으면 개선받고 싶습니다! (카테고리를 추가하는 코드가 많이 스파게티가 된 것 같습니다...) +[이슈 #36](https://github.com/kakao-tech-campus-2nd-step3/Team21_Android/issues/36) +- 이슈 36에 대한 해결방법을 알고 싶습니다. +- 불필요하거나 비효율적인 코드가 있으면 개선받고 싶습니다! diff --git a/app/.gitignore b/app/.gitignore index 42afabf..65d12b9 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1 +1,2 @@ -/build \ No newline at end of file +/build +google-services.json \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 713f063..94e0d5c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -5,6 +5,7 @@ import java.util.Properties plugins { alias(libs.plugins.androidApplication) alias(libs.plugins.jetbrainsKotlinAndroid) + id("com.google.gms.google-services") } fun getApiKey(key: String): String = gradleLocalProperties(rootDir, providers).getProperty(key) @@ -65,11 +66,16 @@ dependencies { implementation(libs.okhttp) implementation(libs.play.services.location) implementation(libs.androidx.work.runtime.ktx) + implementation ("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6") implementation("com.github.prolificinteractive:material-calendarview:2.0.1") implementation ("com.jakewharton.threetenabp:threetenabp:1.2.1") implementation ("com.google.android.material:material:1.2.0") implementation ("com.kakao.sdk:v2-all:2.20.6") implementation ("com.kakao.sdk:v2-user:2.20.6") + implementation(platform("com.google.firebase:firebase-bom:33.1.2")) + implementation("com.google.firebase:firebase-analytics-ktx") + implementation("com.google.firebase:firebase-config-ktx:22.0.0") + implementation("com.google.firebase:firebase-messaging-ktx:24.0.0") testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index eab025c..2bf60b2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,10 +21,32 @@ android:supportsRtl="true" android:theme="@style/Theme.Everymoment" tools:targetApi="31"> + + + + + + + + + + + + + + + + android:exported="true" + android:screenOrientation="portrait"> @@ -37,11 +59,13 @@ + android:name=".presentation.view.main.KakaoLoginActivity" + android:exported="false" + android:screenOrientation="portrait" /> + android:name=".presentation.view.main.MainActivity" + android:exported="true" + android:screenOrientation="portrait"> diff --git a/app/src/main/java/com/example/everymoment/GlobalApplication.kt b/app/src/main/java/com/example/everymoment/GlobalApplication.kt index 2b0b832..0198ab4 100644 --- a/app/src/main/java/com/example/everymoment/GlobalApplication.kt +++ b/app/src/main/java/com/example/everymoment/GlobalApplication.kt @@ -1,11 +1,16 @@ package com.example.everymoment import android.app.Application +import com.example.everymoment.data.model.PreferenceUtil import com.kakao.sdk.common.KakaoSdk class GlobalApplication : Application() { + companion object { + lateinit var prefs: PreferenceUtil + } override fun onCreate() { super.onCreate() KakaoSdk.init(this, "${BuildConfig.KAKAO_NATIVE_KEY}") + prefs = PreferenceUtil(applicationContext) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/LocationService.kt b/app/src/main/java/com/example/everymoment/LocationService.kt index a76841a..a5eff5b 100644 --- a/app/src/main/java/com/example/everymoment/LocationService.kt +++ b/app/src/main/java/com/example/everymoment/LocationService.kt @@ -17,7 +17,9 @@ import android.util.Log import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import com.example.everymoment.data.model.NetworkUtil +import com.example.everymoment.data.repository.DiaryEntry import com.example.everymoment.data.repository.GooglePlacesResponse +import com.example.everymoment.data.repository.LocationPoint import com.google.android.gms.location.* class LocationService : Service() { @@ -55,6 +57,7 @@ class LocationService : Service() { locationRequest = LocationRequest.Builder(Priority.PRIORITY_BALANCED_POWER_ACCURACY, LOCATION_UPDATE_INTERVAL) .setMinUpdateIntervalMillis(LOCATION_UPDATE_INTERVAL) .setMaxUpdateDelayMillis(LOCATION_UPDATE_INTERVAL) + .setWaitForAccurateLocation(false) .build() locationCallback = object : LocationCallback() { @@ -84,20 +87,38 @@ class LocationService : Service() { val latitude = location.latitude val longitude = location.longitude - getPlaceNamesFromCoordinates(latitude, longitude) { currentPlaceNames -> + getPlaceNamesFromCoordinates(latitude, longitude) { currentPlaceNames, currentAddresses -> if (currentPlaceNames.isNotEmpty()) { Log.d("myplace", "$currentPlaceNames") val currentPlace = currentPlaceNames.firstOrNull() + val currentAddress = currentAddresses.firstOrNull() - // 처음 위치 측정이거나 현재 위치가 이전과 다를 때 if (initialPlaceName == null || (currentPlace != null && !currentPlaceNames.contains(initialPlaceName!!))) { initialPlaceName = currentPlace - Log.d("arieum", "새 장소 측정: $initialPlaceName, 아직 전달 안함") + Log.d("arieum", "새 장소 측정: $initialPlaceName, $currentAddress 아직 전달 안함") } else { - // 장소가 같을 때 if (isFirstLocationUpdateAfterChange) { - Log.d("arieum", "백엔드로 전달: $initialPlaceName") - isFirstLocationUpdateAfterChange = false // 첫 전달 이후에는 false로 설정하여 다시 전달 안함 + Log.d("arieum", "백엔드로 전달: $initialPlaceName, $currentAddress") + + val locationData = DiaryEntry( + locationPoint = LocationPoint(latitude, longitude), + locationName = currentPlace ?: "알 수 없는 장소", + address = currentAddress ?: "알 수 없는 위치" + ) + + NetworkUtil.sendData( + "http://13.125.156.74:8080/api/diaries/auto", + "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MiwiaWF0IjoxNzI4NTM4MDgzLCJleHAiOjE3Mjg3MTA4ODN9.ohkjWMb5haJ-aNzXdivYTskLeKPHd-EIw9FYfbQerBo", + locationData + ) { success, code, message, infoObject -> + if (success) { + Log.d("arieum", "성공! 코드: $code, 메시지: $message, 정보: $infoObject") + } else { + Log.d("arieum", "실패!") + } + } + + isFirstLocationUpdateAfterChange = false } else { Log.d("arieum", "세 번 이상 장소: $initialPlaceName, 더이상 전달 안함") } @@ -140,7 +161,7 @@ class LocationService : Service() { private fun getPlaceNamesFromCoordinates( latitude: Double, longitude: Double, - callback: (List) -> Unit + callback: (List, List) -> Unit ) { val apiKey = BuildConfig.API_KEY val url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" @@ -160,18 +181,19 @@ class LocationService : Service() { if (success && response != null) { try { val placeNames = response.results.map { it.name } - callback(placeNames) + val addresses = response.results.map { it.vicinity } + callback(placeNames, addresses) } catch (e: Exception) { - callback(emptyList()) + callback(emptyList(), emptyList()) } } else { - callback(emptyList()) + callback(emptyList(), emptyList()) } } } companion object { private const val NOTIFICATION_ID = 1 - private const val LOCATION_UPDATE_INTERVAL = 5 * 60 * 1000L // 5분 + private const val LOCATION_UPDATE_INTERVAL = 3 * 60 * 1000L } } \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/NotificationActionReceiver.kt b/app/src/main/java/com/example/everymoment/NotificationActionReceiver.kt new file mode 100644 index 0000000..3c73159 --- /dev/null +++ b/app/src/main/java/com/example/everymoment/NotificationActionReceiver.kt @@ -0,0 +1,36 @@ +package com.example.everymoment + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.widget.Toast +import com.example.everymoment.data.model.Emotions + +class NotificationActionReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val emotion = when (intent.action) { + "${Emotions.HAPPY.name}_ACTION" -> Emotions.HAPPY + "${Emotions.SAD.name}_ACTION" -> Emotions.SAD + "${Emotions.INSENSITIVE.name}_ACTION" -> Emotions.INSENSITIVE + "${Emotions.ANGRY.name}_ACTION" -> Emotions.ANGRY + "${Emotions.CONFOUNDED.name}_ACTION" -> Emotions.CONFOUNDED + else -> null + } + + emotion?.let { handleEmotion(context, it) } + } + + private fun handleEmotion(context: Context, emotion: Emotions) { + val emoji = emotion.getEmotionUnicode() + val label = when (emotion) { + Emotions.HAPPY -> "행복" + Emotions.SAD -> "슬픔" + Emotions.INSENSITIVE -> "무표정" + Emotions.ANGRY -> "화남" + Emotions.CONFOUNDED -> "싫음" + } + + // 선택된 감정 처리 -> 백엔드 전송 필요 + Toast.makeText(context, "선택된 감정: $emoji $label", Toast.LENGTH_SHORT).show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/data/model/FriendRequest.kt b/app/src/main/java/com/example/everymoment/data/model/FriendRequestList.kt similarity index 76% rename from app/src/main/java/com/example/everymoment/data/model/FriendRequest.kt rename to app/src/main/java/com/example/everymoment/data/model/FriendRequestList.kt index eb42b54..9e6e314 100644 --- a/app/src/main/java/com/example/everymoment/data/model/FriendRequest.kt +++ b/app/src/main/java/com/example/everymoment/data/model/FriendRequestList.kt @@ -1,6 +1,6 @@ package com.example.everymoment.data.model -data class FriendRequest( +data class FriendRequestList( val name: String, val profileImage: String ) \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/data/model/NetworkUtil.kt b/app/src/main/java/com/example/everymoment/data/model/NetworkUtil.kt index b66bbb1..c723197 100644 --- a/app/src/main/java/com/example/everymoment/data/model/NetworkUtil.kt +++ b/app/src/main/java/com/example/everymoment/data/model/NetworkUtil.kt @@ -105,17 +105,14 @@ object NetworkUtil { fun patchRequest( url: String, jwtToken: String, - queryParams: Int? = null, data: T? = null, callback: (success: Boolean, response: String?) -> Unit ) { - val fullUrl = if (queryParams != null) "$url?$queryParams" else url - val jsonBody = gson.toJson(data) val requestBody = jsonBody.toRequestBody("application/json; charset=utf-8".toMediaType()) val request = Request.Builder() - .url(fullUrl) + .url(url) .patch(requestBody) .addHeader("Authorization", "Bearer $jwtToken") .build() @@ -136,13 +133,11 @@ object NetworkUtil { fun deleteRequest( url: String, jwtToken: String, - queryParams: Int, + queryParams: Int? = null, callback: (success: Boolean, response: String?) -> Unit ) { - val fullUrl = "$url/$queryParams" - val request = Request.Builder() - .url(fullUrl) + .url(url) .delete() .addHeader("Authorization", "Bearer $jwtToken") .build() diff --git a/app/src/main/java/com/example/everymoment/data/model/PreferenceUtil.kt b/app/src/main/java/com/example/everymoment/data/model/PreferenceUtil.kt new file mode 100644 index 0000000..58a3e09 --- /dev/null +++ b/app/src/main/java/com/example/everymoment/data/model/PreferenceUtil.kt @@ -0,0 +1,17 @@ +package com.example.everymoment.data.model + +import android.content.Context +import android.content.SharedPreferences + +class PreferenceUtil(context: Context) { + private val prefs: SharedPreferences = + context.getSharedPreferences("prefs_name", Context.MODE_PRIVATE) + + fun getString(key: String, defValue: String): String { + return prefs.getString(key, defValue).toString() + } + + fun setString(key: String, str: String) { + prefs.edit().putString(key, str).apply() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/data/model/Timeline.kt b/app/src/main/java/com/example/everymoment/data/model/Timeline.kt deleted file mode 100644 index 4dcf87d..0000000 --- a/app/src/main/java/com/example/everymoment/data/model/Timeline.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.example.everymoment.data.model - -data class Timeline(val time: String, - val buildingName: String, - val address: String, - val emoji: String?, - val isDetailedDiary: Boolean) \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/data/repository/DiaryRepository.kt b/app/src/main/java/com/example/everymoment/data/repository/DiaryRepository.kt new file mode 100644 index 0000000..aad56fa --- /dev/null +++ b/app/src/main/java/com/example/everymoment/data/repository/DiaryRepository.kt @@ -0,0 +1,73 @@ +package com.example.everymoment.data.repository + +import android.util.Log +import com.example.everymoment.data.model.NetworkUtil + +class DiaryRepository { + private val jwtToken = "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MiwiaWF0IjoxNzI4NTM4MDgzLCJleHAiOjE3Mjg3MTA4ODN9.ohkjWMb5haJ-aNzXdivYTskLeKPHd-EIw9FYfbQerBo" + + fun getDiaries( + date: String, + callback: (Boolean, DiaryResponse?) -> Unit + ) { + val url = "http://13.125.156.74:8080/api/diaries/my" + val params = mapOf("date" to date) + + NetworkUtil.getData( + url, + jwtToken, + params, + DiaryResponse::class.java + ) { success, response -> + callback(success, response) + } + } + + fun updateBookmarkStatus( + diaryId: Int, + callback: (Boolean, String?) -> Unit + ) { + val url = "http://13.125.156.74:8080/api/diaries/$diaryId/bookmark" + + NetworkUtil.patchRequest( + url, + jwtToken, + null + ) { success, message -> + Log.d("arieum", "$message") + callback(success, message) + } + } + + fun updateShareStatus( + diaryId: Int, + callback: (Boolean, String?) -> Unit + ) { + val url = "http://13.125.156.74:8080/api/diaries/$diaryId/privacy" + + NetworkUtil.patchRequest( + url, + jwtToken, + null + ) { success, message -> + Log.d("arieum", "$message") + callback(success, message) + } + } + + fun deleteDiary( + diaryId: Int, + callback: (Boolean, String?) -> Unit + ) { + val url = "http://13.125.156.74:8080/api/diaries/$diaryId" + + NetworkUtil.deleteRequest( + url, + jwtToken, + null + ) { success, response -> + Log.d("arieum", "$response") + callback(success, response) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/data/repository/MemberResponse.kt b/app/src/main/java/com/example/everymoment/data/repository/MemberResponse.kt new file mode 100644 index 0000000..511f932 --- /dev/null +++ b/app/src/main/java/com/example/everymoment/data/repository/MemberResponse.kt @@ -0,0 +1,14 @@ +package com.example.everymoment.data.repository + +data class MemberResponse( + val info: MemberInfo +) + +data class MemberInfo( + val members: List +) +data class Member( + val id: Int, + val nickname: String, + val profileImageUrl: String +) \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/data/repository/UserRepository.kt b/app/src/main/java/com/example/everymoment/data/repository/UserRepository.kt index 4d3b26e..b239650 100644 --- a/app/src/main/java/com/example/everymoment/data/repository/UserRepository.kt +++ b/app/src/main/java/com/example/everymoment/data/repository/UserRepository.kt @@ -1,6 +1,9 @@ package com.example.everymoment.data.repository import android.app.Activity +import android.util.Log +import com.example.everymoment.GlobalApplication +import com.example.everymoment.data.model.NetworkUtil import com.kakao.sdk.auth.model.OAuthToken import com.kakao.sdk.user.UserApiClient import com.kakao.sdk.user.model.AccessTokenInfo @@ -35,4 +38,27 @@ class UserRepository { callback(user, error) } } + + fun requestToken(userId: Long?, nickname: String?) { + NetworkUtil.sendData( + "http://13.125.156.74:8080/api/members/login", + null, + mapOf( + "number" to userId, + "nickname" to nickname + ) + ) { success, code, message, infoObject-> + if (success) { + val token = infoObject?.get("token")?.asString + + if (token != null) { + GlobalApplication.prefs.setString("token", token) + } + + Log.d("arieum", "서버 응답: $token") + } else { + Log.d("arieum", "Network failed") + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/extensions/CategoryPopup.kt b/app/src/main/java/com/example/everymoment/extensions/CategoryPopup.kt index d494e8d..be4cc1c 100644 --- a/app/src/main/java/com/example/everymoment/extensions/CategoryPopup.kt +++ b/app/src/main/java/com/example/everymoment/extensions/CategoryPopup.kt @@ -11,8 +11,10 @@ import android.widget.ImageView import android.widget.PopupWindow import android.widget.TextView import androidx.fragment.app.FragmentActivity +import androidx.recyclerview.widget.DiffUtil import com.example.everymoment.databinding.CategoryPopupBinding import com.example.everymoment.R +import com.example.everymoment.data.model.Friends class CategoryPopup( private val fragmentActivity: FragmentActivity, @@ -111,6 +113,7 @@ class CategoryPopup( private fun addCategoryTextView(userInput: String) { val textView = TextView(context).apply { + textSize = 16f layoutParams = GridLayout.LayoutParams().apply { width = GridLayout.LayoutParams.WRAP_CONTENT height = GridLayout.LayoutParams.MATCH_PARENT @@ -139,4 +142,5 @@ class CategoryPopup( addButton.visibility = View.GONE } } + } diff --git a/app/src/main/java/com/example/everymoment/fcm/MyFirebaseMessagingService.kt b/app/src/main/java/com/example/everymoment/fcm/MyFirebaseMessagingService.kt new file mode 100644 index 0000000..da82129 --- /dev/null +++ b/app/src/main/java/com/example/everymoment/fcm/MyFirebaseMessagingService.kt @@ -0,0 +1,113 @@ +package com.example.everymoment.fcm + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.util.Log +import android.widget.RemoteViews +import androidx.core.app.NotificationCompat +import com.example.everymoment.NotificationActionReceiver +import com.example.everymoment.R +import com.example.everymoment.data.model.Emotions +import com.example.everymoment.presentation.view.main.MainActivity +import com.google.firebase.messaging.FirebaseMessagingService +import com.google.firebase.messaging.RemoteMessage + +class MyFirebaseMessagingService : FirebaseMessagingService() { + + private lateinit var notificationManager: NotificationManager + + override fun onNewToken(token: String) { + super.onNewToken(token) + Log.d("FCM Token", "New token: $token") + } + + override fun onMessageReceived(remoteMessage: RemoteMessage) { + Log.d("testt", "From: ${remoteMessage.from}") + + remoteMessage.notification?.let { + Log.d("testt", "Message Notification Body: ${it.body}") + + notificationManager = + getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + createNotificationChannel() + setNotification() + } + } + + private fun setNotification() { + val intent = Intent(this, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + val pendingIntent = PendingIntent.getActivity( + this, + 0, + intent, + PendingIntent.FLAG_IMMUTABLE + ) + + val remoteViews = RemoteViews(packageName, R.layout.custom_notification) + remoteViews.setTextViewText(R.id.happyEmojiTextView, Emotions.HAPPY.getEmotionUnicode()) + remoteViews.setTextViewText(R.id.sadEmojiTextView, Emotions.SAD.getEmotionUnicode()) + remoteViews.setTextViewText(R.id.insensitiveEmojiTextView, Emotions.INSENSITIVE.getEmotionUnicode()) + remoteViews.setTextViewText(R.id.angryEmojiTextView, Emotions.ANGRY.getEmotionUnicode()) + remoteViews.setTextViewText(R.id.confoundedEmojiTextView, Emotions.CONFOUNDED.getEmotionUnicode()) + + val emotions = listOf( + R.id.happyEmojiTextView to Emotions.HAPPY, + R.id.sadEmojiTextView to Emotions.SAD, + R.id.insensitiveEmojiTextView to Emotions.INSENSITIVE, + R.id.angryEmojiTextView to Emotions.ANGRY, + R.id.confoundedEmojiTextView to Emotions.CONFOUNDED + ) + + emotions.forEach { (viewId, emotion) -> + val emotionIntent = Intent(this, NotificationActionReceiver::class.java).apply { + action = "${emotion.name}_ACTION" + } + val emotionPendingIntent = PendingIntent.getBroadcast( + this, + viewId, + emotionIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + remoteViews.setOnClickPendingIntent(viewId, emotionPendingIntent) + } + + val builder = NotificationCompat.Builder( + this, + CHANNEL_ID + ) + .setSmallIcon(R.drawable.ic_launcher_foreground) + .setContentTitle("EveryMoment") + .setContentIntent(pendingIntent) + .setContentText("현재 XX 위치에 머무르고 있어요! 지금의 기분은 어떠신가요?") + .setCustomBigContentView(remoteViews) + .setStyle(NotificationCompat.DecoratedCustomViewStyle()) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setAutoCancel(true) + + notificationManager.notify(NOTIFICATION_ID, builder.build()) + } + + private fun createNotificationChannel() { + val descriptionText = getString(R.string.fcm_channel_description) + val channel = NotificationChannel( + CHANNEL_ID, + CHANNEL_NAME, + NotificationManager.IMPORTANCE_DEFAULT + ).apply { + description = descriptionText + } + notificationManager.createNotificationChannel(channel) + } + + companion object { + private const val NOTIFICATION_ID = 222222 + private const val CHANNEL_ID = "main_default_channel" + private const val CHANNEL_NAME = "main channelName" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/presentation/adapter/CategoryAdapter.kt b/app/src/main/java/com/example/everymoment/presentation/adapter/CategoryAdapter.kt new file mode 100644 index 0000000..58f1d82 --- /dev/null +++ b/app/src/main/java/com/example/everymoment/presentation/adapter/CategoryAdapter.kt @@ -0,0 +1,60 @@ +package com.example.everymoment.presentation.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import com.example.everymoment.R +import com.example.everymoment.databinding.CategoryLayoutBinding + +class CategoryAdapter() : + RecyclerView.Adapter() { + + private var categoryList: List = listOf("#힐링", "#공부", "#놀이", "#zzzzzzz", "#ddddd", "#ee") + private var selectedCategories = mutableSetOf() + + + fun resetSelected() { + selectedCategories = mutableSetOf() + notifyDataSetChanged() + } + + inner class ViewHolder(private val binding: CategoryLayoutBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(item: String, isSelected: Boolean) { + binding.category.text = item + + if (isSelected) { + binding.category.setBackgroundResource(R.drawable.category_background) + binding.category.setTextColor(ContextCompat.getColor(itemView.context, R.color.white)) + } else { + binding.category.setBackgroundResource(R.drawable.category_gray_background) + binding.category.setTextColor(ContextCompat.getColor(itemView.context, R.color.search_gray)) + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = + CategoryLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ViewHolder(binding) + } + + override fun getItemCount(): Int { + return categoryList.size + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(categoryList[position], selectedCategories.contains(position)) + + holder.itemView.setOnClickListener { + if (selectedCategories.contains(holder.adapterPosition)) { + selectedCategories.remove(holder.adapterPosition) + } else { + selectedCategories.add(holder.adapterPosition) + } + notifyItemChanged(holder.adapterPosition) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/presentation/adapter/FriendRequestAdapter.kt b/app/src/main/java/com/example/everymoment/presentation/adapter/FriendRequestAdapter.kt index ee36a92..e141385 100644 --- a/app/src/main/java/com/example/everymoment/presentation/adapter/FriendRequestAdapter.kt +++ b/app/src/main/java/com/example/everymoment/presentation/adapter/FriendRequestAdapter.kt @@ -4,22 +4,26 @@ import android.app.AlertDialog import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -import com.example.everymoment.data.model.FriendRequest +import com.example.everymoment.data.repository.Member import com.example.everymoment.databinding.FriendRequestItemBinding +import com.example.everymoment.extensions.CustomDialog class FriendRequestAdapter( - private val onFriendRequest: (FriendRequest) -> Unit -) : ListAdapter( + private val activity: FragmentActivity, + private val onFriendRequest: (Member) -> Unit +) : ListAdapter( FriendRequestDiffCallback() ) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FriendRequestViewHolder { val binding = FriendRequestItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return FriendRequestViewHolder(binding, onFriendRequest) + return FriendRequestViewHolder(binding, activity, onFriendRequest) } override fun onBindViewHolder(holder: FriendRequestViewHolder, position: Int) { @@ -28,36 +32,32 @@ class FriendRequestAdapter( class FriendRequestViewHolder( private val binding: FriendRequestItemBinding, - private val onFriendRequest: (FriendRequest) -> Unit + private val activity: FragmentActivity, + private val onFriendRequest: (Member) -> Unit ) : RecyclerView.ViewHolder(binding.root) { - fun bind(user: FriendRequest) { - binding.userNickname.text = user.name + fun bind(user: Member) { + binding.userNickname.text = user.nickname binding.friendRequestButton.setOnClickListener { showFriendRequestConfirmationDialog(user) } } - private fun showFriendRequestConfirmationDialog(user: FriendRequest) { - AlertDialog.Builder(itemView.context) - .setTitle("친구 신청") - .setMessage("${user.name}님에게 친구 신청을 하시겠습니까?") - .setNegativeButton("아니오") { dialog, _ -> dialog.dismiss() } - .setPositiveButton("네") { _, _ -> - onFriendRequest(user) - binding.friendRequestButton.visibility = View.GONE - binding.requestCompletedButton.visibility = View.VISIBLE - } - .show() + private fun showFriendRequestConfirmationDialog(user: Member) { + CustomDialog("${user.nickname}님에게\n친구 신청을 하시겠습니까?", "아니오", "신청하기", onPositiveClick = { + onFriendRequest(user) + binding.friendRequestButton.visibility = View.GONE + binding.requestCompletedButton.visibility = View.VISIBLE + }).show(activity.supportFragmentManager, "CustomDialog") } } - class FriendRequestDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: FriendRequest, newItem: FriendRequest): Boolean { - return oldItem.name == newItem.name + class FriendRequestDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Member, newItem: Member): Boolean { + return oldItem.nickname == newItem.nickname } - override fun areContentsTheSame(oldItem: FriendRequest, newItem: FriendRequest): Boolean { + override fun areContentsTheSame(oldItem: Member, newItem: Member): Boolean { return oldItem == newItem } } diff --git a/app/src/main/java/com/example/everymoment/presentation/adapter/FriendRequestListAdapter.kt b/app/src/main/java/com/example/everymoment/presentation/adapter/FriendRequestListAdapter.kt new file mode 100644 index 0000000..405ec32 --- /dev/null +++ b/app/src/main/java/com/example/everymoment/presentation/adapter/FriendRequestListAdapter.kt @@ -0,0 +1,62 @@ +package com.example.everymoment.presentation.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.Toast +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.example.everymoment.data.model.FriendRequestList +import com.example.everymoment.databinding.FriendRequestListItemBinding + +class FriendRequestListAdapter( + private val onFriendRequestList: (FriendRequestList) -> Unit +) : ListAdapter( + FriendRequestListDiffCallback() +) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FriendRequestListViewHolder { + val binding = + FriendRequestListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return FriendRequestListViewHolder(binding, onFriendRequestList) + } + + override fun onBindViewHolder(holder: FriendRequestListViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + fun removeItem(position: Int) { + val currentList = currentList.toMutableList() + currentList.removeAt(position) + submitList(currentList) + } + + inner class FriendRequestListViewHolder( + private val binding: FriendRequestListItemBinding, + private val onFriendRequestList: (FriendRequestList) -> Unit + ) : RecyclerView.ViewHolder(binding.root) { + fun bind(user: FriendRequestList) { + binding.userNickname.text = user.name + + binding.friendRequestAcceptButton.setOnClickListener { + Toast.makeText(itemView.context, "${user.name}님의 친구요청을 수락했습니다.", Toast.LENGTH_SHORT).show() + removeItem(adapterPosition) + } + + binding.friendRequestRefuseButton.setOnClickListener { + Toast.makeText(itemView.context, "${user.name}님의 친구요청을 거절했습니다.", Toast.LENGTH_SHORT).show() + removeItem(adapterPosition) + } + } + } + + class FriendRequestListDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: FriendRequestList, newItem: FriendRequestList): Boolean { + return oldItem.name == newItem.name + } + + override fun areContentsTheSame(oldItem: FriendRequestList, newItem: FriendRequestList): Boolean { + return oldItem == newItem + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/presentation/adapter/FriendsListAdapter.kt b/app/src/main/java/com/example/everymoment/presentation/adapter/FriendsListAdapter.kt index 9dcadb3..d2c33d3 100644 --- a/app/src/main/java/com/example/everymoment/presentation/adapter/FriendsListAdapter.kt +++ b/app/src/main/java/com/example/everymoment/presentation/adapter/FriendsListAdapter.kt @@ -3,17 +3,24 @@ package com.example.everymoment.presentation.adapter import android.app.AlertDialog import android.view.LayoutInflater import android.view.ViewGroup +import androidx.fragment.app.FragmentActivity import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.example.everymoment.data.model.Friends import com.example.everymoment.databinding.FriendsListItemBinding +import com.example.everymoment.extensions.CustomDialog -class FriendsListAdapter(private val onDeleteFriend: (Friends) -> Unit) : ListAdapter(FriendDiffCallback()) { +class FriendsListAdapter( + private val activity: FragmentActivity, + private val onDeleteFriend: (Friends) -> Unit +) : + ListAdapter(FriendDiffCallback()) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FriendViewHolder { - val binding = FriendsListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return FriendViewHolder(binding, onDeleteFriend) + val binding = + FriendsListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return FriendViewHolder(binding, activity, onDeleteFriend) } override fun onBindViewHolder(holder: FriendViewHolder, position: Int) { @@ -22,6 +29,7 @@ class FriendsListAdapter(private val onDeleteFriend: (Friends) -> Unit) : ListAd class FriendViewHolder( private val binding: FriendsListItemBinding, + private val activity: FragmentActivity, private val onDeleteFriend: (Friends) -> Unit ) : RecyclerView.ViewHolder(binding.root) { fun bind(friends: Friends) { @@ -34,12 +42,11 @@ class FriendsListAdapter(private val onDeleteFriend: (Friends) -> Unit) : ListAd } private fun showDeleteConfirmationDialog(friends: Friends) { - AlertDialog.Builder(itemView.context) - .setTitle("친구 삭제") - .setMessage("${friends.name}님을 친구에서 삭제하시겠습니까?") - .setNegativeButton("아니오") { dialog, _ -> dialog.dismiss() } - .setPositiveButton("네") { _, _ -> onDeleteFriend(friends) } - .show() + CustomDialog( + "${friends.name}님을\n친구에서 삭제하시겠습니까?", + "아니오", + "삭제하기", + onPositiveClick = { onDeleteFriend(friends) }).show(activity.supportFragmentManager, "CustomDialog") } } diff --git a/app/src/main/java/com/example/everymoment/presentation/adapter/SharedFriendDiaryListAdapter.kt b/app/src/main/java/com/example/everymoment/presentation/adapter/SharedFriendDiaryListAdapter.kt new file mode 100644 index 0000000..e79aa8b --- /dev/null +++ b/app/src/main/java/com/example/everymoment/presentation/adapter/SharedFriendDiaryListAdapter.kt @@ -0,0 +1,39 @@ +package com.example.everymoment.presentation.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.example.everymoment.data.repository.Diary +import com.example.everymoment.databinding.ShareItemBinding + +class SharedFriendDiaryListAdapter : ListAdapter( + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Diary, newItem: Diary): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: Diary, newItem: Diary): Boolean { + return oldItem == newItem + } + } +) { + inner class ViewHolder(private val binding: ShareItemBinding) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: Diary) { + binding.timeText.text = item.createAt + binding.locationNameText.text = item.locationName + binding.addressText.text = item.address + + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = ShareItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ViewHolder(binding) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(getItem(position)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/presentation/adapter/SharedFriendListAdapter.kt b/app/src/main/java/com/example/everymoment/presentation/adapter/SharedFriendListAdapter.kt new file mode 100644 index 0000000..720d084 --- /dev/null +++ b/app/src/main/java/com/example/everymoment/presentation/adapter/SharedFriendListAdapter.kt @@ -0,0 +1,41 @@ +package com.example.everymoment.presentation.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.example.everymoment.data.repository.Member +import com.example.everymoment.databinding.FriendItemBinding + +class SharedFriendListAdapter : ListAdapter( + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Member, newItem: Member): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: Member, newItem: Member): Boolean { + return oldItem == newItem + } + } +) { + inner class SharedFriendListViewHolder(private val binding: FriendItemBinding) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: Member) { + binding.friendName.text = item.nickname + // binding.friendImage.setImageResource(item.profileImageUrl) + + binding.friendContainer.setOnClickListener { + + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SharedFriendListViewHolder { + val binding = FriendItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return SharedFriendListViewHolder(binding) + } + + override fun onBindViewHolder(holder: SharedFriendListViewHolder, position: Int) { + holder.bind(getItem(position)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/presentation/adapter/TimelineAdapter.kt b/app/src/main/java/com/example/everymoment/presentation/adapter/TimelineAdapter.kt index e40a260..c950e2e 100644 --- a/app/src/main/java/com/example/everymoment/presentation/adapter/TimelineAdapter.kt +++ b/app/src/main/java/com/example/everymoment/presentation/adapter/TimelineAdapter.kt @@ -2,120 +2,131 @@ package com.example.everymoment.presentation.adapter import android.content.Context import android.view.LayoutInflater -import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.PopupMenu import android.widget.Toast import androidx.appcompat.app.AlertDialog -import androidx.core.view.isGone +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.example.everymoment.R -import com.example.everymoment.data.model.Timeline +import com.example.everymoment.data.repository.Diary import com.example.everymoment.databinding.TimelineItemBinding import com.example.everymoment.extensions.EmotionPopup import com.example.everymoment.extensions.ToPxConverter +import com.example.everymoment.presentation.viewModel.TimelineViewModel -class TimelineAdapter(private val timelineList: MutableList): RecyclerView.Adapter() { - - inner class Holder(private val binding: TimelineItemBinding): RecyclerView.ViewHolder(binding.root) { - val time = binding.timeText - val buildingName = binding.locationNameText - val address = binding.addressText - - val addEmotion = binding.addEmotion - val emotion = binding.emotion - - val diaryContainer = binding.detailedDiaryContainer - - val editButton = binding.editIcon - val bookmarkButton = binding.bookmarkIcon - val shareButton = binding.shareIcon - val deleteButton = binding.deleteIcon - } +class TimelineAdapter(private val viewModel: TimelineViewModel) : ListAdapter( + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Diary, newItem: Diary): Boolean { + return oldItem.id == newItem.id + } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { - val binding = TimelineItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return Holder(binding) + override fun areContentsTheSame(oldItem: Diary, newItem: Diary): Boolean { + return oldItem == newItem + } } +) { + inner class TimelineViewHolder(private val binding: TimelineItemBinding) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: Diary) { + binding.timeText.text = item.createAt.substring(11, 16) + binding.locationNameText.text = item.locationName + binding.addressText.text = item.address + + val emotionPopupManager = EmotionPopup(binding.root.context) { selectedEmotion -> + binding.emotion.text = selectedEmotion.getEmotionUnicode() + binding.addEmotion.visibility = View.GONE + binding.emotion.visibility = View.VISIBLE + } - override fun getItemCount(): Int = timelineList.size - - override fun onBindViewHolder(holder: Holder, position: Int) { - var isBookmarked = false - var isShared = false - - holder.time.text = timelineList[position].time - holder.buildingName.text = timelineList[position].buildingName - holder.address.text = timelineList[position].address + binding.addEmotion.setOnClickListener { + emotionPopupManager.showEmotionsPopup(it, ToPxConverter().dpToPx(10)) + } - val emotionPopupManager = EmotionPopup(holder.itemView.context) { selectedEmotion -> - holder.emotion.text = selectedEmotion.getEmotionUnicode() - holder.addEmotion.visibility = View.GONE - holder.emotion.visibility = View.VISIBLE - } + binding.emotion.setOnClickListener { + emotionPopupManager.showEmotionsPopup(it, ToPxConverter().dpToPx(10)) + } - holder.addEmotion.setOnClickListener { - emotionPopupManager.showEmotionsPopup(holder.addEmotion, ToPxConverter().dpToPx(10)) - } + var isBookmarked = item.bookmark + updateBookmarkIcon(isBookmarked) - holder.emotion.setOnClickListener { - emotionPopupManager.showEmotionsPopup(holder.emotion, ToPxConverter().dpToPx(10)) - } + binding.bookmarkIcon.setOnClickListener { + isBookmarked = !isBookmarked + updateBookmarkIcon(isBookmarked) + viewModel.updateBookmarkStatus(item.id) + binding.root.context.showToast( + if (isBookmarked) R.string.add_bookmark else R.string.remove_bookmark + ) + } - holder.bookmarkButton.setOnClickListener { - if (isBookmarked) { - holder.bookmarkButton.setImageResource(R.drawable.baseline_bookmark_border_24) - holder.itemView.context.showToast(holder.itemView.context.getString(R.string.remove_bookmark)) - } else { - holder.bookmarkButton.setImageResource(R.drawable.baseline_bookmark_24) - holder.itemView.context.showToast(holder.itemView.context.getString(R.string.add_bookmark)) + var isShared = item.public + updateShareIcon(isShared) + + binding.shareIcon.setOnClickListener { + isShared = !isShared + updateShareIcon(isShared) + viewModel.updateShareStatus(item.id) + binding.root.context.showToast( + if (isShared) R.string.is_public else R.string.is_private + ) + // SERVER : patch } - isBookmarked = !isBookmarked - } + // 상세 일기 표시 여부 + // binding.detailedDiaryContainer.isGone = !item.thumbnailResponse + + binding.deleteIcon.setOnClickListener { + AlertDialog.Builder(binding.root.context) + .setMessage("삭제하시겠습니까?") + .setPositiveButton("삭제") { _, _ -> + removeItem(adapterPosition) + viewModel.deleteDiary(item.id) + } + .setNegativeButton("취소", null) + .show() + } - holder.shareButton.setOnClickListener { - if (isShared) { - holder.shareButton.setImageResource(R.drawable.ic_share) - holder.itemView.context.showToast(holder.itemView.context.getString(R.string.is_private)) - } else { - holder.shareButton.setImageResource(R.drawable.ic_is_shared) - holder.itemView.context.showToast(holder.itemView.context.getString(R.string.is_public)) + binding.editIcon.setOnClickListener { + val popupMenu = PopupMenu(it.context, it) + popupMenu.menuInflater.inflate(R.menu.location_candidate_menu, popupMenu.menu) + popupMenu.setOnMenuItemClickListener { menuItem -> + binding.locationNameText.text = menuItem.title + true + } + popupMenu.show() } + } - isShared = !isShared + private fun updateBookmarkIcon(isBookmarked: Boolean) { + binding.bookmarkIcon.setImageResource( + if (isBookmarked) R.drawable.baseline_bookmark_24 else R.drawable.bookmark_26dp_5f6368_fill0_wght300_grad0_opsz24 + ) } - holder.diaryContainer.isGone = !timelineList[position].isDetailedDiary + private fun updateShareIcon(isShared: Boolean) { + binding.shareIcon.setImageResource( + if (isShared) R.drawable.ic_is_shared else R.drawable.share_26dp_5f6368_fill0_wght300_grad0_opsz24 + ) + } - holder.deleteButton.setOnClickListener { - val builder = AlertDialog.Builder(holder.itemView.context) - builder.setMessage("삭제하시겠습니까?") - .setPositiveButton("삭제") { dialog, id -> - timelineList.removeAt(position) - notifyItemRemoved(position) - notifyItemRangeChanged(position, timelineList.size) - } - .setNegativeButton("취소") { dialog, id -> - dialog.dismiss() - } - builder.create().show() + private fun removeItem(position: Int) { + val newList = currentList.toMutableList() + newList.removeAt(position) + submitList(newList) } + } - holder.editButton.setOnClickListener { - val popupMenu = PopupMenu(holder.editButton.context, holder.editButton) - popupMenu.menuInflater.inflate(R.menu.location_candidate_menu, popupMenu.menu) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TimelineViewHolder { + val binding = TimelineItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return TimelineViewHolder(binding) + } - popupMenu.setOnMenuItemClickListener { item: MenuItem -> - holder.buildingName.text = item.title.toString() - true - } - popupMenu.show() - } + override fun onBindViewHolder(holder: TimelineViewHolder, position: Int) { + holder.bind(getItem(position)) } - private fun Context.showToast(message: String) { - Toast.makeText(this, message, Toast.LENGTH_SHORT).show() + private fun Context.showToast(messageResId: Int) { + Toast.makeText(this, getString(messageResId), Toast.LENGTH_SHORT).show() } } \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/presentation/view/CalendarViewFragment.kt b/app/src/main/java/com/example/everymoment/presentation/view/CalendarViewFragment.kt deleted file mode 100644 index 245d39f..0000000 --- a/app/src/main/java/com/example/everymoment/presentation/view/CalendarViewFragment.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.example.everymoment.presentation.view - -import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import com.example.everymoment.R -import com.example.everymoment.databinding.FragmentCalendarViewBinding -import com.kakao.sdk.common.util.Utility - -class CalendarViewFragment : Fragment() { - - private lateinit var binding: FragmentCalendarViewBinding - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - binding = FragmentCalendarViewBinding.inflate(inflater, container, false) - Log.d("testt", "keyhash : ${Utility.getKeyHash(requireContext())}") - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.calendarAlarm.setOnClickListener { - requireActivity().supportFragmentManager.beginTransaction().apply { - replace(R.id.fragment_container, NotificationFragment()) - addToBackStack(null) - commit() - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/presentation/view/main/CalendarViewFragment.kt b/app/src/main/java/com/example/everymoment/presentation/view/main/CalendarViewFragment.kt new file mode 100644 index 0000000..66d595a --- /dev/null +++ b/app/src/main/java/com/example/everymoment/presentation/view/main/CalendarViewFragment.kt @@ -0,0 +1,68 @@ +package com.example.everymoment.presentation.view.main + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.example.everymoment.R +import com.example.everymoment.databinding.FragmentCalendarViewBinding +import com.example.everymoment.presentation.view.sub.NotificationFragment +import com.kakao.sdk.common.util.Utility +import com.prolificinteractive.materialcalendarview.CalendarDay +import com.prolificinteractive.materialcalendarview.MaterialCalendarView +import com.prolificinteractive.materialcalendarview.OnDateSelectedListener +import org.threeten.bp.format.DateTimeFormatter + +class CalendarViewFragment : Fragment(), OnDateSelectedListener { + + private lateinit var binding: FragmentCalendarViewBinding + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentCalendarViewBinding.inflate(inflater, container, false) + Log.d("testt", "keyhash : ${Utility.getKeyHash(requireContext())}") + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.calendarAlarm.setOnClickListener { + requireActivity().supportFragmentManager.beginTransaction().apply { + replace(R.id.fragment_container, NotificationFragment()) + addToBackStack(null) + commit() + } + } + + binding.calendarView.setOnDateChangedListener(this) + } + + override fun onDateSelected( + widget: MaterialCalendarView, + date: CalendarDay, + selected: Boolean + ) { + if (selected) { + // 선택된 날짜를 원하는 형식으로 포맷팅 + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + val formattedDate = date.date.format(formatter) + + // TodayLogFragment로 이동하면서 선택한 날짜 전달 + val todayLogFragment = TodayLogFragment().apply { + arguments = Bundle().apply { + putString("selected_date", formattedDate) + } + } + + requireActivity().supportFragmentManager.beginTransaction().apply { + replace(R.id.fragment_container, todayLogFragment) + addToBackStack(null) + commit() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/presentation/view/KakaoLoginActivity.kt b/app/src/main/java/com/example/everymoment/presentation/view/main/KakaoLoginActivity.kt similarity index 97% rename from app/src/main/java/com/example/everymoment/presentation/view/KakaoLoginActivity.kt rename to app/src/main/java/com/example/everymoment/presentation/view/main/KakaoLoginActivity.kt index 99fbd59..688252b 100644 --- a/app/src/main/java/com/example/everymoment/presentation/view/KakaoLoginActivity.kt +++ b/app/src/main/java/com/example/everymoment/presentation/view/main/KakaoLoginActivity.kt @@ -1,4 +1,4 @@ -package com.example.everymoment.presentation.view +package com.example.everymoment.presentation.view.main import android.content.Intent import android.os.Bundle diff --git a/app/src/main/java/com/example/everymoment/presentation/view/MainActivity.kt b/app/src/main/java/com/example/everymoment/presentation/view/main/MainActivity.kt similarity index 67% rename from app/src/main/java/com/example/everymoment/presentation/view/MainActivity.kt rename to app/src/main/java/com/example/everymoment/presentation/view/main/MainActivity.kt index 19774f2..a729582 100644 --- a/app/src/main/java/com/example/everymoment/presentation/view/MainActivity.kt +++ b/app/src/main/java/com/example/everymoment/presentation/view/main/MainActivity.kt @@ -1,10 +1,16 @@ -package com.example.everymoment.presentation.view +package com.example.everymoment.presentation.view.main import android.os.Bundle +import android.util.Log import android.view.View import androidx.appcompat.app.AppCompatActivity import com.example.everymoment.R import com.example.everymoment.databinding.ActivityMainBinding +import com.example.everymoment.presentation.view.main.search.SearchFragment +import com.google.firebase.ktx.Firebase +import com.google.firebase.remoteconfig.FirebaseRemoteConfig +import com.google.firebase.remoteconfig.ktx.remoteConfig +import com.google.firebase.remoteconfig.ktx.remoteConfigSettings class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding @@ -14,6 +20,13 @@ class MainActivity : AppCompatActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) + val remoteConfig: FirebaseRemoteConfig = Firebase.remoteConfig + val configSettings = remoteConfigSettings { + minimumFetchIntervalInSeconds = 0 + } + remoteConfig.setConfigSettingsAsync(configSettings) + fetchValues() + if (savedInstanceState == null) { val fragment = TodayLogFragment() supportFragmentManager.beginTransaction() @@ -32,6 +45,22 @@ class MainActivity : AppCompatActivity() { binding.bottomNavigationView.visibility = View.VISIBLE } + private fun fetchValues() { + Firebase.remoteConfig.fetchAndActivate().addOnCompleteListener { + if (it.isSuccessful) { + val serviceState = Firebase.remoteConfig.getString("serviceState") + val serviceMessage = Firebase.remoteConfig.getString("serviceMessage") + Log.d("testt", "state: $serviceState") + Log.d("testt", "serviceMessage: $serviceMessage") + if (serviceState == "ON_SERVICE") { + Log.d("testt", "온서비스") + } else { + Log.d("testt", "온서비스아님") + } + } + } + } + private fun initNavigationBar() { binding.bottomNavigationView.run { @@ -46,7 +75,7 @@ class MainActivity : AppCompatActivity() { } R.id.feed -> { - val fragment = PostFragment() + val fragment = ShareViewFragment() supportFragmentManager.beginTransaction() .replace(R.id.fragment_container, fragment) .commit() diff --git a/app/src/main/java/com/example/everymoment/presentation/view/SettingFragment.kt b/app/src/main/java/com/example/everymoment/presentation/view/main/SettingFragment.kt similarity index 85% rename from app/src/main/java/com/example/everymoment/presentation/view/SettingFragment.kt rename to app/src/main/java/com/example/everymoment/presentation/view/main/SettingFragment.kt index 1271f04..6f0dea7 100644 --- a/app/src/main/java/com/example/everymoment/presentation/view/SettingFragment.kt +++ b/app/src/main/java/com/example/everymoment/presentation/view/main/SettingFragment.kt @@ -1,4 +1,4 @@ -package com.example.everymoment.presentation.view +package com.example.everymoment.presentation.view.main import android.net.Uri import android.os.Bundle @@ -13,7 +13,6 @@ import com.example.everymoment.databinding.FragmentSettingBinding import com.example.everymoment.extensions.CustomDialog import com.example.everymoment.extensions.CustomEditDialog import com.example.everymoment.extensions.GalleryUtil -import com.google.android.material.dialog.MaterialAlertDialogBuilder class SettingFragment : Fragment() { @@ -50,17 +49,33 @@ class SettingFragment : Fragment() { profileNameDialog.show(requireActivity().supportFragmentManager, "CustomDialog") } - binding.notificationToggle.setOnCheckedChangeListener { _, isChecked -> + binding.autoNotificationToggle.setOnCheckedChangeListener { _, isChecked -> if (isChecked) { Toast.makeText( requireContext(), - resources.getString(R.string.notification_isChecked), + resources.getString(R.string.auto_notification_isChecked), Toast.LENGTH_SHORT ).show() } else { Toast.makeText( requireContext(), - resources.getString(R.string.notification_isUnChecked), + resources.getString(R.string.auto_notification_isUnChecked), + Toast.LENGTH_SHORT + ).show() + } + } + + binding.pushNotificationToggle.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + Toast.makeText( + requireContext(), + resources.getString(R.string.push_notification_isChecked), + Toast.LENGTH_SHORT + ).show() + } else { + Toast.makeText( + requireContext(), + resources.getString(R.string.push_notification_isUnChecked), Toast.LENGTH_SHORT ).show() } diff --git a/app/src/main/java/com/example/everymoment/presentation/view/main/ShareViewFragment.kt b/app/src/main/java/com/example/everymoment/presentation/view/main/ShareViewFragment.kt new file mode 100644 index 0000000..b58f805 --- /dev/null +++ b/app/src/main/java/com/example/everymoment/presentation/view/main/ShareViewFragment.kt @@ -0,0 +1,69 @@ +package com.example.everymoment.presentation.view.main + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import com.example.everymoment.R +import com.example.everymoment.data.repository.Diary +import com.example.everymoment.data.repository.Member +import com.example.everymoment.data.repository.Thumbnail +import com.example.everymoment.databinding.FragmentShareViewBinding +import com.example.everymoment.presentation.adapter.SharedFriendDiaryListAdapter +import com.example.everymoment.presentation.adapter.SharedFriendListAdapter +import com.example.everymoment.presentation.view.sub.friends.FriendsListFragment + +class ShareViewFragment : Fragment() { + private lateinit var binding: FragmentShareViewBinding + + private val friendAdapter = SharedFriendListAdapter() + private val diaryAdapter = SharedFriendDiaryListAdapter() + private var friendList: MutableList = mutableListOf() + private var diaryList: MutableList = mutableListOf() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentShareViewBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupRecyclerView() + + // dummyData1 + friendList.add(Member(1, "url", "춘식이")) + friendList.add(Member(2, "url", "제이지")) + friendList.add(Member(3, "url", "프로도")) + friendList.add(Member(4, "url", "네오")) + friendList.add(Member(5, "url", "피치")) + friendAdapter.submitList(friendList) + friendAdapter.notifyDataSetChanged() + + // dummyData2 + diaryList.add(Diary(1, "춘천 한림대", "강원도 춘천시", "Happy", Thumbnail(1, "url"), "Hello", "2024-05-06", false, true)) + diaryList.add(Diary(2, "춘천 강원대", "강원도 춘천시", "Happy", Thumbnail(1, "url"), "Hello", "2024-05-06", false, true)) + diaryAdapter.submitList(diaryList) + diaryAdapter.notifyDataSetChanged() + + binding.friendListIcon.setOnClickListener { + requireActivity().supportFragmentManager.beginTransaction().apply { + replace(R.id.fragment_container, FriendsListFragment()) + addToBackStack(null) + commit() + } + } + } + + private fun setupRecyclerView() { + binding.friendList.adapter = friendAdapter + binding.timeLineRecyclerView.adapter = diaryAdapter + + binding.friendList.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false) + binding.timeLineRecyclerView.layoutManager = LinearLayoutManager(requireContext()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/presentation/view/TodayLogFragment.kt b/app/src/main/java/com/example/everymoment/presentation/view/main/TodayLogFragment.kt similarity index 67% rename from app/src/main/java/com/example/everymoment/presentation/view/TodayLogFragment.kt rename to app/src/main/java/com/example/everymoment/presentation/view/main/TodayLogFragment.kt index 5acf6ec..8dc0d98 100644 --- a/app/src/main/java/com/example/everymoment/presentation/view/TodayLogFragment.kt +++ b/app/src/main/java/com/example/everymoment/presentation/view/main/TodayLogFragment.kt @@ -1,4 +1,4 @@ -package com.example.everymoment.presentation.view +package com.example.everymoment.presentation.view.main import android.content.Intent import android.os.Build @@ -6,17 +6,25 @@ import android.os.Bundle import androidx.core.content.ContextCompat import android.Manifest import android.content.pm.PackageManager +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import com.example.everymoment.LocationService import com.example.everymoment.R -import com.example.everymoment.data.model.Timeline +import com.example.everymoment.data.repository.DiaryRepository import com.example.everymoment.databinding.FragmentTodayLogBinding import com.example.everymoment.presentation.adapter.TimelineAdapter +import com.example.everymoment.presentation.view.sub.NotificationFragment +import com.example.everymoment.presentation.viewModel.TimelineViewModel +import com.example.everymoment.presentation.viewModel.TimelineViewModelFactory +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale class TodayLogFragment : Fragment() { @@ -38,7 +46,9 @@ class TodayLogFragment : Fragment() { } private lateinit var binding: FragmentTodayLogBinding - + private lateinit var viewModel: TimelineViewModel + private val diaryRepository = DiaryRepository() + private val calendar = Calendar.getInstance() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -50,16 +60,52 @@ class TodayLogFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val timelineList: MutableList = mutableListOf() - // 리스트 예시 - timelineList.add(Timeline("오전 10:00", "빽다방 강원대점", "강원도 춘천시 충열로", "😢", true)) - timelineList.add(Timeline("오후 12:00", "천지관", "강원도 춘천시 충열로", "😢", false)) + viewModel = ViewModelProvider(this, TimelineViewModelFactory(diaryRepository)).get( + TimelineViewModel::class.java + ) + + val TodayDate = arguments?.getString("selected_date") + Log.d("TodayDate", "Selected date: $TodayDate") checkPermissions() - setupRecyclerView(timelineList) + updateDateText() + + val adapter = TimelineAdapter(viewModel) + setupRecyclerView(adapter) + observeViewModel(adapter) + + val initialDate = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) + + viewModel.fetchDiaries(initialDate) + binding.notification.setOnClickListener { navigateToNotificationFragment() } + + binding.nextDate.setOnClickListener { + calendar.add(Calendar.DATE, 1) + updateDateText() + val currentDate = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) + viewModel.fetchDiaries(currentDate) + } + + binding.prevDate.setOnClickListener { + calendar.add(Calendar.DATE, -1) + updateDateText() + val currentDate = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) + viewModel.fetchDiaries(currentDate) + } + } + + private fun observeViewModel(adapter: TimelineAdapter) { + viewModel.diaries.observe(viewLifecycleOwner) { diaryList -> + adapter.submitList(diaryList) + } + } + + private fun updateDateText() { + val formattedDate = SimpleDateFormat("M월 d일 (E)", Locale("ko", "KR")).format(calendar.time) + binding.currentDate.text = formattedDate } private fun navigateToNotificationFragment() { @@ -70,8 +116,8 @@ class TodayLogFragment : Fragment() { } } - private fun setupRecyclerView(timelineList: MutableList) { - binding.timeLineRecyclerView.adapter = TimelineAdapter(timelineList) + private fun setupRecyclerView(adapter: TimelineAdapter) { + binding.timeLineRecyclerView.adapter = adapter binding.timeLineRecyclerView.layoutManager = LinearLayoutManager(requireContext()) } diff --git a/app/src/main/java/com/example/everymoment/presentation/view/main/search/SearchFilterDialogFragment.kt b/app/src/main/java/com/example/everymoment/presentation/view/main/search/SearchFilterDialogFragment.kt new file mode 100644 index 0000000..6ccda0b --- /dev/null +++ b/app/src/main/java/com/example/everymoment/presentation/view/main/search/SearchFilterDialogFragment.kt @@ -0,0 +1,203 @@ +package com.example.everymoment.presentation.view.main.search + +import android.app.DatePickerDialog +import android.icu.util.Calendar +import android.os.Build +import android.os.Bundle +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.GridLayout +import android.widget.TextView +import android.widget.Toast +import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.GridLayoutManager +import com.example.everymoment.R +import com.example.everymoment.data.model.Emotions +import com.example.everymoment.databinding.FragmentSearchFilterDialogBinding +import com.example.everymoment.presentation.adapter.CategoryAdapter +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +class SearchFilterDialogFragment : BottomSheetDialogFragment() { + + private lateinit var binding: FragmentSearchFilterDialogBinding + private lateinit var categoryAdapter: CategoryAdapter + private var checkedBookmark: Boolean = false + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentSearchFilterDialogBinding.inflate(inflater, container, false) + return binding.root + } + + @RequiresApi(Build.VERSION_CODES.O) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val bottomSheetBehavior = BottomSheetBehavior.from(view.parent as View) + bottomSheetBehavior.isDraggable = false + + setEmoji() + setCategories() + + binding.bookmark.setOnClickListener { + if (!checkedBookmark) { + binding.bookmarkIcon.setImageResource(R.drawable.baseline_bookmark_24) + binding.bookmarkDesc.setTextColor( + ContextCompat.getColor( + requireContext(), + R.color.primary_color + ) + ) + } else { + binding.bookmarkIcon.setImageResource(R.drawable.gray_border_bookmark) + binding.bookmarkDesc.setTextColor( + ContextCompat.getColor( + requireContext(), + R.color.search_gray + ) + ) + } + checkedBookmark = !checkedBookmark + } + + binding.startDate.setOnClickListener { + showCalendarDialog(binding.startDate) { + if (binding.startDate.text.isNotEmpty()) { + binding.startDate.setBackgroundResource(R.drawable.search_filter_date_background) + }} + } + + binding.endDate.setOnClickListener { + showCalendarDialog(binding.endDate) { + if (binding.endDate.text.isNotEmpty()) { + binding.endDate.setBackgroundResource(R.drawable.search_filter_date_background) + }} + } + + binding.reset.setOnClickListener { + resetSelections() + } + + binding.apply.setOnClickListener { + if (binding.startDate.text.isNullOrEmpty() && binding.endDate.text.isNotEmpty()) { + makeToast(resources.getString(R.string.check_start_date)) + } else if (binding.startDate.text.isNotEmpty() && binding.endDate.text.isNullOrEmpty()) { + makeToast(resources.getString(R.string.check_end_date)) + } else if (binding.startDate.text.isNotEmpty() && binding.endDate.text.isNotEmpty()) { + if (!checkValidTerm()) { + makeToast(resources.getString(R.string.invalid_term)) + } + } else { + dismiss() + } + } + + } + + private fun makeToast(string: String) { + Toast.makeText( + context, + string, + Toast.LENGTH_SHORT + ).show() + } + + private fun setCategories() { + val gridLayoutManager = GridLayoutManager(requireContext(), 3) + binding.categoryRcv.layoutManager = gridLayoutManager + categoryAdapter = CategoryAdapter() + binding.categoryRcv.adapter = categoryAdapter + + if (categoryAdapter.itemCount != 0) { + binding.noCategoryText.visibility = View.GONE + binding.categoryRcv.visibility = View.VISIBLE + } + } + + @RequiresApi(Build.VERSION_CODES.O) + fun String.toLocalDate(): LocalDate { + val formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd") + return LocalDate.parse(this, formatter) + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun checkValidTerm(): Boolean { + val startDate = binding.startDate.text.toString().toLocalDate() + val endDate = binding.endDate.text.toString().toLocalDate() + return !startDate.isAfter(endDate) + } + + private fun resetSelections() { + checkedBookmark = false + binding.bookmarkIcon.setImageResource(R.drawable.gray_border_bookmark) + binding.bookmarkDesc.setTextColor( + ContextCompat.getColor( + requireContext(), + R.color.search_gray + ) + ) + + binding.happy.isChecked = false + binding.sad.isChecked = false + binding.insensitive.isChecked = false + binding.angry.isChecked = false + binding.confounded.isChecked = false + + binding.startDate.text = "" + binding.startDate.setBackgroundResource(R.drawable.search_filter_date_gray_background) + binding.endDate.text = "" + binding.endDate.setBackgroundResource(R.drawable.search_filter_date_gray_background) + + categoryAdapter.resetSelected() + } + + private fun changeDateBackground(input: ()->Unit) { + input.invoke() + } + + private fun showCalendarDialog(textView: TextView, checkInput: ()->Unit) { + val calendar = Calendar.getInstance() + val year = calendar.get(Calendar.YEAR) + val month = calendar.get(Calendar.MONTH) + val day = calendar.get(Calendar.DAY_OF_MONTH) + context?.let { context -> + DatePickerDialog(context, R.style.CustomDatePicker, { _, selectedYear, selectedMonth, selectedDay -> + val formattedDate = + String.format("%04d.%02d.%02d", selectedYear, selectedMonth + 1, selectedDay) + textView.text = formattedDate + changeDateBackground(checkInput) + }, year, month, day).show() + } + } + + private fun setEmoji() { + binding.happy.text = Emotions.HAPPY.getEmotionUnicode() + binding.happy.textOn = Emotions.HAPPY.getEmotionUnicode() + binding.happy.textOff = Emotions.HAPPY.getEmotionUnicode() + binding.sad.text = Emotions.SAD.getEmotionUnicode() + binding.sad.textOn = Emotions.SAD.getEmotionUnicode() + binding.sad.textOff = Emotions.SAD.getEmotionUnicode() + binding.insensitive.text = Emotions.INSENSITIVE.getEmotionUnicode() + binding.insensitive.textOn = Emotions.INSENSITIVE.getEmotionUnicode() + binding.insensitive.textOff = Emotions.INSENSITIVE.getEmotionUnicode() + binding.angry.text = Emotions.ANGRY.getEmotionUnicode() + binding.angry.textOn = Emotions.ANGRY.getEmotionUnicode() + binding.angry.textOff = Emotions.ANGRY.getEmotionUnicode() + binding.confounded.text = Emotions.CONFOUNDED.getEmotionUnicode() + binding.confounded.textOn = Emotions.CONFOUNDED.getEmotionUnicode() + binding.confounded.textOff = Emotions.CONFOUNDED.getEmotionUnicode() + } + + companion object { + const val TAG = "SearchFilterBottomSheet" + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/presentation/view/SearchFragment.kt b/app/src/main/java/com/example/everymoment/presentation/view/main/search/SearchFragment.kt similarity index 78% rename from app/src/main/java/com/example/everymoment/presentation/view/SearchFragment.kt rename to app/src/main/java/com/example/everymoment/presentation/view/main/search/SearchFragment.kt index e9c4b8b..0c494d3 100644 --- a/app/src/main/java/com/example/everymoment/presentation/view/SearchFragment.kt +++ b/app/src/main/java/com/example/everymoment/presentation/view/main/search/SearchFragment.kt @@ -1,16 +1,16 @@ -package com.example.everymoment.presentation.view +package com.example.everymoment.presentation.view.main.search import android.os.Bundle import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.example.everymoment.R import com.example.everymoment.databinding.FragmentSearchBinding class SearchFragment : Fragment() { private lateinit var binding: FragmentSearchBinding + private lateinit var filterBottomSheet: SearchFilterDialogFragment override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -34,11 +34,11 @@ class SearchFragment : Fragment() { } private fun setFilterSheet() { - + filterBottomSheet = SearchFilterDialogFragment() } private fun showFilterSheet() { - + filterBottomSheet.show(parentFragmentManager, SearchFilterDialogFragment.TAG) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/presentation/view/NotificationFragment.kt b/app/src/main/java/com/example/everymoment/presentation/view/sub/NotificationFragment.kt similarity index 94% rename from app/src/main/java/com/example/everymoment/presentation/view/NotificationFragment.kt rename to app/src/main/java/com/example/everymoment/presentation/view/sub/NotificationFragment.kt index 0b38dab..efb6c55 100644 --- a/app/src/main/java/com/example/everymoment/presentation/view/NotificationFragment.kt +++ b/app/src/main/java/com/example/everymoment/presentation/view/sub/NotificationFragment.kt @@ -1,4 +1,4 @@ -package com.example.everymoment.presentation.view +package com.example.everymoment.presentation.view.sub import android.os.Bundle import androidx.fragment.app.Fragment diff --git a/app/src/main/java/com/example/everymoment/presentation/view/PostFragment.kt b/app/src/main/java/com/example/everymoment/presentation/view/sub/PostFragment.kt similarity index 96% rename from app/src/main/java/com/example/everymoment/presentation/view/PostFragment.kt rename to app/src/main/java/com/example/everymoment/presentation/view/sub/PostFragment.kt index bbec736..b047f9c 100644 --- a/app/src/main/java/com/example/everymoment/presentation/view/PostFragment.kt +++ b/app/src/main/java/com/example/everymoment/presentation/view/sub/PostFragment.kt @@ -1,4 +1,4 @@ -package com.example.everymoment.presentation.view +package com.example.everymoment.presentation.view.sub import android.content.Context import android.os.Bundle @@ -8,7 +8,6 @@ import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager import androidx.recyclerview.widget.LinearLayoutManager -import com.example.everymoment.R import com.example.everymoment.databinding.FragmentPostBinding import com.example.everymoment.presentation.adapter.PostAdapter diff --git a/app/src/main/java/com/example/everymoment/presentation/view/DiaryEditFragment.kt b/app/src/main/java/com/example/everymoment/presentation/view/sub/diary/DiaryEditFragment.kt similarity index 90% rename from app/src/main/java/com/example/everymoment/presentation/view/DiaryEditFragment.kt rename to app/src/main/java/com/example/everymoment/presentation/view/sub/diary/DiaryEditFragment.kt index fac7fa7..bb5a8a8 100644 --- a/app/src/main/java/com/example/everymoment/presentation/view/DiaryEditFragment.kt +++ b/app/src/main/java/com/example/everymoment/presentation/view/sub/diary/DiaryEditFragment.kt @@ -1,16 +1,18 @@ -package com.example.everymoment.presentation.view +package com.example.everymoment.presentation.view.sub.diary import android.os.Bundle import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ImageView import com.example.everymoment.databinding.FragmentDiaryEditBinding import com.example.everymoment.extensions.Bookmarks import com.example.everymoment.extensions.CategoryPopup import com.example.everymoment.extensions.EmotionPopup import com.example.everymoment.extensions.GalleryUtil import com.example.everymoment.extensions.ToPxConverter +import com.example.everymoment.presentation.view.main.MainActivity class DiaryEditFragment : Fragment() { @@ -52,16 +54,19 @@ class DiaryEditFragment : Fragment() { binding.image1.setOnClickListener { galleryUtil.openGallery(onImageSelected = { + binding.image1.scaleType = ImageView.ScaleType.CENTER_CROP binding.image1.setImageURI(it) + imagesArray[0] = true + binding.image2.visibility = View.VISIBLE }) - imagesArray[0] = true } binding.image2.setOnClickListener { galleryUtil.openGallery(onImageSelected = { + binding.image2.scaleType = ImageView.ScaleType.CENTER_CROP binding.image2.setImageURI(it) + imagesArray[1] = true }) - imagesArray[1] = true } binding.addCategory.setOnClickListener { diff --git a/app/src/main/java/com/example/everymoment/presentation/view/DiaryReadFragment.kt b/app/src/main/java/com/example/everymoment/presentation/view/sub/diary/DiaryReadFragment.kt similarity index 95% rename from app/src/main/java/com/example/everymoment/presentation/view/DiaryReadFragment.kt rename to app/src/main/java/com/example/everymoment/presentation/view/sub/diary/DiaryReadFragment.kt index 6d6eca2..e679f3d 100644 --- a/app/src/main/java/com/example/everymoment/presentation/view/DiaryReadFragment.kt +++ b/app/src/main/java/com/example/everymoment/presentation/view/sub/diary/DiaryReadFragment.kt @@ -1,4 +1,4 @@ -package com.example.everymoment.presentation.view +package com.example.everymoment.presentation.view.sub.diary import android.os.Bundle import androidx.fragment.app.Fragment diff --git a/app/src/main/java/com/example/everymoment/presentation/view/FriendRequestFragment.kt b/app/src/main/java/com/example/everymoment/presentation/view/sub/friends/FriendRequestFragment.kt similarity index 55% rename from app/src/main/java/com/example/everymoment/presentation/view/FriendRequestFragment.kt rename to app/src/main/java/com/example/everymoment/presentation/view/sub/friends/FriendRequestFragment.kt index 817424b..e754508 100644 --- a/app/src/main/java/com/example/everymoment/presentation/view/FriendRequestFragment.kt +++ b/app/src/main/java/com/example/everymoment/presentation/view/sub/friends/FriendRequestFragment.kt @@ -1,6 +1,7 @@ -package com.example.everymoment.presentation.view +package com.example.everymoment.presentation.view.sub.friends import android.os.Bundle +import android.util.Log import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View @@ -8,8 +9,9 @@ import android.view.ViewGroup import androidx.core.widget.addTextChangedListener import androidx.recyclerview.widget.LinearLayoutManager import com.example.everymoment.R -import com.example.everymoment.data.model.FriendRequest -import com.example.everymoment.data.model.Friends +import com.example.everymoment.data.model.NetworkUtil +import com.example.everymoment.data.repository.Member +import com.example.everymoment.data.repository.MemberResponse import com.example.everymoment.databinding.FragmentFriendRequestBinding import com.example.everymoment.presentation.adapter.FriendRequestAdapter @@ -19,19 +21,8 @@ class FriendRequestFragment : Fragment() { private lateinit var adapter: FriendRequestAdapter // 테스트용 더미 데이터 - private val allUsers = mutableListOf( - FriendRequest("박지연", "https://example.com/user1.jpg"), - FriendRequest("한예지", "https://example.com/user2.jpg"), - FriendRequest("춘식이", "https://example.com/user3.jpg"), - FriendRequest("소연이", "https://example.com/user4.jpg"), - FriendRequest("박소담", "https://example.com/user5.jpg"), - FriendRequest("한가인", "https://example.com/user6.jpg"), - FriendRequest("박지승", "https://example.com/user7.jpg"), - FriendRequest("김고은", "https://example.com/user8.jpg"), - FriendRequest("박소영", "https://example.com/user9.jpg"), - FriendRequest("김범수", "https://example.com/user10.jpg"), - FriendRequest("한혜진", "https://example.com/user11.jpg") - ) + private var allMembers = mutableListOf() + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -46,6 +37,7 @@ class FriendRequestFragment : Fragment() { setupRecyclerView() setupSearch() + fetchMembersFromServer() binding.friendsBackButton.setOnClickListener { requireActivity().supportFragmentManager.beginTransaction().apply { @@ -54,20 +46,52 @@ class FriendRequestFragment : Fragment() { commit() } } + + } + + private fun fetchMembersFromServer() { + val url = "http://13.125.156.74:8080/api/members?size=30" + val jwtToken = + "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6NiwiaWF0IjoxNzI4NjA5ODk3LCJleHAiOjE3Mjg3ODI2OTd9.JaJ2Ut7M_YePTXZZNODRu6eGBXwbO2kLtDXl2jz9Ock" + + NetworkUtil.getData( + url, + jwtToken, + responseClass = MemberResponse::class.java + ) { success, memberResponse -> + if (success && memberResponse != null) { + Log.d("memberNetwork", "fetchedMemberList : ${memberResponse.info.members}") + + allMembers.clear() + allMembers.addAll(memberResponse.info.members) + Log.d("memberNetwork", "allMembers list: $allMembers") + + activity?.runOnUiThread { + adapter.submitList(allMembers) + setupRecyclerView() + } + } else { + Log.d("memberNetwork", "Network failed") + activity?.runOnUiThread { + + } + } + } } private fun setupRecyclerView() { - adapter = FriendRequestAdapter { user -> + adapter = FriendRequestAdapter (requireActivity()) { user -> } binding.friendRequestRecyclerView.layoutManager = LinearLayoutManager(requireContext()) binding.friendRequestRecyclerView.adapter = adapter - adapter.submitList(allUsers) + adapter.submitList(allMembers) } private fun setupSearch() { binding.searchUserEditText.addTextChangedListener { editable -> val searchText = editable.toString() - val filteredList = allUsers.filter { it.name.contains(searchText, ignoreCase = true) } + val filteredList = + allMembers.filter { it.nickname.contains(searchText, ignoreCase = true) } adapter.submitList(filteredList) if (filteredList.isEmpty()) { diff --git a/app/src/main/java/com/example/everymoment/presentation/view/sub/friends/FriendRequestListFragment.kt b/app/src/main/java/com/example/everymoment/presentation/view/sub/friends/FriendRequestListFragment.kt new file mode 100644 index 0000000..add884e --- /dev/null +++ b/app/src/main/java/com/example/everymoment/presentation/view/sub/friends/FriendRequestListFragment.kt @@ -0,0 +1,64 @@ +package com.example.everymoment.presentation.view.sub.friends + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import com.example.everymoment.R +import com.example.everymoment.data.model.FriendRequestList +import com.example.everymoment.databinding.FragmentFriendRequestListBinding +import com.example.everymoment.presentation.adapter.FriendRequestListAdapter + + +class FriendRequestListFragment : Fragment() { + + // 테스트용 더미 데이터 + private val friendRequestMember = mutableListOf( + FriendRequestList("박지연", "https://example.com/user1.jpg"), + FriendRequestList("한예지", "https://example.com/user2.jpg"), + FriendRequestList("춘식이", "https://example.com/user3.jpg"), + FriendRequestList("소연이", "https://example.com/user4.jpg"), + FriendRequestList("박소담", "https://example.com/user5.jpg"), + FriendRequestList("한가인", "https://example.com/user6.jpg"), + FriendRequestList("박지승", "https://example.com/user7.jpg"), + FriendRequestList("김고은", "https://example.com/user8.jpg"), + FriendRequestList("박소영", "https://example.com/user9.jpg"), + FriendRequestList("김범수", "https://example.com/user10.jpg"), + FriendRequestList("한혜진", "https://example.com/user11.jpg") + ) + + private lateinit var binding: FragmentFriendRequestListBinding + private lateinit var adapter: FriendRequestListAdapter + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentFriendRequestListBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupRecyclerView() + + binding.friendRequestListBackButton.setOnClickListener { + requireActivity().supportFragmentManager.beginTransaction().apply { + replace(R.id.fragment_container, FriendsListFragment()) + addToBackStack(null) + commit() + } + } + } + + private fun setupRecyclerView() { + adapter = FriendRequestListAdapter { friendRequest -> + } + binding.friendRequestListRecyclerView.layoutManager = LinearLayoutManager(requireContext()) + binding.friendRequestListRecyclerView.adapter = adapter + adapter.submitList(friendRequestMember.toList()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/presentation/view/FriendsListFragment.kt b/app/src/main/java/com/example/everymoment/presentation/view/sub/friends/FriendsListFragment.kt similarity index 52% rename from app/src/main/java/com/example/everymoment/presentation/view/FriendsListFragment.kt rename to app/src/main/java/com/example/everymoment/presentation/view/sub/friends/FriendsListFragment.kt index 60159f6..0607c37 100644 --- a/app/src/main/java/com/example/everymoment/presentation/view/FriendsListFragment.kt +++ b/app/src/main/java/com/example/everymoment/presentation/view/sub/friends/FriendsListFragment.kt @@ -1,9 +1,11 @@ -package com.example.everymoment.presentation.view +package com.example.everymoment.presentation.view.sub.friends import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.animation.Animation +import android.view.animation.AnimationUtils import androidx.core.widget.addTextChangedListener import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager @@ -16,6 +18,23 @@ class FriendsListFragment : Fragment() { private lateinit var binding: FragmentFriendsListBinding private lateinit var adapter: FriendsListAdapter + private var isFabExpanded = false + + private val fromBottomFabAnim : Animation by lazy { + AnimationUtils.loadAnimation(requireContext(), R.anim.from_bottom_fab) + } + + private val toBottomFabAnim : Animation by lazy { + AnimationUtils.loadAnimation(requireContext(), R.anim.to_bottom_fab) + } + + private val rotateClockWiseFabAnim : Animation by lazy { + AnimationUtils.loadAnimation(requireContext(), R.anim.rotate_clock_wise) + } + + private val rotateAntiClockWiseFabAnim : Animation by lazy { + AnimationUtils.loadAnimation(requireContext(), R.anim.rotate_anti_clock_wise) + } // 테스트용 더미 데이터 private val friendsList = mutableListOf( @@ -43,20 +62,73 @@ class FriendsListFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupRecyclerView() - setupSearch() + binding.mainFab.setOnClickListener { + if (isFabExpanded) { + shrinkFab() + } else { + expandFab() + } + } - binding.friendAddButton.setOnClickListener { + binding.friendRequestFab.setOnClickListener { requireActivity().supportFragmentManager.beginTransaction().apply { replace(R.id.fragment_container, FriendRequestFragment()) addToBackStack(null) commit() } } + + binding.friendAcceptFab.setOnClickListener { + requireActivity().supportFragmentManager.beginTransaction().apply { + replace(R.id.fragment_container, FriendRequestListFragment()) + addToBackStack(null) + commit() + } + } + + setupRecyclerView() + setupSearch() + + + } + + private fun expandFab() { + + binding.mainFab.startAnimation(rotateClockWiseFabAnim) + binding.friendRequestFab.startAnimation(fromBottomFabAnim) + binding.friendAcceptFab.startAnimation(fromBottomFabAnim) + binding.friendRequestFabTv.startAnimation(fromBottomFabAnim) + binding.friendAcceptFabTv.startAnimation(fromBottomFabAnim) + + binding.friendRequestFabTv.visibility = View.VISIBLE + binding.friendAcceptFabTv.visibility = View.VISIBLE + binding.friendRequestFab.visibility = View.VISIBLE + binding.friendRequestFab.isClickable = true + binding.friendAcceptFab.visibility = View.VISIBLE + binding.friendAcceptFab.isClickable = true + isFabExpanded = !isFabExpanded + } + + private fun shrinkFab() { + + binding.mainFab.startAnimation(rotateAntiClockWiseFabAnim) + binding.friendRequestFab.startAnimation(toBottomFabAnim) + binding.friendAcceptFab.startAnimation(toBottomFabAnim) + binding.friendRequestFabTv.startAnimation(toBottomFabAnim) + binding.friendAcceptFabTv.startAnimation(toBottomFabAnim) + + binding.friendRequestFabTv.visibility = View.GONE + binding.friendAcceptFabTv.visibility = View.GONE + binding.friendRequestFab.visibility = View.GONE + binding.friendRequestFab.isClickable = false + binding.friendAcceptFab.visibility = View.GONE + binding.friendAcceptFab.isClickable = false + + isFabExpanded = !isFabExpanded } private fun setupRecyclerView() { - adapter = FriendsListAdapter { friend -> + adapter = FriendsListAdapter(requireActivity()) { friend -> deleteFriend(friend) } binding.friendsRecyclerView.layoutManager = LinearLayoutManager(requireContext()) diff --git a/app/src/main/java/com/example/everymoment/presentation/viewModel/KakaoLoginViewModel.kt b/app/src/main/java/com/example/everymoment/presentation/viewModel/KakaoLoginViewModel.kt index 17778eb..cff13f2 100644 --- a/app/src/main/java/com/example/everymoment/presentation/viewModel/KakaoLoginViewModel.kt +++ b/app/src/main/java/com/example/everymoment/presentation/viewModel/KakaoLoginViewModel.kt @@ -56,6 +56,7 @@ class KakaoLoginViewModel(private val userRepository: UserRepository) : ViewMode "\n회원번호: ${user.id}" + "\n닉네임: ${user.kakaoAccount?.profile?.nickname}" ) + userRepository.requestToken(user.id, user.kakaoAccount?.profile?.nickname) _uiState.value = KakaoLoginUiState( isLoggedIn = true, userId = user.id, diff --git a/app/src/main/java/com/example/everymoment/presentation/viewModel/TimelineViewModel.kt b/app/src/main/java/com/example/everymoment/presentation/viewModel/TimelineViewModel.kt new file mode 100644 index 0000000..7ba71fe --- /dev/null +++ b/app/src/main/java/com/example/everymoment/presentation/viewModel/TimelineViewModel.kt @@ -0,0 +1,45 @@ +package com.example.everymoment.presentation.viewModel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.everymoment.data.repository.Diary +import com.example.everymoment.data.repository.DiaryRepository +import kotlinx.coroutines.launch + +class TimelineViewModel(private val diaryRepository: DiaryRepository) : ViewModel() { + private val _diaries = MutableLiveData>() + val diaries: LiveData> get() = _diaries + + + fun fetchDiaries(date: String) { + viewModelScope.launch { + diaryRepository.getDiaries(date) { success, response -> + if (success && response != null) { + _diaries.postValue(response.info.diaries) + } + } + } + } + + fun updateBookmarkStatus(diaryId: Int) { + viewModelScope.launch { + diaryRepository.updateBookmarkStatus(diaryId) { success, response -> + } + } + } + + fun updateShareStatus(diaryId: Int) { + viewModelScope.launch { + diaryRepository.updateShareStatus(diaryId) { success, response -> + } + } + } + + fun deleteDiary(diaryId: Int){ + viewModelScope.launch { + diaryRepository.deleteDiary(diaryId) { success, response -> } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/everymoment/presentation/viewModel/TimelineViewModelFactory.kt b/app/src/main/java/com/example/everymoment/presentation/viewModel/TimelineViewModelFactory.kt new file mode 100644 index 0000000..956ba56 --- /dev/null +++ b/app/src/main/java/com/example/everymoment/presentation/viewModel/TimelineViewModelFactory.kt @@ -0,0 +1,16 @@ +package com.example.everymoment.presentation.viewModel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.example.everymoment.data.repository.DiaryRepository + +class TimelineViewModelFactory( + private val diaryRepository: DiaryRepository +) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(TimelineViewModel::class.java)) { + return TimelineViewModel(diaryRepository) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/from_bottom_fab.xml b/app/src/main/res/anim/from_bottom_fab.xml new file mode 100644 index 0000000..b318ef3 --- /dev/null +++ b/app/src/main/res/anim/from_bottom_fab.xml @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/rotate_anti_clock_wise.xml b/app/src/main/res/anim/rotate_anti_clock_wise.xml new file mode 100644 index 0000000..b1a6131 --- /dev/null +++ b/app/src/main/res/anim/rotate_anti_clock_wise.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/rotate_clock_wise.xml b/app/src/main/res/anim/rotate_clock_wise.xml new file mode 100644 index 0000000..4ab4805 --- /dev/null +++ b/app/src/main/res/anim/rotate_clock_wise.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/to_bottom_fab.xml b/app/src/main/res/anim/to_bottom_fab.xml new file mode 100644 index 0000000..d73d5ef --- /dev/null +++ b/app/src/main/res/anim/to_bottom_fab.xml @@ -0,0 +1,23 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/add_24px.xml b/app/src/main/res/drawable/add_24px.xml new file mode 100644 index 0000000..76c2d3c --- /dev/null +++ b/app/src/main/res/drawable/add_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/bookmark_26dp_5f6368_fill0_wght300_grad0_opsz24.xml b/app/src/main/res/drawable/bookmark_26dp_5f6368_fill0_wght300_grad0_opsz24.xml new file mode 100644 index 0000000..7ea34ef --- /dev/null +++ b/app/src/main/res/drawable/bookmark_26dp_5f6368_fill0_wght300_grad0_opsz24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/category_gray_background.xml b/app/src/main/res/drawable/category_gray_background.xml new file mode 100644 index 0000000..14bc8f7 --- /dev/null +++ b/app/src/main/res/drawable/category_gray_background.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/delete_26dp_5f6368_fill0_wght300_grad0_opsz24.xml b/app/src/main/res/drawable/delete_26dp_5f6368_fill0_wght300_grad0_opsz24.xml new file mode 100644 index 0000000..e4dd795 --- /dev/null +++ b/app/src/main/res/drawable/delete_26dp_5f6368_fill0_wght300_grad0_opsz24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/emoji_selected_circle.xml b/app/src/main/res/drawable/emoji_selected_circle.xml new file mode 100644 index 0000000..3d45ef6 --- /dev/null +++ b/app/src/main/res/drawable/emoji_selected_circle.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gray_border_bookmark.xml b/app/src/main/res/drawable/gray_border_bookmark.xml new file mode 100644 index 0000000..f314b19 --- /dev/null +++ b/app/src/main/res/drawable/gray_border_bookmark.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_friend.xml b/app/src/main/res/drawable/ic_friend.xml new file mode 100644 index 0000000..d447995 --- /dev/null +++ b/app/src/main/res/drawable/ic_friend.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/manage_search_24px.xml b/app/src/main/res/drawable/manage_search_24px.xml index 6ddb58f..d64ff63 100644 --- a/app/src/main/res/drawable/manage_search_24px.xml +++ b/app/src/main/res/drawable/manage_search_24px.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" - android:tint="?attr/colorControlNormal" + android:tint="@color/primary_color" android:autoMirrored="true"> + + diff --git a/app/src/main/res/drawable/outline_calendar_month_24.xml b/app/src/main/res/drawable/outline_calendar_month_24.xml new file mode 100644 index 0000000..915b8b5 --- /dev/null +++ b/app/src/main/res/drawable/outline_calendar_month_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/outline_circle_24.xml b/app/src/main/res/drawable/outline_circle_24.xml new file mode 100644 index 0000000..d8879d2 --- /dev/null +++ b/app/src/main/res/drawable/outline_circle_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/person_alert_24px.xml b/app/src/main/res/drawable/person_alert_24px.xml new file mode 100644 index 0000000..a232d8f --- /dev/null +++ b/app/src/main/res/drawable/person_alert_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/refresh_24px.xml b/app/src/main/res/drawable/refresh_24px.xml new file mode 100644 index 0000000..f4302a5 --- /dev/null +++ b/app/src/main/res/drawable/refresh_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/search_filter_bottomsheet_background.xml b/app/src/main/res/drawable/search_filter_bottomsheet_background.xml index d62de07..95668dc 100644 --- a/app/src/main/res/drawable/search_filter_bottomsheet_background.xml +++ b/app/src/main/res/drawable/search_filter_bottomsheet_background.xml @@ -2,8 +2,11 @@ - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/search_filter_date_background.xml b/app/src/main/res/drawable/search_filter_date_background.xml new file mode 100644 index 0000000..69fb037 --- /dev/null +++ b/app/src/main/res/drawable/search_filter_date_background.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/search_filter_date_gray_background.xml b/app/src/main/res/drawable/search_filter_date_gray_background.xml new file mode 100644 index 0000000..0833617 --- /dev/null +++ b/app/src/main/res/drawable/search_filter_date_gray_background.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/share_26dp_5f6368_fill0_wght300_grad0_opsz24.xml b/app/src/main/res/drawable/share_26dp_5f6368_fill0_wght300_grad0_opsz24.xml new file mode 100644 index 0000000..e23dc8d --- /dev/null +++ b/app/src/main/res/drawable/share_26dp_5f6368_fill0_wght300_grad0_opsz24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/story_ring_not_updated.xml b/app/src/main/res/drawable/story_ring_not_updated.xml new file mode 100644 index 0000000..ed52129 --- /dev/null +++ b/app/src/main/res/drawable/story_ring_not_updated.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/story_ring_updated.xml b/app/src/main/res/drawable/story_ring_updated.xml new file mode 100644 index 0000000..35b12d3 --- /dev/null +++ b/app/src/main/res/drawable/story_ring_updated.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tag_24px.xml b/app/src/main/res/drawable/tag_24px.xml new file mode 100644 index 0000000..40b2457 --- /dev/null +++ b/app/src/main/res/drawable/tag_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_kakao_login.xml b/app/src/main/res/layout/activity_kakao_login.xml index 0f33afb..2a75d07 100644 --- a/app/src/main/res/layout/activity_kakao_login.xml +++ b/app/src/main/res/layout/activity_kakao_login.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" - tools:context=".presentation.view.KakaoLoginActivity"> + tools:context=".presentation.view.main.KakaoLoginActivity"> + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/category_popup.xml b/app/src/main/res/layout/category_popup.xml index 3ab09dd..1504434 100644 --- a/app/src/main/res/layout/category_popup.xml +++ b/app/src/main/res/layout/category_popup.xml @@ -19,8 +19,8 @@ @@ -24,6 +24,7 @@ android:layout_marginBottom="5dp" android:text="nickName" android:textColor="@color/primary_color" + android:textSize="16sp" android:textStyle="bold" /> \ No newline at end of file diff --git a/app/src/main/res/layout/custom_notification.xml b/app/src/main/res/layout/custom_notification.xml new file mode 100644 index 0000000..554853a --- /dev/null +++ b/app/src/main/res/layout/custom_notification.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/emotion_window.xml b/app/src/main/res/layout/emotion_window.xml index 6cd6d21..71c09c5 100644 --- a/app/src/main/res/layout/emotion_window.xml +++ b/app/src/main/res/layout/emotion_window.xml @@ -8,42 +8,46 @@ android:orientation="horizontal" android:paddingStart="7dp" android:paddingTop="2dp" - android:paddingEnd="2dp" - android:paddingBottom="2dp"> + android:paddingBottom="5dp"> + android:textSize="22sp" /> + android:textSize="22sp" /> + android:textSize="22sp" /> + android:textSize="22sp" /> + android:textSize="22sp" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_calendar_view.xml b/app/src/main/res/layout/fragment_calendar_view.xml index 3edcc3a..4a8f4ac 100644 --- a/app/src/main/res/layout/fragment_calendar_view.xml +++ b/app/src/main/res/layout/fragment_calendar_view.xml @@ -6,30 +6,58 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" - tools:context=".presentation.view.CalendarViewFragment"> + tools:context=".presentation.view.main.CalendarViewFragment"> - - + + + + + + + + + + tools:context=".presentation.view.sub.diary.DiaryEditFragment"> @@ -123,29 +127,36 @@ android:id="@+id/address" android:layout_width="match_parent" android:layout_height="30dp" - android:layout_marginStart="5dp" + android:layout_marginStart="10dp" android:layout_marginTop="5dp" android:background="@android:color/transparent" + android:hint="@string/address_hint" + android:lines="1" + android:singleLine="true" android:text="address" + android:textColorHint="#838383" android:textSize="17sp" /> @@ -175,20 +191,20 @@ @@ -196,11 +212,11 @@ android:id="@+id/content" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginStart="5dp" + android:layout_marginStart="10dp" android:layout_marginTop="20dp" android:background="@android:color/transparent" android:hint="@string/diary_hint_sentence" - android:inputType="text" /> + android:inputType="textMultiLine" /> diff --git a/app/src/main/res/layout/fragment_diary_read.xml b/app/src/main/res/layout/fragment_diary_read.xml index d6322a1..af5ff14 100644 --- a/app/src/main/res/layout/fragment_diary_read.xml +++ b/app/src/main/res/layout/fragment_diary_read.xml @@ -6,7 +6,7 @@ android:layout_height="match_parent" android:background="#515151" android:orientation="vertical" - tools:context=".presentation.view.DiaryReadFragment"> + tools:context=".presentation.view.sub.diary.DiaryReadFragment"> @@ -142,9 +142,11 @@ android:layout_height="match_parent" android:layout_marginEnd="5dp" android:background="@drawable/category_background" - android:padding="3dp" - android:paddingStart="10dp" - android:paddingEnd="10dp" + android:gravity="center" + android:paddingStart="5dp" + android:paddingTop="2dp" + android:paddingEnd="5dp" + android:paddingBottom="2dp" android:textColor="@color/white" android:visibility="visible" tools:visibility="visible" /> @@ -155,9 +157,11 @@ android:layout_height="match_parent" android:layout_marginEnd="5dp" android:background="@drawable/category_background" - android:padding="3dp" - android:paddingStart="10dp" - android:paddingEnd="10dp" + android:gravity="center" + android:paddingStart="5dp" + android:paddingTop="2dp" + android:paddingEnd="5dp" + android:paddingBottom="2dp" android:textColor="@color/white" android:visibility="visible" tools:visibility="visible" /> @@ -165,21 +169,22 @@ @@ -187,7 +192,7 @@ android:id="@+id/content" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginStart="5dp" + android:layout_marginStart="10dp" android:layout_marginTop="20dp" android:text="content" android:textSize="17sp" diff --git a/app/src/main/res/layout/fragment_friend_request.xml b/app/src/main/res/layout/fragment_friend_request.xml index bb04310..ab0b492 100644 --- a/app/src/main/res/layout/fragment_friend_request.xml +++ b/app/src/main/res/layout/fragment_friend_request.xml @@ -75,8 +75,10 @@ android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/searchUserEditText" + app:layout_constraintVertical_bias="1.0" tools:listitem="@layout/friend_request_item" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_friend_request_list.xml b/app/src/main/res/layout/fragment_friend_request_list.xml new file mode 100644 index 0000000..3ea221c --- /dev/null +++ b/app/src/main/res/layout/fragment_friend_request_list.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_friends_list.xml b/app/src/main/res/layout/fragment_friends_list.xml index f323a4d..3a18def 100644 --- a/app/src/main/res/layout/fragment_friends_list.xml +++ b/app/src/main/res/layout/fragment_friends_list.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" - tools:context=".presentation.view.FriendsListFragment"> + tools:context=".presentation.view.sub.friends.FriendsListFragment"> - + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_notification.xml b/app/src/main/res/layout/fragment_notification.xml index 11e470e..9cd1537 100644 --- a/app/src/main/res/layout/fragment_notification.xml +++ b/app/src/main/res/layout/fragment_notification.xml @@ -6,7 +6,7 @@ android:layout_height="match_parent" android:background="@color/white" android:orientation="vertical" - tools:context=".presentation.view.NotificationFragment"> + tools:context=".presentation.view.sub.NotificationFragment"> + tools:context=".presentation.view.sub.PostFragment"> + tools:context=".presentation.view.main.search.SearchFragment"> + android:layout_marginStart="5dp" + android:layout_marginEnd="5dp" + android:layout_marginTop="5dp" + android:paddingBottom="5dp" + android:paddingEnd="5dp" + android:paddingStart="5dp"> - + android:background="@drawable/search_view_background" + android:drawableStart="@drawable/search_20px" + android:drawablePadding="8dp" + android:drawableTint="@color/search_icon_background" + android:hint="@string/search_window_guide" + android:inputType="text" + android:padding="10dp" + android:textSize="14sp" + app:layout_constraintEnd_toStartOf="@id/filter" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" /> - + - - - + android:text="@string/search_filter" + android:textColor="@color/primary_color" + android:layout_gravity="bottom"/> - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_setting.xml b/app/src/main/res/layout/fragment_setting.xml index 963c331..8400f99 100644 --- a/app/src/main/res/layout/fragment_setting.xml +++ b/app/src/main/res/layout/fragment_setting.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" - tools:context=".presentation.view.SettingFragment"> + tools:context=".presentation.view.main.SettingFragment"> + + + + + app:layout_constraintTop_toBottomOf="@id/pushNotification" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/timeInterval"> + android:textColor="@color/white" + android:textSize="15sp" /> + android:textColor="@color/white" + android:textSize="15sp" /> + android:textColor="@color/white" + android:textSize="15sp" /> + android:textColor="@color/white" + android:textSize="15sp" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_share_view.xml b/app/src/main/res/layout/fragment_share_view.xml new file mode 100644 index 0000000..9cc8ece --- /dev/null +++ b/app/src/main/res/layout/fragment_share_view.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_today_log.xml b/app/src/main/res/layout/fragment_today_log.xml index bd8f2f2..3622e65 100644 --- a/app/src/main/res/layout/fragment_today_log.xml +++ b/app/src/main/res/layout/fragment_today_log.xml @@ -7,24 +7,35 @@ android:layout_height="match_parent" android:orientation="vertical" android:background="@color/white" - tools:context=".presentation.view.TodayLogFragment"> + tools:context=".presentation.view.main.TodayLogFragment"> + android:layout_height="wrap_content"> + + - + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/friend_request_list_item.xml b/app/src/main/res/layout/friend_request_list_item.xml new file mode 100644 index 0000000..79ef207 --- /dev/null +++ b/app/src/main/res/layout/friend_request_list_item.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/notification_item.xml b/app/src/main/res/layout/notification_item.xml index 8416d96..f9a1902 100644 --- a/app/src/main/res/layout/notification_item.xml +++ b/app/src/main/res/layout/notification_item.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="80dp"> + android:layout_height="90dp"> @@ -125,7 +126,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="20dp" - android:layout_marginTop="50dp" + android:layout_marginTop="60dp" android:text="content" android:textSize="17sp" android:visibility="visible" diff --git a/app/src/main/res/layout/search_filter_bottomsheet.xml b/app/src/main/res/layout/search_filter_bottomsheet.xml deleted file mode 100644 index 4e79a33..0000000 --- a/app/src/main/res/layout/search_filter_bottomsheet.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/share_item.xml b/app/src/main/res/layout/share_item.xml new file mode 100644 index 0000000..8ca366d --- /dev/null +++ b/app/src/main/res/layout/share_item.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/timeline_item.xml b/app/src/main/res/layout/timeline_item.xml index 3d3a64a..8534fcf 100644 --- a/app/src/main/res/layout/timeline_item.xml +++ b/app/src/main/res/layout/timeline_item.xml @@ -25,6 +25,7 @@ android:id="@+id/timeText" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_gravity="center_vertical" android:hint="오전 10:00" android:textSize="10sp" /> @@ -43,25 +44,25 @@ + android:layout_width="22dp" + android:layout_height="22dp" + android:src="@drawable/delete_26dp_5f6368_fill0_wght300_grad0_opsz24" /> @@ -84,8 +85,8 @@ diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index d16fb62..137379a 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,7 +1,18 @@ - + + + + - + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 78ed83e..c5c6d2a 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -2,9 +2,17 @@ + +