diff --git a/app/src/main/java/com/petpal/mungmate/model/WalkRecord.kt b/app/src/main/java/com/petpal/mungmate/model/WalkRecord.kt new file mode 100644 index 00000000..46344ca1 --- /dev/null +++ b/app/src/main/java/com/petpal/mungmate/model/WalkRecord.kt @@ -0,0 +1,14 @@ +package com.petpal.mungmate.model + +data class WalkRecord( + val walkRecorduid:String, //산책 기록자 uid + val walkRecordDate:String, //기록한 날짜 + val walkRecordStartTime:String, //시작시간 + val walkRecordEndTime:String, //종료시간 + val walkDuration:String, //소요시간 + val walkDistance:Double, //거리 + val walkMatchingId:String?=null, //산책 상대 uid + val walkMemo:String, //메모 + val walkPhoto:String?=null //사진 + +) diff --git a/app/src/main/java/com/petpal/mungmate/ui/WalkReviewWriteFragment.kt b/app/src/main/java/com/petpal/mungmate/ui/WalkReviewWriteFragment.kt new file mode 100644 index 00000000..af2ee7bf --- /dev/null +++ b/app/src/main/java/com/petpal/mungmate/ui/WalkReviewWriteFragment.kt @@ -0,0 +1,163 @@ +package com.petpal.mungmate.ui + +import android.app.Activity +import android.content.Intent +import android.graphics.Bitmap +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import com.bumptech.glide.Glide +import com.bumptech.glide.request.target.SimpleTarget +import com.bumptech.glide.request.transition.Transition +import com.google.android.material.snackbar.Snackbar +import com.google.firebase.auth.ktx.auth +import com.google.firebase.firestore.FieldValue +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.ktx.Firebase +import com.google.firebase.storage.FirebaseStorage +import com.petpal.mungmate.MainActivity + +import com.petpal.mungmate.R +import com.petpal.mungmate.databinding.FragmentWalkReviewWriteBinding +import com.petpal.mungmate.model.Place +import com.petpal.mungmate.model.Review +import com.petpal.mungmate.model.WalkRecord +import com.petpal.mungmate.ui.placereview.WritePlaceReviewFragment +import java.io.ByteArrayOutputStream +import java.util.UUID + + +class WalkReviewWriteFragment : Fragment() { + private lateinit var fragmentWalkReviewWriteBinding: FragmentWalkReviewWriteBinding + private lateinit var mainActivity: MainActivity + private val db = FirebaseFirestore.getInstance() + private val storage = FirebaseStorage.getInstance() + private var selectedImageUri: Uri? = null + private val auth = Firebase.auth + private lateinit var userId:String + + + + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + fragmentWalkReviewWriteBinding= FragmentWalkReviewWriteBinding.inflate(layoutInflater) + mainActivity=activity as MainActivity + + val user=auth.currentUser + userId=user!!.uid + val walkRecorduid=arguments?.getString("walkRecorduid") + val walkRecordDate=arguments?.getString("walkRecordDate") + val walkRecordStartTime=arguments?.getString("walkRecordStartTime") + val walkRecordEndTime=arguments?.getString("walkRecordEndTime") + val walkDuration=arguments?.getString("walkDuration") + val walkDistance=arguments?.getString("walkDistance") + val walkMatchingId=arguments?.getString("walkMatchingId") + + fragmentWalkReviewWriteBinding.imageViewWalk.setOnClickListener { + selectImageFromGallery() + } + + fragmentWalkReviewWriteBinding.buttonWalkReviewSubmit.setOnClickListener { + val walkMemo=fragmentWalkReviewWriteBinding.editTextWalkContent.text.toString() + val walkPhoto=selectedImageUri + if (walkPhoto != null) { + uploadImageToStorage(walkPhoto) { walkPhoto -> + val walkReview=WalkRecord(walkRecorduid!!,walkRecordDate!!,walkRecordStartTime!!,walkRecordEndTime!!,walkDuration!!,walkDistance!!.toDouble(),walkMatchingId, + walkMemo,walkPhoto) + addWalkReview(userId,walkReview) + // 리뷰 등록 후 Navigation 이동 + mainActivity.navigate(R.id.action_WriteWalkReviewFragment_to_mainFragment) + } + } + } + + + return fragmentWalkReviewWriteBinding.root + } + + fun addWalkReview(userId: String, walkReview: WalkRecord) { + val userRef = db.collection("users").document(userId) + + userRef.update("walkRecordList", FieldValue.arrayUnion(walkReview)) + .addOnSuccessListener { + Toast.makeText(context, "리뷰가 성공적으로 등록되었습니다.", Toast.LENGTH_SHORT).show() + } + .addOnFailureListener { e -> + + Toast.makeText(context, "리뷰 등록 실패 .", Toast.LENGTH_SHORT).show() + } + } + + + + private fun selectImageFromGallery() { + val intent = Intent(Intent.ACTION_PICK) + intent.type = "image/*" + startActivityForResult(intent, IMAGE_PICK_CODE1) + } + + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + + if (resultCode == Activity.RESULT_OK && requestCode == IMAGE_PICK_CODE1) { + selectedImageUri = data?.data + fragmentWalkReviewWriteBinding.imageViewWalk.setImageURI(selectedImageUri) + } + } + + private fun uploadImageToStorage(uri: Uri, onSuccess: (String) -> Unit) { + showProgress() + + Glide.with(this) + .asBitmap() + .load(uri) + .override(400, 400) + .into(object : SimpleTarget() { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + val byteArrayOutputStream = ByteArrayOutputStream() + resource.compress(Bitmap.CompressFormat.JPEG, 80, byteArrayOutputStream) + val byteArray = byteArrayOutputStream.toByteArray() + + val ref = storage.reference.child("reviews/${UUID.randomUUID()}.jpg") + ref.putBytes(byteArray) + .addOnSuccessListener { + ref.downloadUrl.addOnSuccessListener { + hideProgress() + onSuccess(it.toString()) + //showSnackbar("리뷰가 성공적으로 등록되었습니다.") + Toast.makeText(context, "리뷰가 성공적으로 등록되었습니다.", Toast.LENGTH_SHORT).show() + } + } + .addOnFailureListener { + fragmentWalkReviewWriteBinding.progressBarWalk.visibility = View.GONE + Toast.makeText(context, "이미지 업로드 실패", Toast.LENGTH_SHORT).show() + } + } + }) + } + + private fun showSnackbar(message: String) { + Snackbar.make(fragmentWalkReviewWriteBinding.root, message, Snackbar.LENGTH_SHORT).show() + } + fun showProgress() { + fragmentWalkReviewWriteBinding.progressBarWalk.visibility = View.VISIBLE + fragmentWalkReviewWriteBinding.progressBackgroundWalk.visibility = View.VISIBLE + } + + fun hideProgress() { + fragmentWalkReviewWriteBinding.progressBarWalk.visibility = View.GONE + fragmentWalkReviewWriteBinding.progressBackgroundWalk.visibility = View.GONE + } + companion object { + private const val IMAGE_PICK_CODE1 = 1000 + } + +} diff --git a/app/src/main/java/com/petpal/mungmate/ui/placereview/PlaceReviewFragment.kt b/app/src/main/java/com/petpal/mungmate/ui/placereview/PlaceReviewFragment.kt index 6f6edaf5..e86b7ccc 100644 --- a/app/src/main/java/com/petpal/mungmate/ui/placereview/PlaceReviewFragment.kt +++ b/app/src/main/java/com/petpal/mungmate/ui/placereview/PlaceReviewFragment.kt @@ -1,5 +1,7 @@ package com.petpal.mungmate.ui.placereview +import android.app.AlertDialog +import android.app.Dialog import android.content.Context import android.os.Bundle import android.util.Log @@ -147,17 +149,25 @@ class PlaceReviewFragment : Fragment() { bundle.putString("reviewImageURL", review.imageRes) bundle.putString("placeId",placeId) mainActivity.navigate(R.id.action_placeReviewFragment_to_placeReviewModifyFragment,bundle) - // Toast.makeText(holder.itemView.context, "Modify clicked for position $position", Toast.LENGTH_SHORT).show() } holder.placeReviewDelete.setOnClickListener { - viewModel.deleteReviewForPlace(placeId, reviewId) + val builder = AlertDialog.Builder(it.context) + builder.setTitle("멍메이트") + builder.setMessage("정말로 이 리뷰를 삭제하시겠습니까?") + builder.setPositiveButton("확인") { dialog, _ -> + viewModel.deleteReviewForPlace(placeId, reviewId) + dialog.dismiss() + } + builder.setNegativeButton("취소") { dialog, _ -> + dialog.dismiss() + } + builder.create().show() } } override fun getItemCount(): Int = reviews.size - // This function updates the reviews list and notifies the RecyclerView of the changes fun updateReviews(newReviews: List) { reviews = newReviews notifyDataSetChanged() diff --git a/app/src/main/java/com/petpal/mungmate/ui/walk/WalkFragment.kt b/app/src/main/java/com/petpal/mungmate/ui/walk/WalkFragment.kt index d6216921..c60c4f0f 100644 --- a/app/src/main/java/com/petpal/mungmate/ui/walk/WalkFragment.kt +++ b/app/src/main/java/com/petpal/mungmate/ui/walk/WalkFragment.kt @@ -1,6 +1,7 @@ package com.petpal.mungmate.ui.walk import android.Manifest +import android.annotation.SuppressLint import android.app.Dialog import android.content.pm.PackageManager import android.graphics.Color @@ -9,6 +10,7 @@ import android.location.Location import android.os.Bundle import android.os.Handler import android.os.Looper +import android.text.format.DateUtils.formatElapsedTime import android.util.Log import android.view.LayoutInflater import android.view.View @@ -32,6 +34,9 @@ import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.target.Target import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationCallback +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationResult import com.google.android.gms.location.LocationServices import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog @@ -51,6 +56,10 @@ import kotlinx.coroutines.launch import net.daum.mf.map.api.MapPOIItem import net.daum.mf.map.api.MapPoint import net.daum.mf.map.api.MapView +import java.security.AccessController.checkPermission +import java.text.SimpleDateFormat +import java.util.Date + class WalkFragment : Fragment(), net.daum.mf.map.api.MapView.POIItemEventListener, net.daum.mf.map.api.MapView.CurrentLocationEventListener, net.daum.mf.map.api.MapView.MapViewEventListener { private lateinit var fragmentWalkBinding: FragmentWalkBinding @@ -69,6 +78,21 @@ class WalkFragment : Fragment(), net.daum.mf.map.api.MapView.POIItemEventListene private val auth = Firebase.auth private lateinit var userId:String private lateinit var userNickname:String + private var lastLocation: Location? = null + private var totalDistance = 0.0 + private var elapsedTime = 0L + private var userLocationMarker: MapPOIItem? = null + private var startTimestamp: String ="0" + private val handler = Handler(Looper.getMainLooper()) + + val timerRunnable = object : Runnable { + override fun run() { + elapsedTime += 1 + fragmentWalkBinding.textViewWalkTime.text = formatElapsedTime(elapsedTime) + handler.postDelayed(this, 1000) + } + } + companion object { const val REQUEST_LOCATION_PERMISSION = 1 @@ -89,6 +113,7 @@ class WalkFragment : Fragment(), net.daum.mf.map.api.MapView.POIItemEventListene bottomSheetDialog = BottomSheetDialog(requireContext()) bottomSheetDialog.setContentView(initialBottomSheetView) + setupMapView() setupButtonListeners() observeViewModel() @@ -114,39 +139,48 @@ class WalkFragment : Fragment(), net.daum.mf.map.api.MapView.POIItemEventListene fragmentWalkBinding.mapView.setPOIItemEventListener(this) fragmentWalkBinding.mapView.setCurrentLocationEventListener(this) fragmentWalkBinding.mapView.setMapViewEventListener(this) - requestLocationPermissionIfNeeded() } private fun setupButtonListeners() { fragmentWalkBinding.buttonWalk.setOnClickListener { toggleVisibility(fragmentWalkBinding.LinearLayoutOnWalk, fragmentWalkBinding.LinearLayoutOffWalk) + fragmentWalkBinding.mapView.removeAllPOIItems() fragmentWalkBinding.imageViewWalkToggle.setImageResource(R.drawable.dog_walk) + handler.post(timerRunnable) + startLocationUpdates() + startTimestamp = timestampToString(System.currentTimeMillis()) + Log.d("infooooo",lastLocation.toString()) + + } fragmentWalkBinding.chipMapFilter.setOnClickListener { fragmentWalkBinding.drawerLayout.setScrimColor(Color.parseColor("#FFFFFF")) fragmentWalkBinding.drawerLayout.openDrawer(GravityCompat.END) + } fragmentWalkBinding.buttonFilterSubmit.setOnClickListener { - val isAnyFilterSelected = fragmentWalkBinding.filterDistanceGroup.checkedChipId != -1 || fragmentWalkBinding.filterUserGenderGroup.checkedChipId != -1 || fragmentWalkBinding.filterAgeRangeGroup.checkedChipId != -1 || fragmentWalkBinding.filterPetGenderGroup.checkedChipId != -1 || fragmentWalkBinding.filterPetPropensityGroup.checkedChipId != -1 || fragmentWalkBinding.filterNeuterStatusGroup.checkedChipId != -1 + val isAnyFilterSelected = + fragmentWalkBinding.filterDistanceGroup.checkedChipId != -1 || fragmentWalkBinding.filterUserGenderGroup.checkedChipId != -1 || fragmentWalkBinding.filterAgeRangeGroup.checkedChipId != -1 || fragmentWalkBinding.filterPetGenderGroup.checkedChipId != -1 || fragmentWalkBinding.filterPetPropensityGroup.checkedChipId != -1 || fragmentWalkBinding.filterNeuterStatusGroup.checkedChipId != -1 fragmentWalkBinding.chipMapFilter.isChecked = isAnyFilterSelected + //맵에 대한 필터가 아니지 않아?? +// when (fragmentWalkBinding.filterDistanceGroup.checkedChipId) { +// R.id.distance1 -> { +// //1km 필터에 따른 기능 실행 +// getLastLocationFilter(1000) +// } +// R.id.distance2 -> { +// //2km 필터에 따른 기능 실행 +// getLastLocationFilter(2000) +// } +// R.id.distance3 -> { +// //3km 원래 기본 3km임 +// getLastLocation() +// } +// } - when (fragmentWalkBinding.filterDistanceGroup.checkedChipId) { - R.id.distance1 -> { - //1km 필터에 따른 기능 실행 - getLastLocationFilter(1000) - } - R.id.distance2 -> { - //2km 필터에 따른 기능 실행 - getLastLocationFilter(2000) - } - R.id.distance3 -> { - //3km 원래 기본 3km임 - getLastLocation() - } - } fragmentWalkBinding.drawerLayout.closeDrawer(GravityCompat.END) showSnackbar("필터가 적용되었습니다.") @@ -155,6 +189,21 @@ class WalkFragment : Fragment(), net.daum.mf.map.api.MapView.POIItemEventListene fragmentWalkBinding.buttonStopWalk.setOnClickListener { toggleVisibility(fragmentWalkBinding.LinearLayoutOffWalk, fragmentWalkBinding.LinearLayoutOnWalk) fragmentWalkBinding.imageViewWalkToggle.setImageResource(R.drawable.dog_home) + + val endTimestamp=timestampToString(System.currentTimeMillis()) + val bundle=Bundle() + bundle.putString("walkRecorduid",userId) + bundle.putString("walkRecordDate",getCurrentDate()) + bundle.putString("walkRecordStartTime",startTimestamp) + bundle.putString("walkRecordEndTime",endTimestamp) + bundle.putString("walkDuration",elapsedTime.toString()) + bundle.putString("walkDistance",totalDistance.toString()) + bundle.putString("walkMatchingId","idid") + stopLocationUpdates() + handler.removeCallbacks(timerRunnable) + elapsedTime = 0L + fragmentWalkBinding.textViewWalkTime.text = formatElapsedTime(elapsedTime) + mainActivity.navigate(R.id.action_mainFragment_to_WriteWalkReviewFragment,bundle) } fragmentWalkBinding.imageViewMylocation.setOnClickListener { getCurrentLocation() @@ -162,7 +211,12 @@ class WalkFragment : Fragment(), net.daum.mf.map.api.MapView.POIItemEventListene LastKnownLocation.longitude= null } } - + @SuppressLint("SimpleDateFormat") + fun timestampToString(timestamp: Long): String { + val date = Date(timestamp) + val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") + return formatter.format(date) + } private fun observeViewModel() { @@ -243,10 +297,64 @@ class WalkFragment : Fragment(), net.daum.mf.map.api.MapView.POIItemEventListene } } } + private fun checkPermission(permission: String): Boolean { return ActivityCompat.checkSelfPermission(requireContext(), permission) == PackageManager.PERMISSION_GRANTED } + private fun showUserLocationOnMap(location: Location) { + userLocationMarker?.let { + fragmentWalkBinding.mapView.removePOIItem(it) + } + userLocationMarker = MapPOIItem().apply { + itemName = "Current Location" + mapPoint = MapPoint.mapPointWithGeoCoord(location.latitude, location.longitude) + markerType = MapPOIItem.MarkerType.BluePin + //selectedMarkerType = MapPOIItem.MarkerType.RedPin + } + fragmentWalkBinding.mapView.addPOIItem(userLocationMarker) + fragmentWalkBinding.mapView.setMapCenterPointAndZoomLevel(MapPoint.mapPointWithGeoCoord(location.latitude, location.longitude), 1, true) + } + + private val locationCallback = object : LocationCallback() { + override fun onLocationResult(p0: LocationResult) { + p0 ?: return + for (location in p0.locations) { + // 정확도 체크 + if (location.accuracy <= 3) { + lastLocation?.let { + totalDistance += it.distanceTo(location).toDouble() + // UI 업데이트 + fragmentWalkBinding.textViewWalkDistance.text = "${String.format("%.1f", totalDistance)} m" + } + showUserLocationOnMap(location) + lastLocation = location + lastLocation?.let { location -> + viewModel.updateLocationAndOnWalkStatus(userId, location.latitude, location.longitude) + } + Log.d("infooooo1",lastLocation.toString()) + } + } + } + } + private fun startLocationUpdates() { + if (checkPermission(Manifest.permission.ACCESS_FINE_LOCATION)) { + val locationRequest = LocationRequest.create().apply { + interval = 2000 // 10 seconds + fastestInterval = 5000 // 5 seconds + priority = LocationRequest.PRIORITY_HIGH_ACCURACY + } + fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()) + } + } + + private fun stopLocationUpdates() { + fusedLocationClient.removeLocationUpdates(locationCallback) + totalDistance=0.0 + fragmentWalkBinding.textViewWalkDistance.text=totalDistance.toString() + } + + //LastKnownLocation의 위치에서 검색 마킹(원래 있던 마커들 지우고) private fun getLastLocation() { if (checkPermission(Manifest.permission.ACCESS_FINE_LOCATION) @@ -271,6 +379,7 @@ class WalkFragment : Fragment(), net.daum.mf.map.api.MapView.POIItemEventListene } } } + //거리 필터의 값에 따라 ㄱ private fun getLastLocationFilter(radius:Int) { if (checkPermission(Manifest.permission.ACCESS_FINE_LOCATION) @@ -322,6 +431,12 @@ class WalkFragment : Fragment(), net.daum.mf.map.api.MapView.POIItemEventListene private fun showSnackbar(message: String) { Snackbar.make(fragmentWalkBinding.root, message, Snackbar.LENGTH_LONG).show() } + @SuppressLint("SimpleDateFormat") + fun getCurrentDate(): String { + val current = Date() + val formatter = SimpleDateFormat("yyyy-MM-dd") // 년-월-일 + return formatter.format(current) + } //맵의 마커 클릭시 override fun onPOIItemSelected(p0: net.daum.mf.map.api.MapView?, p1: MapPOIItem?) { diff --git a/app/src/main/java/com/petpal/mungmate/ui/walk/WalkRepository.kt b/app/src/main/java/com/petpal/mungmate/ui/walk/WalkRepository.kt index 0f47defa..2ef18525 100644 --- a/app/src/main/java/com/petpal/mungmate/ui/walk/WalkRepository.kt +++ b/app/src/main/java/com/petpal/mungmate/ui/walk/WalkRepository.kt @@ -146,6 +146,17 @@ class WalkRepository { return document.getString("nickname") } + suspend fun updateLocationAndOnWalkStatus(userId: String, latitude: Double, longitude: Double) { + val userRef = db.collection("users").document(userId) + userRef.update(mapOf( + "onWalk" to true, + "location" to mapOf( + "latitude" to latitude, + "longitude" to longitude + ) + )).await() + } + } diff --git a/app/src/main/java/com/petpal/mungmate/ui/walk/WalkViewModel.kt b/app/src/main/java/com/petpal/mungmate/ui/walk/WalkViewModel.kt index 3880cbd8..81609ffb 100644 --- a/app/src/main/java/com/petpal/mungmate/ui/walk/WalkViewModel.kt +++ b/app/src/main/java/com/petpal/mungmate/ui/walk/WalkViewModel.kt @@ -134,6 +134,16 @@ class WalkViewModel(private val repository: WalkRepository) : ViewModel() { } } + fun updateLocationAndOnWalkStatus(userId: String, latitude: Double, longitude: Double) { + viewModelScope.launch { + try { + repository.updateLocationAndOnWalkStatus(userId, latitude, longitude) + // 필요한 경우 성공 메시지나 상태 업데이트 + } catch (e: Exception) { + errorMessage.postValue(e.localizedMessage ?: "Failed to update location") + } + } + } fun removeFavorite(placeId: String, userId: String) { viewModelScope.launch { try { diff --git a/app/src/main/res/drawable/stop.png b/app/src/main/res/drawable/stop.png new file mode 100644 index 00000000..84233e7d Binary files /dev/null and b/app/src/main/res/drawable/stop.png differ diff --git a/app/src/main/res/layout/fragment_walk.xml b/app/src/main/res/layout/fragment_walk.xml index d9e99cfb..8c7b72ca 100644 --- a/app/src/main/res/layout/fragment_walk.xml +++ b/app/src/main/res/layout/fragment_walk.xml @@ -151,8 +151,9 @@ android:layout_width="100dp" android:layout_height="100dp" android:layout_gravity="center" + android:layout_marginLeft="20dp" android:background="@android:color/transparent" - android:src="@drawable/stop_circle_24px" + android:src="@drawable/stop" app:civ_border_color="#000000" app:civ_border_overlay="true" /> diff --git a/app/src/main/res/layout/fragment_walk_review_write.xml b/app/src/main/res/layout/fragment_walk_review_write.xml new file mode 100644 index 00000000..4d25b821 --- /dev/null +++ b/app/src/main/res/layout/fragment_walk_review_write.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + +