diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index a551c90..da21469 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,24 +4,15 @@ - - - - - - \ No newline at end of file diff --git a/.idea/render.experimental.xml b/.idea/render.experimental.xml new file mode 100644 index 0000000..8ec256a --- /dev/null +++ b/.idea/render.experimental.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3d8ac8e..f0e0174 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -73,6 +73,8 @@ dependencies { implementation("com.google.firebase:firebase-analytics") implementation("com.google.firebase:firebase-auth") + implementation ("com.google.firebase:firebase-firestore:24.7.0") + implementation ("com.google.firebase:firebase-storage:20.2.0")// 최신 버전 확인 // Naver Map SDK implementation("com.naver.maps:map-sdk:3.19.1") @@ -91,4 +93,7 @@ dependencies { // When using the BoM, you don't specify versions in Firebase library dependencies implementation("com.google.firebase:firebase-database") + implementation ("com.github.bumptech.glide:glide:4.15.1") + annotationProcessor ("com.github.bumptech.glide:compiler:4.15.1") + } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a24b2af..22af81b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -52,6 +52,7 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/CheckProfileAndTimeTableFragment/CheckTimeTableFragment.java b/app/src/main/java/com/example/mohassu/CheckProfileAndTimeTableFragment/CheckTimeTableFragment.java index a9737ee..9555771 100644 --- a/app/src/main/java/com/example/mohassu/CheckProfileAndTimeTableFragment/CheckTimeTableFragment.java +++ b/app/src/main/java/com/example/mohassu/CheckProfileAndTimeTableFragment/CheckTimeTableFragment.java @@ -1,49 +1,120 @@ package com.example.mohassu.CheckProfileAndTimeTableFragment; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.TextView; +import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import com.example.mohassu.R; +import com.example.mohassu.models.Friend; +import com.github.tlaabs.timetableview.TimetableView; +import com.google.firebase.firestore.FirebaseFirestore; public class CheckTimeTableFragment extends Fragment { + private TimetableView timetableView; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_check_time_table, container, false); + return inflater.inflate(R.layout.fragment_check_time_table, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + // 🔥 Back 버튼 클릭 리스너 ImageButton backButton = view.findViewById(R.id.btnBack); - backButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - // 현재 프래그먼트를 제거하고 백 스택에서 돌아가기 - requireActivity() - .getSupportFragmentManager() - .popBackStack(); - - // `BottomSheetCheckProfileFragment` 다시 표시 - BottomSheetCheckProfileFragment bottomSheet = (BottomSheetCheckProfileFragment) - requireActivity() - .getSupportFragmentManager() - .findFragmentByTag("BottomSheetCheckProfile"); - - if (bottomSheet != null && bottomSheet.getDialog() != null) { - bottomSheet.getDialog().show(); // 숨겨진 다이얼로그 다시 표시 - } - } - }); + backButton.setOnClickListener(v -> requireActivity().getSupportFragmentManager().popBackStack()); + + // 🔥 TimetableView 초기화 + timetableView = view.findViewById(R.id.timetable); + // 🔥 타이틀 설정 TextView tvTitle = view.findViewById(R.id.tvTitle); + + // 🔥 Bundle에서 친구 정보 가져오기 Bundle args = getArguments(); if (args != null) { - String userName = args.getString("nickName", "Default User"); - tvTitle.setText(getString(R.string.check_time_table_title, userName)); + Friend friend = (Friend) args.getSerializable("friend"); + if (friend != null) { + // 🔥 친구 닉네임으로 타이틀 설정 + tvTitle.setText(getString(R.string.check_time_table_title, friend.getNickname())); + + // 🔥 친구의 시간표 데이터 로드 + loadFriendTimeTable(friend.getUid()); + } else { + Log.e("CheckTimeTableFragment", "Friend 객체가 null입니다."); + } + } else { + Log.e("CheckTimeTableFragment", "전달된 Bundle이 null입니다."); + } + } + + private void loadFriendTimeTable(String friendUid) { + if (friendUid == null || friendUid.isEmpty()) { + Log.e("CheckTimeTableFragment", "friendUid가 null이거나 빈 문자열입니다."); + Toast.makeText(requireContext(), "친구의 정보를 불러올 수 없습니다.", Toast.LENGTH_SHORT).show(); + return; } - return view; + Log.d("CheckTimeTableFragment", "로드할 친구 UID: " + friendUid); + + FirebaseFirestore db = FirebaseFirestore.getInstance(); + db.collection("users").document(friendUid) + .get() + .addOnSuccessListener(documentSnapshot -> { + if (documentSnapshot.exists()) { + // 🔥 시간표 데이터 가져오기 + String timetableData = documentSnapshot.getString("timetableData"); + + if (timetableData != null && !timetableData.isEmpty()) { + Log.d("CheckTimeTableFragment", "시간표 데이터: " + timetableData); + + // 🔥 TimetableView에 데이터 로드 + try { + timetableView.load(timetableData); + Toast.makeText(requireContext(), "시간표를 불러왔습니다.", Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + Log.e("CheckTimeTableFragment", "시간표 로드 중 오류 발생", e); + Toast.makeText(requireContext(), "시간표를 로드할 수 없습니다.", Toast.LENGTH_SHORT).show(); + } + } else { + Toast.makeText(requireContext(), "시간표 데이터가 없습니다.", Toast.LENGTH_SHORT).show(); + } + } else { + Log.e("CheckTimeTableFragment", "친구의 프로필을 찾을 수 없습니다."); + Toast.makeText(requireContext(), "프로필을 찾을 수 없습니다.", Toast.LENGTH_SHORT).show(); + } + }) + .addOnFailureListener(e -> { + Log.e("CheckTimeTableFragment", "시간표 불러오기 실패: " + e.getMessage(), e); + Toast.makeText(requireContext(), "시간표 불러오기 실패: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + }); + } + + @Override + public void onResume() { + super.onResume(); + // 🔥 상단 타이틀 숨기기 + if (requireActivity().getActionBar() != null) { + requireActivity().getActionBar().hide(); + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + // 🔥 상단 타이틀 복원 + if (requireActivity().getActionBar() != null) { + requireActivity().getActionBar().show(); + } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/CheckProfileBottomSheetFragment.java b/app/src/main/java/com/example/mohassu/CheckProfileBottomSheetFragment.java new file mode 100644 index 0000000..25b6b53 --- /dev/null +++ b/app/src/main/java/com/example/mohassu/CheckProfileBottomSheetFragment.java @@ -0,0 +1,86 @@ +package com.example.mohassu; + +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.navigation.NavController; +import androidx.navigation.Navigation; + +import com.bumptech.glide.Glide; +import com.example.mohassu.models.Friend; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +public class CheckProfileBottomSheetFragment extends BottomSheetDialogFragment { + + // Friend 객체를 전달받는 newInstance 메서드 + public static CheckProfileBottomSheetFragment newInstance(Friend friend) { + CheckProfileBottomSheetFragment fragment = new CheckProfileBottomSheetFragment(); + Bundle args = new Bundle(); + args.putSerializable("friend", friend); + fragment.setArguments(args); + return fragment; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_bottom_sheet_check_profile, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + // 🔥 Friend 객체 가져오기 + Friend friend = (Friend) getArguments().getSerializable("friend"); + + if (friend == null) { + return; + } + + // 📌 UI 요소 초기화 + TextView nicknameTextView = view.findViewById(R.id.text_nickname); + TextView nameTextView = view.findViewById(R.id.text_name); + ImageView photoImageView = view.findViewById(R.id.img_profile); + Button viewTimeTableButton = view.findViewById(R.id.view_time_table_button); + ImageButton btnBottomSheetClose = view.findViewById(R.id.btn_bottomsheet_close);// 🔥 추가된 버튼 + + // 🔥 데이터 바인딩 + nicknameTextView.setText(friend.getNickname() != null ? friend.getNickname() : "닉네임 없음"); + nameTextView.setText(friend.getName() != null ? friend.getName() : "이름 없음"); + + // 🔥 Glide를 활용하여 이미지 로드 + Glide.with(requireContext()) + .load(friend.getPhotoUrl()) + .placeholder(R.drawable.img_logo) // 로딩 중 표시할 이미지 + .error(R.drawable.img_logo) // 로딩 실패 시 표시할 이미지 + .into(photoImageView); + + + // 🔥 "시간표 보기" 버튼 클릭 리스너 추가 + viewTimeTableButton.setOnClickListener(v -> { + Bundle bundle = new Bundle(); + bundle.putSerializable("friend", friend); + + try { + dismiss(); + NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); + Log.d("CheckProfileBottomSheetFragment", "NavController 찾기 성공"); + navController.navigate(R.id.action_checkProfileToCheckTimeTable, bundle); + } catch (Exception e) { + e.printStackTrace(); + } + }); + + btnBottomSheetClose.setOnClickListener(v -> dismiss()); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/Constants.java b/app/src/main/java/com/example/mohassu/Constants.java new file mode 100644 index 0000000..446d7f4 --- /dev/null +++ b/app/src/main/java/com/example/mohassu/Constants.java @@ -0,0 +1,35 @@ +package com.example.mohassu; + +import com.naver.maps.geometry.LatLng; + +import java.util.ArrayList; +import java.util.List; + +public class Constants { + public static final List PLACES = new ArrayList<>(); + + static { + PLACES.add(new PlaceInfo("정보과학관", new LatLng(37.494576, 126.959706), 30)); + PLACES.add(new PlaceInfo("한경직기념관", new LatLng(37.495619, 126.957444), 30)); + PLACES.add(new PlaceInfo("중앙도서관", new LatLng(37.496325, 126.958585), 30)); + PLACES.add(new PlaceInfo("전산관", new LatLng(37.495451, 126.959518), 30)); + PLACES.add(new PlaceInfo("형남공학관", new LatLng(37.495732, 126.956152), 30)); + PLACES.add(new PlaceInfo("안익태기념관", new LatLng(37.495753, 126.955012), 30)); + PLACES.add(new PlaceInfo("경상관", new LatLng(37.496549, 126.955030), 30)); + PLACES.add(new PlaceInfo("문화관", new LatLng(37.496576, 126.954271), 30)); + PLACES.add(new PlaceInfo("베어드홀", new LatLng(37.496506, 126.956273), 30)); + PLACES.add(new PlaceInfo("학생회관", new LatLng(37.496873, 126.956308), 30)); + PLACES.add(new PlaceInfo("숭덕경상관", new LatLng(37.496942, 126.954923), 30)); + PLACES.add(new PlaceInfo("백마관", new LatLng(37.497830, 126.956273), 30)); + PLACES.add(new PlaceInfo("벤처관", new LatLng(37.497544, 126.957438), 30)); + PLACES.add(new PlaceInfo("진리관", new LatLng(37.496906, 126.957495), 30)); + PLACES.add(new PlaceInfo("조만식기념관", new LatLng(37.497249, 126.958212), 30)); + PLACES.add(new PlaceInfo("미래관", new LatLng( 37.495610, 126.958487), 30)); + + + + + + // 필요시 추가 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/DialogFragment/AddFriendDialogFragment.java b/app/src/main/java/com/example/mohassu/DialogFragment/AddFriendDialogFragment.java new file mode 100644 index 0000000..7367d7b --- /dev/null +++ b/app/src/main/java/com/example/mohassu/DialogFragment/AddFriendDialogFragment.java @@ -0,0 +1,117 @@ +package com.example.mohassu.DialogFragment; + +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; + +import com.example.mohassu.R; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.GeoPoint; + +import java.util.HashMap; +import java.util.Map; + +public class AddFriendDialogFragment extends DialogFragment { + + private EditText emailInput; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.dialog_add_friend, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + emailInput = view.findViewById(R.id.emailInput); // 이메일 입력 필드 + + // "친구 추가" 버튼 클릭 리스너 + view.findViewById(R.id.btnAddFriend).setOnClickListener(v -> addFriend()); + + // "취소" 버튼 클릭 리스너 + view.findViewById(R.id.btnCancelFriend).setOnClickListener(v -> dismiss()); + } + + private void addFriend() { + String email = emailInput.getText().toString().trim(); + + if (TextUtils.isEmpty(email)) { + Toast.makeText(requireContext(), "이메일을 입력해주세요.", Toast.LENGTH_SHORT).show(); + return; + } + + FirebaseAuth auth = FirebaseAuth.getInstance(); + FirebaseFirestore db = FirebaseFirestore.getInstance(); + + String currentUserId = auth.getCurrentUser().getUid(); // 현재 로그인한 사용자 UID + + // 이메일로 사용자 검색 + //추가한 코드: 다른 데이터(좌표, 이름 등) + db.collection("users") + .whereEqualTo("email", email) + .get() + .addOnCompleteListener(task -> { + if (task.isSuccessful() && !task.getResult().isEmpty()) { + // 검색된 사용자 정보 + String friendUserId = task.getResult().getDocuments().get(0).getId(); + String name = task.getResult().getDocuments().get(0).getString("name"); + String nickname = task.getResult().getDocuments().get(0).getString("nickname"); + String photoUrl = task.getResult().getDocuments().get(0).getString("photoUrl"); + String place = task.getResult().getDocuments().get(0).getString("place"); + String statusMessage = task.getResult().getDocuments().get(0).getString("statusMessage"); + + db.collection("users").document(friendUserId) + .collection("location") + .document("currentLocation") + .get() + .addOnSuccessListener(locationDoc -> { + if (locationDoc.exists()) { + GeoPoint location = locationDoc.getGeoPoint("location"); + + if (location == null) { + location = new GeoPoint(0, 0); // 기본 위치 설정 (위도, 경도) + } // 위치 null 튕김 방지 + + + // 친구 추가 로직 + Map friendData = new HashMap<>(); + friendData.put("friendUserId", friendUserId); + friendData.put("email", email); + friendData.put("name", name); + friendData.put("nickname", nickname); + friendData.put("photoUrl", photoUrl); + friendData.put("place", place); + friendData.put("location", location); + if (statusMessage != null) + friendData.put("statusMessage", statusMessage); + + + db.collection("users").document(currentUserId) + .collection("friends").document(friendUserId) + .set(friendData) + .addOnSuccessListener(aVoid -> { + Toast.makeText(requireContext(), "친구 추가 완료!", Toast.LENGTH_SHORT).show(); + dismiss(); // 다이얼로그 닫기 + }) + .addOnFailureListener(e -> { + Toast.makeText(requireContext(), "친구 추가 실패: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + }); + } else { + Toast.makeText(requireContext(), "사용자를 찾을 수 없습니다.", Toast.LENGTH_SHORT).show(); + } + }); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/DialogFragment/ClassAddDialogFragment.java b/app/src/main/java/com/example/mohassu/DialogFragment/ClassAddDialogFragment.java index 70a2a46..fd9acd1 100644 --- a/app/src/main/java/com/example/mohassu/DialogFragment/ClassAddDialogFragment.java +++ b/app/src/main/java/com/example/mohassu/DialogFragment/ClassAddDialogFragment.java @@ -19,14 +19,15 @@ public class ClassAddDialogFragment extends DialogFragment { - private TextView selectedLocationButton = null; + private TextView selectedLocationButton = null; // 강의 장소 선택 로직을 위해서 정의 + private String selectedClassPlace = ""; // 선택된 강의 장소를 저장하는 변수 + public interface OnClassAddedListener { void onClassAdded(String className, String classPlace, int day, int startHour, int startMinute, int endHour, int endMinute); } private OnClassAddedListener listener; - private String selectedClassPlace = ""; // 선택된 강의 장소를 저장하는 변수 public void setOnClassAddedListener(OnClassAddedListener listener) { this.listener = listener; diff --git a/app/src/main/java/com/example/mohassu/DialogFragment/ClassEditDialogFragment.java b/app/src/main/java/com/example/mohassu/DialogFragment/ClassEditDialogFragment.java deleted file mode 100644 index 86c5c7d..0000000 --- a/app/src/main/java/com/example/mohassu/DialogFragment/ClassEditDialogFragment.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.example.mohassu.DialogFragment; - -public class ClassEditDialogFragment { -} diff --git a/app/src/main/java/com/example/mohassu/DialogFragment/ClassEditOrDeleteDialogFragment.java b/app/src/main/java/com/example/mohassu/DialogFragment/ClassEditOrDeleteDialogFragment.java new file mode 100644 index 0000000..e35d8c5 --- /dev/null +++ b/app/src/main/java/com/example/mohassu/DialogFragment/ClassEditOrDeleteDialogFragment.java @@ -0,0 +1,193 @@ +package com.example.mohassu.DialogFragment; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.TimePicker; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; + +import com.example.mohassu.R; +import com.github.tlaabs.timetableview.Schedule; +import com.github.tlaabs.timetableview.Time; + +public class ClassEditOrDeleteDialogFragment extends DialogFragment { + + private TextView selectedLocationButton = null; // 강의 장소 선택 로직을 위해서 정의 + private String selectedClassPlace = ""; // 선택된 강의 장소를 저장하는 변수 + + public interface OnClassEditOrDeleteListener { + void onEdit(Schedule editedSchedule); + void onDelete(); + } + + private OnClassEditOrDeleteListener listener; + + public void setOnClassEditOrDeleteListener(OnClassEditOrDeleteListener listener) { + this.listener = listener; + } + + @Override + public int getTheme() { + return R.style.CustomDialogStyle; + } + + @Override + public void onStart() { + super.onStart(); + if (getDialog() != null && getDialog().getWindow() != null) { + getDialog().getWindow().setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + } + } + + public static ClassEditOrDeleteDialogFragment newInstance(Schedule existingSchedule) { + ClassEditOrDeleteDialogFragment fragment = new ClassEditOrDeleteDialogFragment(); + Bundle args = new Bundle(); + args.putSerializable("schedule", existingSchedule); + fragment.setArguments(args); + return fragment; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.dialog_class_edit_or_delete, container, false); + + EditText classTitle = view.findViewById(R.id.input_class_name); + Spinner spinnerDay = view.findViewById(R.id.spinner_day); + Spinner spinnerStartTime = view.findViewById(R.id.start_time); + Spinner spinnerEndTime = view.findViewById(R.id.end_time); + EditText inputClassPlace = view.findViewById(R.id.input_class_place); + Button editSaveButton = view.findViewById(R.id.btn_edit_save); + Button deleteButton = view.findViewById(R.id.btn_delete); + ImageButton closeButton = view.findViewById(R.id.btn_close); + LinearLayout horizontalContainer = view.findViewById(R.id.horizontal_spinner_container); + + Schedule existingSchedule = new Schedule(); + existingSchedule = (Schedule) getArguments().getSerializable("schedule"); + + // 닫기 버튼 동작 + closeButton.setOnClickListener(v -> dismiss()); + + // strings.xml에서 강의 장소 배열 가져오기 + String[] locations = getResources().getStringArray(R.array.locations); + + // 동적으로 버튼 추가 + for (String location : locations) { + TextView locationButton = new TextView(requireContext()); + locationButton.setText(location); + locationButton.setBackgroundResource(R.drawable.background_banner_white); // 기본 배경 + locationButton.setPadding(16, 8, 16, 8); + locationButton.setTextColor(getResources().getColor(android.R.color.black)); + locationButton.setGravity(View.TEXT_ALIGNMENT_CENTER); + + // 레이아웃 매개변수 설정 + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ); + params.setMargins(8, 0, 8, 0); // 버튼 간격 + locationButton.setLayoutParams(params); + + // 버튼 클릭 이벤트 + locationButton.setOnClickListener(v -> { + // 이전에 선택된 버튼이 있는 경우, 상태 해제 + if (selectedLocationButton != null && selectedLocationButton != locationButton) { + selectedLocationButton.setBackgroundResource(R.drawable.background_banner_white); // 기본 배경 + selectedLocationButton.setTextColor(getResources().getColor(android.R.color.black)); // 기본 텍스트 색상 + } + + // 현재 버튼을 선택 상태로 설정 + selectedLocationButton = locationButton; + selectedLocationButton.setBackgroundResource(R.drawable.background_banner_selected); // 선택된 배경 + selectedLocationButton.setTextColor(getResources().getColor(android.R.color.white)); // 선택된 텍스트 색상 + + // 선택된 장소를 저장 + selectedClassPlace = location; + inputClassPlace.setText(location); // EditText에 설정 + }); + + // 이전에 저장된 장소 선택 + if (existingSchedule != null && location.equals(existingSchedule.getClassPlace())) { + selectedLocationButton = locationButton; + selectedLocationButton.setBackgroundResource(R.drawable.background_banner_selected); + selectedLocationButton.setTextColor(getResources().getColor(android.R.color.white)); + } + + horizontalContainer.addView(locationButton); + } + + // 기존 데이터 설정 + if (existingSchedule != null) { + classTitle.setText(existingSchedule.getClassTitle()); + inputClassPlace.setText(existingSchedule.getClassPlace()); + + // Day 스피너 설정 (0: Monday, 1: Tuesday, ...) + spinnerDay.setSelection(existingSchedule.getDay()); + + // Start Time 설정 + String startTime = String.format("%02d:%02d", existingSchedule.getStartTime().getHour(), existingSchedule.getStartTime().getMinute()); + setSpinnerSelection(spinnerStartTime, startTime); + + // End Time 설정 + String endTime = String.format("%02d:%02d", existingSchedule.getEndTime().getHour(), existingSchedule.getEndTime().getMinute()); + setSpinnerSelection(spinnerEndTime, endTime); + } + + // 수정 버튼 동작 + editSaveButton.setOnClickListener(v -> { + if (listener != null) { + String classTitleText = classTitle.getText().toString(); + String classPlaceText = inputClassPlace.getText().toString(); + int day = spinnerDay.getSelectedItemPosition(); + int startHour = Integer.parseInt(spinnerStartTime.getSelectedItem().toString().split(":")[0]); + int startMinute = Integer.parseInt(spinnerStartTime.getSelectedItem().toString().split(":")[1]); + int endHour = Integer.parseInt(spinnerEndTime.getSelectedItem().toString().split(":")[0]); + int endMinute = Integer.parseInt(spinnerEndTime.getSelectedItem().toString().split(":")[1]); + + // 수정된 Schedule 객체 생성 + Schedule editedSchedule = new Schedule(); + editedSchedule.setClassTitle(classTitleText); + editedSchedule.setClassPlace(classPlaceText); + editedSchedule.setDay(day); + editedSchedule.setStartTime(new Time(startHour, startMinute)); + editedSchedule.setEndTime(new Time(endHour, endMinute)); + + // 수정된 데이터를 listener를 통해 전달 + listener.onEdit(editedSchedule); + } + dismiss(); + }); + + // 삭제 버튼 동작 + deleteButton.setOnClickListener(v -> { + if (listener != null) { + listener.onDelete(); + } + dismiss(); + }); + + return view; + } + + private void setSpinnerSelection(Spinner spinner, String value) { + for (int i = 0; i < spinner.getCount(); i++) { + if (spinner.getItemAtPosition(i).toString().equals(value)) { + spinner.setSelection(i); + break; + } + } + } +} diff --git a/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/Signup1IDAndPWFragment.java b/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/Signup1IDAndPWFragment.java index 50f4e54..118ee2b 100644 --- a/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/Signup1IDAndPWFragment.java +++ b/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/Signup1IDAndPWFragment.java @@ -2,6 +2,8 @@ import android.os.Bundle; import android.text.TextUtils; +import android.util.Log; +import android.util.Patterns; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -16,21 +18,20 @@ import androidx.navigation.Navigation; import com.example.mohassu.R; +import com.google.firebase.auth.ActionCodeSettings; import com.google.firebase.auth.FirebaseAuth; -import com.google.firebase.database.DataSnapshot; -import com.google.firebase.database.DatabaseReference; -import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.auth.FirebaseUser; public class Signup1IDAndPWFragment extends Fragment { private FirebaseAuth auth; // Firebase Auth 인스턴스 - private DatabaseReference databaseReference; // Firebase Database Reference - private EditText etId, etPassword, etConfirmPassword; - private TextView tvIdError, tvPasswordError; - private Button btnCheckId, btnSignupNext; + private EditText etEmail, etPassword, etConfirmPassword; + private TextView tvEmailError, tvPasswordError; + private Button btnCheckEmail, btnSignupNext,btnVerifyEmail; - private boolean isIdChecked = false; // 아이디 중복 검사 여부 + private boolean isEmailChecked = false; // 이메일 중복 검사 여부 + private ActionCodeSettings actionCodeSettings; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -51,68 +52,90 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { // Firebase 초기화 auth = FirebaseAuth.getInstance(); - databaseReference = FirebaseDatabase.getInstance().getReference("users"); // Firebase Database 경로 // UI 요소 초기화 - etId = view.findViewById(R.id.etId); + etEmail = view.findViewById(R.id.etEmail); // 이메일 입력 필드 etPassword = view.findViewById(R.id.etPassword); etConfirmPassword = view.findViewById(R.id.etConfirmPassword); - tvIdError = view.findViewById(R.id.tvIdError); + tvEmailError = view.findViewById(R.id.tvEmaildError); tvPasswordError = view.findViewById(R.id.tvPasswordError); - btnCheckId = view.findViewById(R.id.btnCheckId); + + + btnCheckEmail = view.findViewById(R.id.btnCheckEmail); + btnVerifyEmail = view.findViewById(R.id.btnSendVerification); btnSignupNext = view.findViewById(R.id.btnNext); - // 아이디 중복 검사 - btnCheckId.setOnClickListener(v -> checkIdDuplication()); + // 이메일 중복 및 유효성 검사 버튼 클릭 리스너 + btnCheckEmail.setOnClickListener(v -> checkEmailDuplication()); - // 회원가입 버튼 클릭 - btnSignupNext.setOnClickListener(v -> registerUser()); + // 회원가입 버튼 클릭 리스너 + btnVerifyEmail.setOnClickListener(v -> registerUser()); } - private void checkIdDuplication() { - String userId = etId.getText().toString().trim(); + private void checkEmailDuplication() { + String email = etEmail.getText().toString().trim(); + String dummyPassword = "DummyPassword123"; // 테스트용 비밀번호 (중복 검사에만 사용) - if (TextUtils.isEmpty(userId)) { - tvIdError.setText("아이디를 입력해주세요."); - tvIdError.setVisibility(View.VISIBLE); + // 이메일 형식 유효성 검사 + if (!isValidEmail(email)) { + tvEmailError.setText("유효한 이메일 주소를 입력해주세요."); + tvEmailError.setVisibility(View.VISIBLE); return; } - databaseReference.child(userId).get().addOnCompleteListener(task -> { - if (task.isSuccessful()) { - DataSnapshot dataSnapshot = task.getResult(); - if (dataSnapshot.exists()) { - tvIdError.setText("이미 사용 중인 아이디입니다."); - tvIdError.setVisibility(View.VISIBLE); - isIdChecked = false; - } else { - tvIdError.setVisibility(View.GONE); - Toast.makeText(requireContext(), "사용 가능한 아이디입니다.", Toast.LENGTH_SHORT).show(); - isIdChecked = true; - } - } else { - Toast.makeText(requireContext(), "아이디 중복 검사 실패: " + task.getException().getMessage(), Toast.LENGTH_SHORT).show(); - } - }); + // Firebase Authentication에서 중복 이메일 검사 + auth.createUserWithEmailAndPassword(email, dummyPassword) + .addOnCompleteListener(task -> { + if (task.isSuccessful()) { + // 중복되지 않은 이메일 -> 테스트 계정 삭제 + auth.getCurrentUser().delete() + .addOnCompleteListener(deleteTask -> { + if (deleteTask.isSuccessful()) { + tvEmailError.setVisibility(View.GONE); + Toast.makeText(requireContext(), "사용 가능한 이메일입니다.", Toast.LENGTH_SHORT).show(); + isEmailChecked = true; + } + }); + } else { + // 중복된 이메일 + if (task.getException() != null && task.getException().getMessage().contains("email address is already in use")) { + tvEmailError.setText("이미 사용 중인 이메일입니다."); + tvEmailError.setVisibility(View.VISIBLE); + isEmailChecked = false; + } else { + Toast.makeText(requireContext(), "중복 검사 실패: " + task.getException().getMessage(), Toast.LENGTH_SHORT).show(); + } + } + }); } + private void registerUser() { - String userId = etId.getText().toString().trim(); + String email = etEmail.getText().toString().trim(); String password = etPassword.getText().toString().trim(); String confirmPassword = etConfirmPassword.getText().toString().trim(); - // 유효성 검사 - if (!isIdChecked) { - Toast.makeText(requireContext(), "아이디 중복 검사를 진행해주세요.", Toast.LENGTH_SHORT).show(); + // 이메일 형식 검사 + if (!isValidEmail(email)) { + tvEmailError.setText("유효한 이메일 주소를 입력해주세요."); + tvEmailError.setVisibility(View.VISIBLE); return; } + // 이메일 중복 검사 여부 확인 + if (!isEmailChecked) { + Toast.makeText(requireContext(), "이메일 중복 검사를 진행해주세요.", Toast.LENGTH_SHORT).show(); + return; + } + + // 비밀번호 입력 여부 확인 if (TextUtils.isEmpty(password) || TextUtils.isEmpty(confirmPassword)) { tvPasswordError.setText("비밀번호를 입력해주세요."); tvPasswordError.setVisibility(View.VISIBLE); return; } + // 비밀번호 일치 확인 if (!password.equals(confirmPassword)) { tvPasswordError.setText("비밀번호가 일치하지 않습니다."); tvPasswordError.setVisibility(View.VISIBLE); @@ -122,28 +145,41 @@ private void registerUser() { tvPasswordError.setVisibility(View.GONE); // Firebase Authentication 회원가입 - auth.createUserWithEmailAndPassword(userId + "@gmail.com", password) + auth.createUserWithEmailAndPassword(email, password) .addOnCompleteListener(requireActivity(), task -> { if (task.isSuccessful()) { - // 이메일 인증 메일 전송 + // 인증 이메일 발송 auth.getCurrentUser().sendEmailVerification() .addOnCompleteListener(emailTask -> { if (emailTask.isSuccessful()) { - Toast.makeText(requireContext(), "회원가입 성공! 이메일 인증을 완료해주세요.", Toast.LENGTH_SHORT).show(); - - // Firebase Realtime Database에 사용자 데이터 저장 - databaseReference.child(userId).setValue(userId) - .addOnCompleteListener(dbTask -> { - if (dbTask.isSuccessful()) { - // 이메일 인증 안내 화면 또는 다음 Fragment로 이동 - NavController navController = Navigation.findNavController(requireView()); - navController.navigate(R.id.actionNextToSignup2); // 적절한 Action ID로 변경 - } else { - Toast.makeText(requireContext(), "데이터 저장 실패: " + dbTask.getException().getMessage(), Toast.LENGTH_SHORT).show(); - } - }); + Toast.makeText(requireContext(), "회원가입 성공! 인증 이메일을 확인해주세요.", Toast.LENGTH_SHORT).show(); + +// // 인증 확인 버튼 활성화 +// Button btnVerifyEmail = requireView().findViewById(R.id.btnVerifyEmail); + + // 이메일 인증 확인 버튼 클릭 리스너 설정 + btnSignupNext.setOnClickListener(v -> { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + if (user != null) { + user.reload() // 사용자 정보 새로고침 + .addOnCompleteListener(reloadTask -> { + NavController navController = Navigation.findNavController(requireView()); + navController.navigate(R.id.actionNextToSignup2); // 적절한 Action ID로 변경 +// if (user.isEmailVerified()) { +// Toast.makeText(requireContext(), "이메일 인증 완료!", Toast.LENGTH_SHORT).show(); +// // 다음 Fragment로 이동 +// NavController navController = Navigation.findNavController(requireView()); +// navController.navigate(R.id.actionNextToSignup2); // 적절한 Action ID로 변경 +// } else { +// Toast.makeText(requireContext(), "이메일 인증을 완료해주세요!", Toast.LENGTH_SHORT).show(); +// } + }); + } else { + Toast.makeText(requireContext(), "사용자 정보를 확인할 수 없습니다.", Toast.LENGTH_SHORT).show(); + } + }); } else { - Toast.makeText(requireContext(), "인증 이메일 전송 실패: " + emailTask.getException().getMessage(), Toast.LENGTH_SHORT).show(); + Toast.makeText(requireContext(), "인증 이메일 발송 실패: " + emailTask.getException().getMessage(), Toast.LENGTH_SHORT).show(); } }); } else { @@ -151,4 +187,9 @@ private void registerUser() { } }); } + + // 유효한 이메일 형식인지 확인 + private boolean isValidEmail(String email) { + return !TextUtils.isEmpty(email) && Patterns.EMAIL_ADDRESS.matcher(email).matches(); + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/Signup2DetailFragment.java b/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/Signup2DetailFragment.java index b19a074..5282afe 100644 --- a/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/Signup2DetailFragment.java +++ b/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/Signup2DetailFragment.java @@ -1,6 +1,131 @@ +//package com.example.mohassu.LoginAndSignUpFragment; +// +//import android.os.Bundle; +//import android.view.LayoutInflater; +//import android.view.View; +//import android.view.ViewGroup; +//import android.widget.Button; +//import android.widget.DatePicker; +//import android.widget.EditText; +//import android.widget.Toast; +// +//import androidx.annotation.NonNull; +//import androidx.fragment.app.Fragment; +//import androidx.navigation.NavController; +//import androidx.navigation.Navigation; +// +//import com.example.mohassu.R; +//import com.google.firebase.auth.FirebaseAuth; +//import com.google.firebase.database.DatabaseReference; +//import com.google.firebase.database.FirebaseDatabase; +// +//public class Signup2DetailFragment extends Fragment { +// +// private EditText etNickname, etName; +// private DatePicker dpBirthdate; +// private Button signupNextButton; +// +// private FirebaseAuth mAuth; +// private DatabaseReference databaseReference; +// +//// @Override +//// protected void onCreate(Bundle savedInstanceState) { +//// super.onCreate(savedInstanceState); +//// Setcon +//// +//// //androidx.fragment.app.FragmentManager +//// //androidx.fragment.app.FragmentTransaction +//// +//// } +// +// @Override +// public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { +// +// return inflater.inflate(R.layout.fragment_sign_up2, container, false); +// } +// +// @Override +// public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { +// super.onViewCreated(view, savedInstanceState); +// +//// getParentFragmentManager() +//// .beginTransaction() +//// .replace(R.id.main,new Signup3ProfileFragment()) +//// .commit(); +// +// // NavController 초기화 +// NavController navController = Navigation.findNavController(view); +// +// // 뒤로가기 버튼에 클릭 리스너 추가 +// view.findViewById(R.id.btnBack).setOnClickListener(v -> { +// navController.navigateUp(); +// }); +// +// // Firebase 초기화 +// mAuth = FirebaseAuth.getInstance(); +// databaseReference = FirebaseDatabase.getInstance().getReference("users"); +// +// // UI 초기화 +// etNickname = view.findViewById(R.id.etNickname); +// etName = view.findViewById(R.id.etName); +// dpBirthdate = view.findViewById(R.id.dpSpinner); +// signupNextButton = view.findViewById(R.id.btnNext); +// +// signupNextButton.setOnClickListener(v -> saveUserProfile(view)); +// } +// +// private void saveUserProfile(View view) { +// String nickname = etNickname.getText().toString().trim(); +// String name = etName.getText().toString().trim(); +// int day = dpBirthdate.getDayOfMonth(); +// int month = dpBirthdate.getMonth() + 1; // Month is 0-based in DatePicker +// int year = dpBirthdate.getYear(); +// String birthdate = year + "-" + month + "-" + day; +// +// // 필드 유효성 검사 +// if (nickname.isEmpty() || name.isEmpty()) { +// Toast.makeText(requireContext(), "모든 필드를 입력해주세요.", Toast.LENGTH_SHORT).show(); +// return; +// } +// +// // Firebase Realtime Database에 저장 +// String uid = mAuth.getCurrentUser().getUid(); +// UserProfile userProfile = new UserProfile(nickname, name, birthdate); +// +// databaseReference.child(uid).setValue(userProfile) +// .addOnCompleteListener(task -> { +// if (task.isSuccessful()) { +// Toast.makeText(requireContext(), "프로필 저장 성공!", Toast.LENGTH_SHORT).show(); +// +// // 다음 Fragment로 이동 +// NavController navController = Navigation.findNavController(requireView()); +// navController.navigate(R.id.actionNextToSignup3); // 적절한 Action ID로 변경 +// } else { +// Toast.makeText(requireContext(), "프로필 저장 실패: " + task.getException().getMessage(), Toast.LENGTH_SHORT).show(); +// } +// }); +// } +// +// public static class UserProfile { +// public String nickname; +// public String name; +// public String birthdate; +// +// public UserProfile() { +// // 기본 생성자 +// } +// +// public UserProfile(String nickname, String name, String birthdate) { +// this.nickname = nickname; +// this.name = name; +// this.birthdate = birthdate; +// } +// } +//} package com.example.mohassu.LoginAndSignUpFragment; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -16,17 +141,21 @@ import com.example.mohassu.R; import com.google.firebase.auth.FirebaseAuth; -import com.google.firebase.database.DatabaseReference; -import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.firestore.FirebaseFirestore; + +import java.util.HashMap; +import java.util.Map; public class Signup2DetailFragment extends Fragment { + private static final String TAG = "Signup2DetailFragment"; + private EditText etNickname, etName; private DatePicker dpBirthdate; private Button signupNextButton; private FirebaseAuth mAuth; - private DatabaseReference databaseReference; + private FirebaseFirestore db; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -37,17 +166,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - // NavController 초기화 - NavController navController = Navigation.findNavController(view); - - // 뒤로가기 버튼에 클릭 리스너 추가 - view.findViewById(R.id.btnBack).setOnClickListener(v -> { - navController.navigateUp(); - }); - // Firebase 초기화 mAuth = FirebaseAuth.getInstance(); - databaseReference = FirebaseDatabase.getInstance().getReference("users"); + db = FirebaseFirestore.getInstance(); // UI 초기화 etNickname = view.findViewById(R.id.etNickname); @@ -55,6 +176,7 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { dpBirthdate = view.findViewById(R.id.dpSpinner); signupNextButton = view.findViewById(R.id.btnNext); + // 다음 버튼 클릭 리스너 signupNextButton.setOnClickListener(v -> saveUserProfile(view)); } @@ -72,37 +194,29 @@ private void saveUserProfile(View view) { return; } - // Firebase Realtime Database에 저장 + // 현재 사용자 UID 가져오기 String uid = mAuth.getCurrentUser().getUid(); - UserProfile userProfile = new UserProfile(nickname, name, birthdate); - - databaseReference.child(uid).setValue(userProfile) - .addOnCompleteListener(task -> { - if (task.isSuccessful()) { - Toast.makeText(requireContext(), "프로필 저장 성공!", Toast.LENGTH_SHORT).show(); - - // 다음 Fragment로 이동 - NavController navController = Navigation.findNavController(requireView()); - navController.navigate(R.id.actionNextToSignup3); // 적절한 Action ID로 변경 - } else { - Toast.makeText(requireContext(), "프로필 저장 실패: " + task.getException().getMessage(), Toast.LENGTH_SHORT).show(); - } + String email = mAuth.getCurrentUser().getEmail(); + + // Firestore에 사용자 정보 저장 + Map userProfile = new HashMap<>(); + userProfile.put("email",email); + userProfile.put("nickname", nickname); + userProfile.put("name", name); + userProfile.put("birthDate", birthdate); + + db.collection("users").document(uid) + .set(userProfile) + .addOnSuccessListener(aVoid -> { + Toast.makeText(requireContext(), "프로필 저장 성공!", Toast.LENGTH_SHORT).show(); + + // 다음 프래그먼트로 이동 + NavController navController = Navigation.findNavController(view); + navController.navigate(R.id.actionNextToSignup3); // 적절한 Action ID로 변경 + }) + .addOnFailureListener(e -> { + Toast.makeText(requireContext(), "Firestore 저장 실패: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + Log.e(TAG, "Firestore Error", e); }); } - - public static class UserProfile { - public String nickname; - public String name; - public String birthdate; - - public UserProfile() { - // 기본 생성자 - } - - public UserProfile(String nickname, String name, String birthdate) { - this.nickname = nickname; - this.name = name; - this.birthdate = birthdate; - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/Signup3ProfileFragment.java b/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/Signup3ProfileFragment.java index 79c0ce4..50e3258 100644 --- a/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/Signup3ProfileFragment.java +++ b/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/Signup3ProfileFragment.java @@ -20,8 +20,15 @@ import androidx.navigation.Navigation; import com.example.mohassu.R; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.storage.FirebaseStorage; +import com.google.firebase.storage.StorageReference; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; public class Signup3ProfileFragment extends Fragment { @@ -32,6 +39,9 @@ public class Signup3ProfileFragment extends Fragment { private Button signupNextButton, skipButton; private Uri selectedImageUri; // 선택된 이미지의 URI 저장 + private FirebaseStorage storage; + private FirebaseFirestore db; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_sign_up3, container, false); @@ -41,6 +51,10 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + // Firebase 초기화 + storage = FirebaseStorage.getInstance(); + db = FirebaseFirestore.getInstance(); + // NavController 초기화 NavController navController = Navigation.findNavController(view); @@ -61,13 +75,11 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat // 다음 버튼 클릭 리스너 signupNextButton.setOnClickListener(v -> { if (selectedImageUri != null) { - // 선택된 이미지 URI를 저장하거나 업로드 로직 추가 - Toast.makeText(requireContext(), "프로필이 저장되었습니다!", Toast.LENGTH_SHORT).show(); + // 프로필 사진 업로드 + uploadProfileImage(navController); } else { Toast.makeText(requireContext(), "프로필을 선택하지 않았습니다.", Toast.LENGTH_SHORT).show(); } - // 다음 Fragment로 이동 - navController.navigate(R.id.actionNextToSignup4); }); // 건너뛰기 버튼 클릭 리스너 @@ -100,4 +112,33 @@ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent d } } } + + // Firebase Storage에 프로필 사진 업로드 + private void uploadProfileImage(NavController navController) { + if (selectedImageUri != null) { + String userId = FirebaseAuth.getInstance().getCurrentUser().getUid(); + StorageReference storageRef = storage.getReference().child("profilePictures/" + UUID.randomUUID().toString()); + + storageRef.putFile(selectedImageUri) + .addOnSuccessListener(taskSnapshot -> storageRef.getDownloadUrl().addOnSuccessListener(uri -> { + // Firestore에 사진 URL 저장 + Map updates = new HashMap<>(); + updates.put("photoUrl", uri.toString()); + + db.collection("users").document(userId) + .update(updates) + .addOnSuccessListener(aVoid -> { + Toast.makeText(requireContext(), "프로필 사진 저장 성공!", Toast.LENGTH_SHORT).show(); + // 다음 Fragment로 이동 + navController.navigate(R.id.actionNextToSignup4); + }) + .addOnFailureListener(e -> { + Toast.makeText(requireContext(), "Firestore 저장 실패: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + }); + })) + .addOnFailureListener(e -> { + Toast.makeText(requireContext(), "사진 업로드 실패: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + }); + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/Signup4TimeTableFragment.java b/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/Signup4TimeTableFragment.java index 8906c1f..77b31ca 100644 --- a/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/Signup4TimeTableFragment.java +++ b/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/Signup4TimeTableFragment.java @@ -1,29 +1,55 @@ package com.example.mohassu.LoginAndSignUpFragment; +import android.content.Context; +import android.content.SharedPreferences; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.navigation.NavController; import androidx.navigation.Navigation; import com.example.mohassu.DialogFragment.ClassAddDialogFragment; -import com.example.mohassu.DialogFragment.ClassEditDialogFragment; +import com.example.mohassu.DialogFragment.ClassEditOrDeleteDialogFragment; import com.example.mohassu.R; +import com.github.tlaabs.timetableview.Schedule; +import com.github.tlaabs.timetableview.Time; +import com.github.tlaabs.timetableview.TimetableView; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.firestore.FirebaseFirestore; + +import java.util.ArrayList; +import java.util.HashMap; public class Signup4TimeTableFragment extends Fragment { + + private static final String PREFS_NAME = "TimetablePrefs"; //회원가입1에서 받은 이메일을 넣어주면 될 듯 -> 지금은 그냥 휴대폰의 아무유저나 다 동일하게 되어있음.. + private static final String TIMETABLE_KEY = "timetable"; + + private TimetableView timetable; + private FirebaseFirestore db; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_sign_up4, container, false); } @Override - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + db = FirebaseFirestore.getInstance(); + timetable = view.findViewById(R.id.timetable); + + // Firestore에서 시간표 불러오기 + loadTimetableFromFirestoreStructured(); + // NavController 초기화 NavController navController = Navigation.findNavController(view); @@ -35,20 +61,199 @@ public void onViewCreated(View view, Bundle savedInstanceState) { // 수업 추가하기 dialog view.findViewById(R.id.btnAddClass).setOnClickListener(v -> { ClassAddDialogFragment classAddDialogFragment = new ClassAddDialogFragment(); + classAddDialogFragment.setOnClassAddedListener((className, classPlace, day, startHour, startMinute, endHour, endMinute) -> { + // Validate inputs + if (className.isEmpty() || classPlace.isEmpty() || startHour > endHour || (startHour == endHour && startMinute >= endMinute)) { + if (className.isEmpty() || classPlace.isEmpty()) { + Toast.makeText(requireContext(), "전부 입력해주세요!", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(requireContext(), "시작 시간이 종료 시간보다 빠르거나 같아야 합니다.", Toast.LENGTH_SHORT).show(); + } + } else { + addScheduleToTimetable(className, classPlace, day, startHour, startMinute, endHour, endMinute); + } + }); classAddDialogFragment.show(requireActivity().getSupportFragmentManager(), "ClassAddDialog"); }); - // 다음 프레그먼트를 클릭 시 다음 Fragment로 이동 + timetable.setOnStickerSelectEventListener((idx, schedules) -> { + // 다이얼로그 프래그먼트 호출 + ClassEditOrDeleteDialogFragment classEditOrDeleteDialogFragment = ClassEditOrDeleteDialogFragment.newInstance(schedules.get(0)); + Log.d("StickerSelectEvent", "schedule idx : " + idx); + classEditOrDeleteDialogFragment.setOnClassEditOrDeleteListener(new ClassEditOrDeleteDialogFragment.OnClassEditOrDeleteListener() { + @Override + public void onEdit(Schedule editedSchedule) { + // 기존 스티커 수정 + ArrayList updatedSchedules = new ArrayList<>(); + updatedSchedules.add(editedSchedule); + timetable.edit(idx, updatedSchedules); + Toast.makeText(requireContext(), "수업 정보가 수정되었습니다.", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onDelete() { + // 스티커 삭제 + timetable.remove(idx); + Toast.makeText(requireContext(), "수업 정보가 삭제되었습니다.", Toast.LENGTH_SHORT).show(); + } + }); + classEditOrDeleteDialogFragment.show(requireActivity().getSupportFragmentManager(), "ClassEditDialog"); + }); + + // 다음 버튼 Button signupNextButton = view.findViewById(R.id.btnNext); signupNextButton.setFocusable(false); signupNextButton.setOnClickListener(v -> { - navController.navigate(R.id.actionMoveToLogin); + saveTimetableToFirestoreStructured(); + saveTimetable(); + navController.navigate(R.id.actionNextToSignupDone); }); + // 건너뛰기 버튼 Button signupSkipButton = view.findViewById(R.id.btnSkip); signupSkipButton.setFocusable(false); signupSkipButton.setOnClickListener(v -> { - navController.navigate(R.id.actionMoveToLogin); + navController.navigate(R.id.actionSkipToSignupDone); }); } -} + + private void addScheduleToTimetable(String className, String classPlace, int day, int startHour, int startMinute, int endHour, int endMinute) { + ArrayList schedules = new ArrayList<>(); + + Schedule schedule = new Schedule(); + schedule.setClassTitle(className); + schedule.setClassPlace(classPlace); + schedule.setDay(day); + schedule.setStartTime(new Time(startHour, startMinute)); + schedule.setEndTime(new Time(endHour, endMinute)); + + schedules.add(schedule); + + timetable.add(schedules); + } + + private void saveTimetable() { + SharedPreferences prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + + String json = timetable.createSaveData(); + editor.putString(TIMETABLE_KEY, json); + editor.apply(); + + Toast.makeText(requireContext(), "시간표가 저장되었습니다.", Toast.LENGTH_SHORT).show(); + } + + private void saveTimetableToFirestoreStructured() { + String userId = FirebaseAuth.getInstance().getCurrentUser().getUid(); + String json = timetable.createSaveData(); + FirebaseFirestore db = FirebaseFirestore.getInstance(); + + // Firestore에 저장 + db.collection("users").document(userId) + .update(new HashMap() {{ + put("timetableData", json); + }}) + .addOnSuccessListener(aVoid -> { + Toast.makeText(requireContext(), "Firestore에 시간표가 저장되었습니다.", Toast.LENGTH_SHORT).show(); + }) + .addOnFailureListener(e -> { + Toast.makeText(requireContext(), "시간표 저장 실패: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + }); + + // 시간표 데이터를 가져오기 + ArrayList schedules = timetable.getAllSchedulesInStickers(); + + for (Schedule schedule : schedules) { + HashMap scheduleMap = new HashMap<>(); + scheduleMap.put("classTitle", schedule.getClassTitle()); + scheduleMap.put("classPlace", schedule.getClassPlace()); + scheduleMap.put("professorName", schedule.getProfessorName()); + scheduleMap.put("day", schedule.getDay()); + scheduleMap.put("startTime", new HashMap() {{ + put("hour", schedule.getStartTime().getHour()); + put("minute", schedule.getStartTime().getMinute()); + }}); + scheduleMap.put("endTime", new HashMap() {{ + put("hour", schedule.getEndTime().getHour()); + put("minute", schedule.getEndTime().getMinute()); + }}); + + // Firestore에 저장 + db.collection("users") + .document(userId) + .collection("timetable") + .add(scheduleMap) + .addOnSuccessListener(aVoid -> Log.d("Firestore", "수업 저장 성공")) + .addOnFailureListener(e -> Log.e("Firestore", "수업 저장 실패: " + e.getMessage())); + } + + Toast.makeText(requireContext(), "Firestore에 시간표가 저장되었습니다.", Toast.LENGTH_SHORT).show(); + } + + private void loadTimetableFromFirestoreStructured() { + String userId = FirebaseAuth.getInstance().getCurrentUser().getUid(); +// +// +// db에서 파싱한 data를 다시 json형식으로 합쳐서 표현하는 방법 +// db.collection("users") +// .document(userId) +// .collection("timetable") +// .get() +// .addOnSuccessListener(querySnapshot -> { +// ArrayList schedules = new ArrayList<>(); +// querySnapshot.forEach(document -> { +// HashMap data = (HashMap) document.getData(); +// +// Schedule schedule = new Schedule(); +// schedule.setClassTitle((String) data.get("classTitle")); +// schedule.setClassPlace((String) data.get("classPlace")); +// schedule.setProfessorName((String) data.get("professorName")); +// schedule.setDay(((Long) data.get("day")).intValue()); +// +// HashMap startTime = (HashMap) data.get("startTime"); +// schedule.setStartTime(new Time(((Long) startTime.get("hour")).intValue(), ((Long) startTime.get("minute")).intValue())); +// +// HashMap endTime = (HashMap) data.get("endTime"); +// schedule.setEndTime(new Time(((Long) endTime.get("hour")).intValue(), ((Long) endTime.get("minute")).intValue())); +// +// schedules.add(schedule); +// }); +// +// // 시간표에 로드 +// timetable.add(schedules); +// Toast.makeText(requireContext(), "Firestore에서 시간표를 불러왔습니다.", Toast.LENGTH_SHORT).show(); +// }) +// .addOnFailureListener(e -> { +// Toast.makeText(requireContext(), "시간표 불러오기 실패: " + e.getMessage(), Toast.LENGTH_SHORT).show(); +// }); + +// db에 json을 그냥 불러와서 사용하는 방법 +// db.collection("users") +// .document(userId) +// .get() +// .addOnSuccessListener(documentSnapshot -> { +// if (documentSnapshot.exists()) { +// // 특정 필드 값 가져오기 +// String json = documentSnapshot.getString("timetableData"); // "name" 필드 +// // 사용 +// if (json != null) { +// timetable.load(json); +// //Toast.makeText(requireContext(), "저장된 시간표를 불러왔습니다.", Toast.LENGTH_SHORT).show(); +// } +// } else { +// Log.d("Firestore", "Document does not exist."); +// } +// }) +// .addOnFailureListener(e -> { +// Log.e("Firestore", "Error fetching document", e); +// }); +// local에 저장하고 사용하는 방법 +// SharedPreferences prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); +// String json = prefs.getString(TIMETABLE_KEY, null); +// +// if (json != null) { +// timetable.load(json); +// //Toast.makeText(requireContext(), "저장된 시간표를 불러왔습니다.", Toast.LENGTH_SHORT).show(); +// } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/SignupDoneFragment.java b/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/SignupDoneFragment.java index 3b4efb0..77d3300 100644 --- a/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/SignupDoneFragment.java +++ b/app/src/main/java/com/example/mohassu/LoginAndSignUpFragment/SignupDoneFragment.java @@ -30,7 +30,7 @@ public void onViewCreated(View view, Bundle savedInstanceState) { Button signupNextButton = view.findViewById(R.id.btnGoToLogin); signupNextButton.setFocusable(false); signupNextButton.setOnClickListener(v -> { - navController.navigate(R.id.btnGoToLogin); + navController.navigate(R.id.actionMoveToLogin); }); } } \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/MainFragment/EmptyBottomSheetProfile.java b/app/src/main/java/com/example/mohassu/MainFragment/EmptyBottomSheetProfile.java new file mode 100644 index 0000000..ffa547a --- /dev/null +++ b/app/src/main/java/com/example/mohassu/MainFragment/EmptyBottomSheetProfile.java @@ -0,0 +1,94 @@ +package com.example.mohassu.MainFragment; + +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.navigation.NavController; +import androidx.navigation.Navigation; + +import com.bumptech.glide.Glide; +import com.example.mohassu.R; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.firestore.DocumentSnapshot; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.QueryDocumentSnapshot; + +public class EmptyBottomSheetProfile extends BottomSheetDialogFragment { + + private static final String ARG_FRIEND_ID = "friend_id"; + FirebaseAuth auth = FirebaseAuth.getInstance(); + FirebaseFirestore db = FirebaseFirestore.getInstance(); + FirebaseUser currentUser = auth.getCurrentUser(); + + public static EmptyBottomSheetProfile newInstance(String friendId) { + EmptyBottomSheetProfile fragment = new EmptyBottomSheetProfile(); + Bundle args = new Bundle(); + args.putString(ARG_FRIEND_ID, friendId); // 데이터를 Bundle에 넣어서 전달 + fragment.setArguments(args); + return fragment; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_bottom_sheet_check_profile, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + ImageButton closeButton = view.findViewById(R.id.btn_close); + if (closeButton != null) { + closeButton.setOnClickListener(v -> dismiss()); + } + + String friendId = getArguments() != null ? getArguments().getString(ARG_FRIEND_ID) : null; + + if (currentUser != null) { + String uid = currentUser.getUid(); + + db.collection("users").document(uid) + .collection("friends").document(friendId) + .get() + .addOnSuccessListener(documentSnapshot -> { + if (documentSnapshot.exists()) { + String nickName = documentSnapshot.getString("nickname"); + String name = documentSnapshot.getString("name"); + String photoUrl = documentSnapshot.getString("photoUrl"); + + // UI 업데이트 + TextView textNickname = view.findViewById(R.id.text_nickname); + TextView textName = view.findViewById(R.id.text_name); + ImageView profileImage = view.findViewById(R.id.img_profile); + + textNickname.setText(nickName != null ? nickName : "#nickname"); + textName.setText(name != null ? name : "#name"); + + // 프로필 사진 로드 + if (photoUrl != null) { + Glide.with(this) + .load(photoUrl) + .placeholder(R.drawable.img_default) + .error(R.drawable.img_default) + .into(profileImage); + } else { + profileImage.setImageResource(R.drawable.pic_basic_profile); + } + } else { + Toast.makeText(getContext(), "친구 정보를 찾을 수 없습니다.", Toast.LENGTH_SHORT).show(); + } + }); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/MainFragment/MainFriendListFragment.java b/app/src/main/java/com/example/mohassu/MainFragment/MainFriendListFragment.java index 8423def..87fe858 100644 --- a/app/src/main/java/com/example/mohassu/MainFragment/MainFriendListFragment.java +++ b/app/src/main/java/com/example/mohassu/MainFragment/MainFriendListFragment.java @@ -1,32 +1,175 @@ package com.example.mohassu.MainFragment; import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.EditText; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.navigation.NavController; import androidx.navigation.Navigation; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import com.example.mohassu.CheckProfileBottomSheetFragment; +import com.example.mohassu.DialogFragment.AddFriendDialogFragment; import com.example.mohassu.R; +import com.example.mohassu.adapters.FriendAdapter; +import com.example.mohassu.models.Friend; +import com.example.mohassu.models.ScheduleClass; +import com.example.mohassu.models.Time; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.firestore.DocumentSnapshot; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.QueryDocumentSnapshot; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; public class MainFriendListFragment extends Fragment { + + private RecyclerView friendRecyclerView; + private FriendAdapter friendAdapter; + private List friendList = new ArrayList<>(); + private EditText etSearchFriend; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_main_friend_list, container, false); } @Override - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - // NavController 초기화 NavController navController = Navigation.findNavController(view); - // 뒤로가기 버튼에 클릭 리스너 추가 - view.findViewById(R.id.btnBack).setOnClickListener(v -> { - navController.navigateUp(); + // 뒤로가기 버튼 + view.findViewById(R.id.btnBack).setOnClickListener(v -> navController.navigateUp()); + + // 친구 추가 버튼 + view.findViewById(R.id.add_friend_button).setOnClickListener(v -> { + AddFriendDialogFragment dialog = new AddFriendDialogFragment(); + dialog.show(getParentFragmentManager(), "AddFriendDialog"); + }); + + etSearchFriend = view.findViewById(R.id.etSearchFriend); + friendRecyclerView = view.findViewById(R.id.friendRecyclerView); + friendRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); + + friendAdapter = new FriendAdapter(requireContext(), friendList, this::showCheckProfileBottomSheet); + friendRecyclerView.setAdapter(friendAdapter); + + fetchFriends(); + + etSearchFriend.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + friendAdapter.filter(s.toString()); + } + + @Override + public void afterTextChanged(Editable s) {} }); } -} + + private void fetchFriends() { + FirebaseFirestore db = FirebaseFirestore.getInstance(); + String currentUserId = FirebaseAuth.getInstance().getCurrentUser().getUid(); + + db.collection("users").document(currentUserId).collection("friends") + .get() + .addOnCompleteListener(task -> { + if (task.isSuccessful()) { + friendList.clear(); + for (DocumentSnapshot document : task.getResult()) { + String friendUid = document.getId(); + fetchFriendDetails(friendUid); + } + } else { + Log.e("fetchFriend", "친구 목록을 불러오는 중 오류 발생"); + } + }); + } + + private void fetchFriendDetails(String friendUid) { + FirebaseFirestore db = FirebaseFirestore.getInstance(); + + db.collection("users").document(friendUid) + .get() + .addOnSuccessListener(documentSnapshot -> { + if (documentSnapshot.exists()) { + String name = documentSnapshot.getString("name"); + String email = documentSnapshot.getString("email"); + String nickname = documentSnapshot.getString("nickname"); + String statusMessage = documentSnapshot.getString("statusMessage"); + String photoUrl = documentSnapshot.getString("photoUrl"); + + fetchCurrentClass(friendUid, currentClass -> { + friendList.add(new Friend(friendUid, name, nickname, email, statusMessage, photoUrl, currentClass)); + friendAdapter.setData(friendList); + }); + } + }) + .addOnFailureListener(e -> Log.e("fetchFriendDetails", "프로필 불러오기 실패: " + e.getMessage())); + } + + private void fetchCurrentClass(String friendUid, CurrentClassCallback callback) { + FirebaseFirestore db = FirebaseFirestore.getInstance(); + + db.collection("users").document(friendUid).collection("timeTable") + .get() + .addOnCompleteListener(task -> { + if (task.isSuccessful()) { + Calendar calendar = Calendar.getInstance(); + int today = calendar.get(Calendar.DAY_OF_WEEK) - 1; + int currentHour = calendar.get(Calendar.HOUR_OF_DAY); + int currentMinute = calendar.get(Calendar.MINUTE); + + ScheduleClass currentClass = null; + + for (QueryDocumentSnapshot document : task.getResult()) { + int day = document.getLong("day").intValue(); + int startHour = document.getLong("startTime.hour").intValue(); + int startMinute = document.getLong("startTime.minute").intValue(); + int endHour = document.getLong("endTime.hour").intValue(); + int endMinute = document.getLong("endTime.minute").intValue(); + + if (today == day && + (currentHour > startHour || (currentHour == startHour && currentMinute >= startMinute)) && + (currentHour < endHour || (currentHour == endHour && currentMinute <= endMinute))) { + currentClass = new ScheduleClass( + document.getString("classTitle"), + document.getString("classPlace"), + document.getString("professorName"), + day, + new Time(startHour, startMinute), + new Time(endHour, endMinute) + ); + break; + } + } + callback.onClassFetched(currentClass); + } + }); + } + + interface CurrentClassCallback { + void onClassFetched(ScheduleClass currentClass); + } + + public void showCheckProfileBottomSheet(Friend friend) { + CheckProfileBottomSheetFragment bottomSheet = CheckProfileBottomSheetFragment.newInstance(friend); + bottomSheet.show(getParentFragmentManager(), "CheckProfileBottomSheet"); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/MainFragment/MainHomeFragment.java b/app/src/main/java/com/example/mohassu/MainFragment/MainHomeFragment.java index 8a42f51..7e8c678 100644 --- a/app/src/main/java/com/example/mohassu/MainFragment/MainHomeFragment.java +++ b/app/src/main/java/com/example/mohassu/MainFragment/MainHomeFragment.java @@ -1,62 +1,653 @@ package com.example.mohassu.MainFragment; +import static com.naver.maps.map.CameraUpdate.REASON_GESTURE; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.location.Location; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.FrameLayout; import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; import androidx.fragment.app.Fragment; import androidx.navigation.NavController; import androidx.navigation.Navigation; +import com.bumptech.glide.Glide; +import com.example.mohassu.Constants; +import com.example.mohassu.PlaceInfo; import com.example.mohassu.R; +import com.google.android.gms.location.GeofencingClient; +import com.google.android.gms.location.LocationServices; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.firestore.FieldValue; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.GeoPoint; +import com.google.firebase.firestore.QueryDocumentSnapshot; +import com.google.firebase.storage.FirebaseStorage; +import com.google.firebase.storage.StorageReference; +import com.naver.maps.geometry.LatLng; +import com.naver.maps.map.CameraAnimation; +import com.naver.maps.map.CameraUpdate; +import com.naver.maps.map.LocationTrackingMode; +import com.naver.maps.map.MapFragment; +import com.naver.maps.map.NaverMap; +import com.naver.maps.map.OnMapReadyCallback; +import com.naver.maps.map.overlay.LocationOverlay; +import com.naver.maps.map.overlay.Marker; +import com.naver.maps.map.overlay.OverlayImage; +import com.naver.maps.map.util.FusedLocationSource; + +import java.util.HashMap; +import java.util.Map; + +public class MainHomeFragment extends Fragment implements OnMapReadyCallback { -public class MainHomeFragment extends Fragment { + private NaverMap naverMap; + private FusedLocationSource locationSource; + private static final int LOCATION_PERMISSION_REQUEST_CODE = 1000; + private boolean isCameraMovedByUser = false; + private boolean isMyMarkerClicked = false; // 마커 클릭 상태 추적 변수 + private boolean isFriendMarkerClicked = false; + private boolean isFocusMode = false; + private boolean isEditTextClicked = false; + private boolean isPlaceFound = false; + ImageButton notificationButton; + ImageButton promiseListButton; + ImageButton signupNextButton; + ImageButton createPromiseButton; + ImageButton myPageButton; + ImageButton myLocationButton; + TextView tvBuildingName; + Marker locationMarker; + + FirebaseAuth auth = FirebaseAuth.getInstance(); + FirebaseFirestore db = FirebaseFirestore.getInstance(); + FirebaseUser currentUser = auth.getCurrentUser(); + + // ActivityResultLauncher for permission requests + private final ActivityResultLauncher requestPermissionLauncher = + registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + if (isGranted) { + if (naverMap != null) { + naverMap.setLocationTrackingMode(LocationTrackingMode.Follow); + } + } else { + Toast.makeText(requireContext(), "위치 권한이 거부되었습니다.", Toast.LENGTH_SHORT).show(); + } + }); @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_main_home, container, false); } @Override - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + Toast.makeText(requireContext(), "지도를 불러오는 중입니다... \n화면을 누르지 마십시오", Toast.LENGTH_LONG).show(); + // Initialize MapFragment + MapFragment mapFragment = (MapFragment) getChildFragmentManager().findFragmentById(R.id.fragment_map); + if (mapFragment == null) { + mapFragment = MapFragment.newInstance(); + getChildFragmentManager().beginTransaction().add(R.id.fragment_map, mapFragment).commit(); + } + mapFragment.getMapAsync(this); + + // Initialize LocationSource + locationSource = new FusedLocationSource(this, LOCATION_PERMISSION_REQUEST_CODE); + + GeofencingClient geofencingClient = LocationServices.getGeofencingClient(requireContext()); + + // Custom button to center on current location + myLocationButton = view.findViewById(R.id.btnNowLocation); + if (myLocationButton != null) { + myLocationButton.setOnClickListener(v -> { + isCameraMovedByUser = false; // 자동 중심 이동 다시 활성화 + LatLng currentPosition = naverMap.getLocationOverlay().getPosition(); + if (currentPosition != null) { + // Move camera to the current position + naverMap.moveCamera(CameraUpdate.scrollTo(currentPosition)); + } else { // 예외처리 생략 가능 + Toast.makeText(requireContext(), "현재 위치를 찾을 수 없습니다.", Toast.LENGTH_SHORT).show(); + } + }); + } + // NavController 초기화 NavController navController = Navigation.findNavController(view); // 다음 프레그먼트를 클릭 시 다음 Fragment로 이동 // 알림 페이지 이동 - ImageButton notificationButton = view.findViewById(R.id.btnNotification); + notificationButton = view.findViewById(R.id.btnNotification); notificationButton.setFocusable(false); notificationButton.setOnClickListener(v -> { navController.navigate(R.id.actionNotification); }); // 약속 리스트 페이지 이동 - ImageButton promiseListButton = view.findViewById(R.id.btnPromiseList); + promiseListButton = view.findViewById(R.id.btnPromiseList); promiseListButton.setFocusable(false); promiseListButton.setOnClickListener(v -> { navController.navigate(R.id.actionPromiseList); }); // 친구 리스트 페이지 이동 - ImageButton signupNextButton = view.findViewById(R.id.btnFriendList); + signupNextButton = view.findViewById(R.id.btnFriendList); signupNextButton.setFocusable(false); signupNextButton.setOnClickListener(v -> { navController.navigate(R.id.actionFriendList); }); //약속 추가 페이지 이동 - ImageButton createPromiseButton = view.findViewById(R.id.btnAddPlan); + createPromiseButton = view.findViewById(R.id.btnAddPlan); createPromiseButton.setFocusable(false); createPromiseButton.setOnClickListener(v -> { navController.navigate(R.id.actionAddPlan); }); // 마이페이지 이동 - ImageButton myPageButton = view.findViewById(R.id.btnMyPage); + myPageButton = view.findViewById(R.id.btnMyPage); myPageButton.setFocusable(false); myPageButton.setOnClickListener(v -> { navController.navigate(R.id.actionMyPage); }); } -} + + @Override + public void onMapReady(@NonNull NaverMap naverMap) { + this.naverMap = naverMap; + + // 현재 위치 불러오기 전 초기 화면을 보이지 않는 위치(바다)로 설정 + CameraUpdate initialUpdate = CameraUpdate.scrollTo(new LatLng(0, 0)); + naverMap.moveCamera(initialUpdate); + + // 위치 정보 가져오기 + naverMap.setLocationSource(locationSource); + + // +- 줌컨트롤 버튼 비활성화 + naverMap.getUiSettings().setZoomControlEnabled(false); + + + // 위치 요청 수락 시 트래킹모드 가동, 거부 시 다시 묻기 + if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + naverMap.setLocationTrackingMode(LocationTrackingMode.Follow);// 트래킹 모드 설정 후 나중에 오버레이 비활성화 + } else { + requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION); + } + + // 지도 드래그 이벤트 설정 + naverMap.addOnCameraChangeListener((reason, animated) -> { + if (reason == REASON_GESTURE) { + isCameraMovedByUser = true; // 사용자가 화면을 이동했을 때 플래그 설정 + isMyMarkerClicked = false; // 사용자가 화면을 이동하면 마커 클릭 상태 해제 + isFriendMarkerClicked = false; + + if (isFocusMode) { + resetMarkerFocusMode(); + } + FrameLayout mapContainer = requireActivity().findViewById(R.id.fragment_map); + View myBalloonView = mapContainer.findViewById(R.id.dialog_edit_message); // ID로 찾기 + if (myBalloonView != null) { + mapContainer.removeView(myBalloonView); // 내 말풍선 제거 + } + View friendBalloonView = mapContainer.findViewById(R.id.dialog_text_message); // ID로 찾기 + if (friendBalloonView != null) { + mapContainer.removeView(friendBalloonView); // 친구 말풍선 제거 + } + View bannerView = mapContainer.findViewById(R.id.fragment_status_banner); // ID로 찾기 + if (bannerView != null) { + mapContainer.removeView(bannerView); // 친구 상태 배너 제거 + } + View profileButton = mapContainer.findViewById(R.id.dialog_show_profile); // ID로 찾기 + if (profileButton != null) { + mapContainer.removeView(profileButton); // 친구 프로필 확인 버튼 제거 + } + } + }); + + // 내 Marker 초기화 + initializeMyMarker(); + + // 친구 Marker 초기화 및 위치 갱신 + loadFriendMarkers(); + + // 지도 클릭 이벤트 설정 (말풍선 닫기) + naverMap.setOnMapClickListener((point, coord) -> { + if (!isEditTextClicked) { + isMyMarkerClicked = false; // 사용자가 화면을 클릭하면 마커 클릭 상태 해제 + isFriendMarkerClicked = false; + if (isFocusMode) { + resetMarkerFocusMode(); + } + FrameLayout mapContainer = requireActivity().findViewById(R.id.fragment_map); + View myBalloonView = mapContainer.findViewById(R.id.dialog_edit_message); // ID로 찾기 + if (myBalloonView != null) { + mapContainer.removeView(myBalloonView); // 말풍선 제거 + } + View friendBalloonView = mapContainer.findViewById(R.id.dialog_text_message); // ID로 찾기 + if (friendBalloonView != null) { + mapContainer.removeView(myBalloonView); // 말풍선 제거 + } + View bannerView = mapContainer.findViewById(R.id.fragment_status_banner); // ID로 찾기 + if (bannerView != null) { + mapContainer.removeView(bannerView); // 말풍선 제거 + } + View profileButton = mapContainer.findViewById(R.id.dialog_show_profile); // ID로 찾기 + if (profileButton != null) { + mapContainer.removeView(profileButton); // 말풍선 제거 + } + } + }); + + + // 위치 변화 업데이트 + naverMap.addOnLocationChangeListener(location -> { + if (locationMarker == null) { + // locationMarker가 초기화되지 않았다면 초기화 기다리기 + return; + } + updateUserLocationToFirestore(location); + loadUserLocationFromFirestore(); + naverMap.getLocationOverlay().setVisible(false); // 오버레이 비활성화 + }); + + } + + private void initializeMyMarker() { + CameraUpdate hide = CameraUpdate.scrollAndZoomTo(new LatLng(0,0), 20.0) + .animate(CameraAnimation.Easing); + naverMap.moveCamera(hide); // 가끔씩 naverMap 로딩 버그로 인해 위치 업데이트 이후 보이게 설정 + + // XML 레이아웃을 Inflate + View myMarkerView = LayoutInflater.from(requireContext()).inflate(R.layout.my_marker, null); + ImageView myProfile = myMarkerView.findViewById(R.id.my_marker_image); + + if (currentUser != null) { + String uid = currentUser.getUid(); + + // Firestore에서 사용자 데이터를 가져옴 + db.collection("users").document(currentUser.getUid()) // 예: 사용자 ID를 문서 ID로 사용 + .get() + .addOnSuccessListener(documentSnapshot -> { + if (documentSnapshot.exists()) { + String photoUrl = documentSnapshot.getString("photoUrl"); + + if (photoUrl != null) { + // Glide를 사용하여 이미지 로드 + Glide.with(this) + .load(photoUrl) + .placeholder(R.drawable.img_default) + .error(R.drawable.img_default) + .into(myProfile); + + } else { + myProfile.setImageResource(R.drawable.pic_basic_profile); // 기본 이미지 + } + // View를 Bitmap으로 변환 + //Bitmap myMarkerBitmap = convertViewToBitmap(myMarkerView); +// Marker 객체 생성 + locationMarker = new Marker(); + locationMarker.setPosition(naverMap.getLocationOverlay().getPosition()); + locationMarker.setIcon(OverlayImage.fromResource(R.drawable.img_marker_red)); // 마커 이미지 설정 + locationMarker.setWidth(120); // 마커 크기 조정 + locationMarker.setHeight(140); + locationMarker.setMap(naverMap); // 지도에 마커 추가 + + locationMarker.setOnClickListener(overlay -> { + + if (naverMap == null) { // 테스트 필요 + Toast.makeText(requireContext(), "지도를 불러오는 중입니다. 잠시만 기다려주세요.", Toast.LENGTH_SHORT).show(); + return true; + } + + //다른 버튼 안 보이게 + showMarkerFocusMode(); + + LatLng location = locationMarker.getPosition(); + + // 현재 위치 가져오기 + CameraUpdate update = CameraUpdate.scrollAndZoomTo(location, 20.0) + .animate(CameraAnimation.Easing); + naverMap.moveCamera(update); + isMyMarkerClicked = true; + isFriendMarkerClicked = false; + + + FrameLayout mapContainer = requireActivity().findViewById(R.id.fragment_map); + + // 말풍선 View 인플레이트 + View myBalloonView = LayoutInflater.from(requireContext()) + .inflate(R.layout.dialog_edit_message, mapContainer, false); + mapContainer.addView(myBalloonView); // 말풍선 추가 + + // EditText 참조 가져오기 + EditText markerMessageEditText = myBalloonView.findViewById(R.id.markerMyMessage); + + db.collection("users") + .document(currentUser.getUid()) // 사용자 ID + .get() + .addOnSuccessListener(statusMsg -> { + if (statusMsg.exists()) { + // 상태 메시지가 이미 저장되어 있으면 EditText에 띄우기 + String storedMessage = statusMsg.getString("statusMessage"); + if (storedMessage != null && !storedMessage.isEmpty()) { + markerMessageEditText.setText(storedMessage); // 기존 메시지 띄우기 + } + } + }) + .addOnFailureListener(e -> { + Log.w("TAG", "Error getting document", e); + }); + + markerMessageEditText.setOnEditorActionListener((v, actionId, event) -> { + isEditTextClicked = true; + if (actionId == EditorInfo.IME_ACTION_DONE) { + String statusMessage = markerMessageEditText.getText().toString().trim(); + + if (!statusMessage.isEmpty()) { + db.collection("users") + .document(currentUser.getUid()) // 사용자 ID + .update("statusMessage", statusMessage) + .addOnSuccessListener(aVoid -> { + // 저장 성공 시 처리 (예: 메시지 표시) + Toast.makeText(requireContext(), "상태 메시지가 업데이트되었습니다.", Toast.LENGTH_SHORT).show(); + }); + } + + + InputMethodManager imm = (InputMethodManager) requireContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.hideSoftInputFromWindow(getView().getWindowToken(), 0); + markerMessageEditText.clearFocus(); // 포커스 해제하여 깜빡임 끄기 + } + return true; + } + return false; + }); + return true; // 클릭 이벤트 소비 + }); + } + }); + } + } + + private void updateUserLocationToFirestore(Location location) { + if (currentUser == null) return; // 사용자 인증되지 않은 경우 + + String uid = currentUser.getUid(); // 사용자 고유 ID (UID) + + //GeoPoint로 location 저장 + GeoPoint geoPoint = new GeoPoint(location.getLatitude(), location.getLongitude()); + + // Firestore에 저장할 데이터 + Map locationData = new HashMap<>(); + locationData.put("location", geoPoint); + locationData.put("timestamp", FieldValue.serverTimestamp()); // 서버의 타임스탬프 추가 + + db.collection("users") + .document(uid) + .collection("location") + .document("currentLocation") + .set(locationData) // 🔥 Firestore에 데이터 저장 + .addOnSuccessListener(aVoid -> Log.d("TAG", "Location updated in Firestore")) + .addOnFailureListener(e -> Log.w("TAG", "Failed to update location", e)); + } + + private void loadUserLocationFromFirestore() { + if (currentUser == null) return; // 사용자 인증되지 않은 경우 + + String uid = currentUser.getUid(); + + db.collection("users") + .document(uid) + .collection("location") + .document("currentLocation") + .addSnapshotListener((snapshot, error) -> { + if (error != null) { + Log.w("TAG", "Listen failed.", error); + return; + } + + if (snapshot != null && snapshot.exists()) { + GeoPoint newGeopoint = snapshot.getGeoPoint("location"); + + LatLng newLocation = new LatLng(newGeopoint.getLatitude(), newGeopoint.getLongitude()); + + // 사용자 위치 업데이트 + locationMarker.setPosition(newLocation); + + // 마커 클릭 상태 또는 초기 화면에서 카메라 이동 + if (isMyMarkerClicked) { + CameraUpdate update = CameraUpdate.scrollTo(newLocation) + .animate(CameraAnimation.Easing); // 줌 레벨 17.0 + naverMap.moveCamera(update); + } else if (!isCameraMovedByUser && !isFriendMarkerClicked) { + CameraUpdate update = CameraUpdate.scrollAndZoomTo(newLocation, 17.0) + .animate(CameraAnimation.Easing); + naverMap.moveCamera(update); + } + + + View view = getView(); + tvBuildingName = view.findViewById(R.id.tvBuildingName); + + loadFromGeofencing(newLocation); + } + }); + } + + + private void loadFromGeofencing(LatLng location) { + for (PlaceInfo place : Constants.PLACES) { + float[] results = new float[1]; + Location.distanceBetween( + location.latitude, location.longitude, + place.getLocation().latitude, place.getLocation().longitude, + results + ); + + if (results[0] <= place.getRadius()) { + String buildingName = place.getName(); + tvBuildingName.setText(buildingName + "에 있어요."); + if (!isFocusMode) { + tvBuildingName.setVisibility(View.VISIBLE); + } + db.collection("users") + .document(currentUser.getUid()) + .update("place", buildingName) // Firestore에 장소명 저장 + .addOnSuccessListener(aVoid -> Log.d("TAG", "Location updated in Firestore")) + .addOnFailureListener(e -> Log.w("TAG", "Failed to update location", e)); + break; // 반경 내 첫 번째 장소를 찾으면 종료 + } + } + tvBuildingName.setVisibility(View.GONE); // 반경 내 장소가 없을 경우 + + if (!isPlaceFound) { + db.collection("users") + .document(currentUser.getUid()) + .update("place", "지도 위 장소") + .addOnSuccessListener(aVoid -> Log.d("TAG", "No place found, updated to '건물없음'")) + .addOnFailureListener(e -> Log.w("TAG", "Failed to update location to '건물없음'", e)); + } + } + + // Firestore에서 친구 데이터 가져오기 + private void loadFriendMarkers() { +// +// // XML 레이아웃을 Inflate +// View friendMarkerView = LayoutInflater.from(requireContext()).inflate(R.layout.your_marker, null); +// ImageView friendProfile = friendMarkerView.findViewById(R.id.your_marker_image); +// +// if (currentUser != null) { +// String uid = currentUser.getUid(); +// +// db.collection("users").document(uid) +// .collection("friends") +// .get() +// .addOnSuccessListener(querySnapshot -> { +// if (querySnapshot.isEmpty()) { +// // 친구 데이터가 없을 경우 처리 +// Toast.makeText(requireContext(), "친구를 추가해보세요!", Toast.LENGTH_SHORT).show(); +// return; +// } +// +// for (QueryDocumentSnapshot document : querySnapshot) { +// String name = document.getString("name"); +// String nickname = document.getString("nickname"); +// String class_name = document.getString("class_name"); +// String place = document.getString("place"); +// String startTime = document.getString("startTime"); +// String endTime = document.getString("endTime"); +// String photoUrl = document.getString("photoUrl"); +// GeoPoint location = document.getGeoPoint("location"); +// String statusMessage = document.getString("statusMessage"); +// // 마커 클릭 시 친구 ID 전달 +// String friendId = document.getId(); +// +// if (photoUrl != null) { +// // Glide를 사용하여 이미지 로드 +// Glide.with(this) +// .load(photoUrl) +// .placeholder(R.drawable.img_default) +// .error(R.drawable.img_default) +// .into(friendProfile); +// } else { +// friendProfile.setImageResource(R.drawable.pic_basic_profile); // 기본 이미지 +// } +// +// // View를 Bitmap으로 변환 +// Bitmap friendMarkerBitmap = convertViewToBitmap(friendMarkerView); +// +// // 친구 위치를 기반으로 마커 추가 +// +// LatLng friendLocation = new LatLng(location.getLatitude(), location.getLongitude()); +// +// Marker friendMarker = new Marker(); +// friendMarker.setPosition(friendLocation); +// friendMarker.setIcon(OverlayImage.fromBitmap(friendMarkerBitmap)); // 마커 이미지 +// friendMarker.setWidth(120); +// friendMarker.setHeight(140); +// friendMarker.setMap(naverMap); +// +// // 마커 클릭 이벤트 +// friendMarker.setOnClickListener(overlay -> { // 아직 테스트 +// // 클릭 이벤트 설정 +// if (naverMap == null || getView() == null) { +// // 지도 초기화가 완료되지 않은 경우 +// Toast.makeText(requireContext(), "지도가 아직 초기화되지 않았습니다.", Toast.LENGTH_SHORT).show(); +// return true; // 이벤트 소비 +// } +// +// //다른 버튼 안 보이게 +// showMarkerFocusMode(); +// +// //친구 위치로 카메라 업데이트 +// CameraUpdate update = CameraUpdate.scrollAndZoomTo(friendLocation, 20.0) +// .animate(CameraAnimation.Easing); +// naverMap.moveCamera(update); +// isFriendMarkerClicked = true; +// isMyMarkerClicked = false; +// +// FrameLayout mapContainer = requireActivity().findViewById(R.id.fragment_map); +// +// // 말풍선 View 인플레이트 +// View friendBalloonView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_text_message, mapContainer, false); +// TextView friendBalloonText = friendBalloonView.findViewById(R.id.markerFriendMessage); +// friendBalloonText.setText(statusMessage); +// mapContainer.addView(friendBalloonView); // 말풍선 추가 +// +// // 배너 View 인플레이트 +// View bannerView = LayoutInflater.from(requireContext()).inflate(R.layout.fragment_status_banner, mapContainer, false); +// mapContainer.addView(bannerView); +// +// // UI 업데이트 +// updateStatusBanner(place, class_name, startTime, endTime); +// +// // 프로필버튼 View 인플레이트 +// View profileButton = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_show_profile, mapContainer, false); +// mapContainer.addView(profileButton); +// // 클릭 이벤트 설정 +// // 프로필 보기 버튼 클릭 이벤트 +// profileButton.findViewById(R.id.showProfileButton).setOnClickListener(v -> { +// // BottomSheetDialogFragment 호출 +// EmptyBottomSheetProfile bottomSheet = EmptyBottomSheetProfile.newInstance(friendId); +// bottomSheet.show(getParentFragmentManager(), "ProfileBottomSheet"); +// }); +// +// return true; // 클릭 이벤트 소비 +// }); +// } +// }); +// } + } + + // 상태 배너 업데이트 메서드 + private void updateStatusBanner(String place, String class_name, String startTime, String endTime) { + View view = getView(); + if (view == null) return; + + TextView placeInfo = view.findViewById(R.id.placeInfo); + TextView classInfo = view.findViewById(R.id.classInfo); + TextView stTimeInfo = view.findViewById(R.id.startTimeInfo); + TextView endTimeInfo = view.findViewById(R.id.endTimeInfo); + + // Firestore 데이터로 텍스트 업데이트 + placeInfo.setText(place != null ? place : "#PLACE"); + //classInfo.setText(class_name != null ? class_name : "#CLASS"); + //stTimeInfo.setText(startTime != null ? startTime : "#st_time"); + //endTimeInfo.setText(endTime != null ? endTime : "#end_time"); + } + + private void showMarkerFocusMode() { + // 버튼 숨기기 + isFocusMode = true; + notificationButton.setVisibility(View.GONE); + promiseListButton.setVisibility(View.GONE); + signupNextButton.setVisibility(View.GONE); + createPromiseButton.setVisibility(View.GONE); + myPageButton.setVisibility(View.GONE); + myLocationButton.setVisibility(View.GONE); + tvBuildingName.setVisibility(View.GONE); + } + + + private void resetMarkerFocusMode() { + // 버튼 다시 표시 + isFocusMode = false; + notificationButton.setVisibility(View.VISIBLE); + promiseListButton.setVisibility(View.VISIBLE); + signupNextButton.setVisibility(View.VISIBLE); + createPromiseButton.setVisibility(View.VISIBLE); + myPageButton.setVisibility(View.VISIBLE); + myLocationButton.setVisibility(View.VISIBLE); + tvBuildingName.setVisibility(View.VISIBLE); + } + + private Bitmap convertViewToBitmap(View view) { + view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); + + Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + view.draw(canvas); + + return bitmap; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/MainFragment/MainHomeFriendTestFragment.java b/app/src/main/java/com/example/mohassu/MainFragment/MainHomeFriendTestFragment.java new file mode 100644 index 0000000..8631496 --- /dev/null +++ b/app/src/main/java/com/example/mohassu/MainFragment/MainHomeFriendTestFragment.java @@ -0,0 +1,356 @@ +package com.example.mohassu.MainFragment; + +import static com.naver.maps.map.CameraUpdate.REASON_GESTURE; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.graphics.Point; +import android.graphics.PointF; +import android.location.Location; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; +import androidx.fragment.app.Fragment; +import androidx.navigation.NavController; +import androidx.navigation.Navigation; + +import com.example.mohassu.CheckProfileAndTimeTableFragment.BottomSheetCheckProfileFragment; +import com.example.mohassu.Constants; +import com.example.mohassu.PlaceInfo; +import com.example.mohassu.R; +import com.google.android.gms.location.GeofencingClient; +import com.google.android.gms.location.LocationServices; +import com.naver.maps.geometry.LatLng; +import com.naver.maps.map.CameraAnimation; +import com.naver.maps.map.CameraUpdate; +import com.naver.maps.map.LocationTrackingMode; +import com.naver.maps.map.MapFragment; +import com.naver.maps.map.NaverMap; +import com.naver.maps.map.OnMapReadyCallback; +import com.naver.maps.map.Projection; +import com.naver.maps.map.overlay.LocationOverlay; +import com.naver.maps.map.overlay.Marker; +import com.naver.maps.map.overlay.OverlayImage; +import com.naver.maps.map.util.FusedLocationSource; + +public class MainHomeFriendTestFragment extends Fragment implements OnMapReadyCallback { + + private NaverMap naverMap; + private FusedLocationSource locationSource; + private static final int LOCATION_PERMISSION_REQUEST_CODE = 1000; + private Marker locationMarker; // Marker 객체 선언 + private boolean isCameraMovedByUser = false; + private boolean isMarkerClicked = false; // 마커 클릭 상태 추적 변수 + + ImageButton notificationButton; + ImageButton promiseListButton; + ImageButton signupNextButton; + ImageButton createPromiseButton; + ImageButton myPageButton; + ImageButton myLocationButton; + TextView tvBuildingName; + boolean focusMode = false; + + private GeofencingClient geofencingClient; + + // ActivityResultLauncher for permission requests + private final ActivityResultLauncher requestPermissionLauncher = + registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + if (isGranted) { + if (naverMap != null) { + naverMap.setLocationTrackingMode(LocationTrackingMode.Follow); + } + } else { + Toast.makeText(requireContext(), "위치 권한이 거부되었습니다.", Toast.LENGTH_SHORT).show(); + } + }); + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_main_home, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + // Initialize MapFragment + MapFragment mapFragment = (MapFragment) getChildFragmentManager().findFragmentById(R.id.fragment_map); + if (mapFragment == null) { + mapFragment = MapFragment.newInstance(); + getChildFragmentManager().beginTransaction().add(R.id.fragment_map, mapFragment).commit(); + } + mapFragment.getMapAsync(this); + + // Initialize LocationSource + locationSource = new FusedLocationSource(this, LOCATION_PERMISSION_REQUEST_CODE); + + geofencingClient = LocationServices.getGeofencingClient(requireContext()); + + // Custom button to center on current location + myLocationButton = view.findViewById(R.id.btnNowLocation); + if (myLocationButton != null) { + myLocationButton.setOnClickListener(v -> { + isCameraMovedByUser = false; // 자동 중심 이동 다시 활성화 + LatLng currentPosition = naverMap.getLocationOverlay().getPosition(); + if (currentPosition != null) { + // Move camera to the current position + naverMap.moveCamera(CameraUpdate.scrollTo(currentPosition)); + } else { // 예외처리 생략 가능 + Toast.makeText(requireContext(), "현재 위치를 찾을 수 없습니다.", Toast.LENGTH_SHORT).show(); + } + }); + } + + // NavController 초기화 + //NavController navController = Navigation.findNavController(view); + + // 다음 프레그먼트를 클릭 시 다음 Fragment로 이동 + // 알림 페이지 이동 + notificationButton = view.findViewById(R.id.btnNotification); + notificationButton.setFocusable(false); + notificationButton.setOnClickListener(v -> { + //navController.navigate(R.id.actionNotification); + }); + // 약속 리스트 페이지 이동 + promiseListButton = view.findViewById(R.id.btnPromiseList); + promiseListButton.setFocusable(false); + promiseListButton.setOnClickListener(v -> { + //navController.navigate(R.id.actionPromiseList); + }); + // 친구 리스트 페이지 이동 + signupNextButton = view.findViewById(R.id.btnFriendList); + signupNextButton.setFocusable(false); + signupNextButton.setOnClickListener(v -> { + //navController.navigate(R.id.actionFriendList); + }); + //약속 추가 페이지 이동 + createPromiseButton = view.findViewById(R.id.btnAddPlan); + createPromiseButton.setFocusable(false); + createPromiseButton.setOnClickListener(v -> { + //navController.navigate(R.id.actionAddPlan); + }); + // 마이페이지 이동 + myPageButton = view.findViewById(R.id.btnMyPage); + myPageButton.setFocusable(false); + myPageButton.setOnClickListener(v -> { + //navController.navigate(R.id.actionMyPage); + }); + } + + @Override + public void onMapReady(@NonNull NaverMap naverMap) { + this.naverMap = naverMap; + + // 초기 좌표를 보이지 않는 위치로 설정 (예: 바다 위의 좌표) + CameraUpdate initialUpdate = CameraUpdate.scrollTo(new LatLng(0, 0)); + naverMap.moveCamera(initialUpdate); + + // 위치 정보 가져오기 + naverMap.setLocationSource(locationSource); + + // +- 줌컨트롤 버튼 비활성화 + naverMap.getUiSettings().setZoomControlEnabled(false); + + // 오버레이 위치 아이콘 비활성화 + LocationOverlay locationOverlay = naverMap.getLocationOverlay(); + locationOverlay.setVisible(false); + + // 지도 이동 이벤트 설정 + naverMap.addOnCameraChangeListener((reason, animated) -> { + if (reason == REASON_GESTURE) { + isCameraMovedByUser = true; // 사용자가 화면을 이동했을 때 플래그 설정 + isMarkerClicked = false; // 사용자가 화면을 이동하면 마커 클릭 상태 해제 + if (focusMode) { + resetMarkerFocusMode(); + } + FrameLayout mapContainer = requireActivity().findViewById(R.id.fragment_map); + View balloonView = mapContainer.findViewById(R.id.dialog_text_message); // ID로 찾기 + if (balloonView != null) { + mapContainer.removeView(balloonView); // 말풍선 제거 + } + View bannerView = mapContainer.findViewById(R.id.fragment_status_banner); // ID로 찾기 + if (bannerView != null) { + mapContainer.removeView(bannerView); // 말풍선 제거 + } + View profileButton = mapContainer.findViewById(R.id.dialog_show_profile); // ID로 찾기 + if (profileButton != null) { + mapContainer.removeView(profileButton); // 말풍선 제거 + } + + } + }); + + // 위치 요청 수락 시 트래킹모드 가동, 거부 시 다시 묻기 + if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + naverMap.setLocationTrackingMode(LocationTrackingMode.Follow);// 트래킹 모드 설정 후에도 오버레이 비활성화 + naverMap.addOnLocationChangeListener(location -> { + locationOverlay.setVisible(false); // 계속해서 오버레이를 비활성화 + }); + } else { + requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION); + } + + // Marker 초기화 + initializeLocationMarker(); + + // 지도 클릭 이벤트 설정 (말풍선 닫기) + naverMap.setOnMapClickListener((point, coord) -> { + if (focusMode) { + resetMarkerFocusMode(); + } + resetMarkerFocusMode(); + FrameLayout mapContainer = requireActivity().findViewById(R.id.fragment_map); + View balloonView = mapContainer.findViewById(R.id.dialog_text_message); // ID로 찾기 + if (balloonView != null) { + mapContainer.removeView(balloonView); // 말풍선 제거 + } + View bannerView = mapContainer.findViewById(R.id.fragment_status_banner); // ID로 찾기 + if (bannerView != null) { + mapContainer.removeView(bannerView); // 말풍선 제거 + } + View profileButton = mapContainer.findViewById(R.id.dialog_show_profile); // ID로 찾기 + if (profileButton != null) { + mapContainer.removeView(profileButton); // 말풍선 제거 + } + isMarkerClicked = false; // 마커 클릭 상태 해제 + }); + + + // 위치 변화 업데이트 + naverMap.addOnLocationChangeListener(location -> { + LatLng currentLocation = new LatLng(location.getLatitude(), location.getLongitude()); + locationMarker.setPosition(currentLocation); + + // 마커 클릭 상태 또는 초기 화면에서 카메라 이동 + if (isMarkerClicked) { + CameraUpdate update = CameraUpdate.scrollTo(currentLocation) + .animate(CameraAnimation.Easing); // 줌 레벨 17.0 + naverMap.moveCamera(update); + } else if (!isCameraMovedByUser) { + CameraUpdate update = CameraUpdate.scrollAndZoomTo(currentLocation, 17.0) + .animate(CameraAnimation.Easing); + naverMap.moveCamera(update); + } + + + View view = getView(); + tvBuildingName = view.findViewById(R.id.tvBuildingName); + + for (PlaceInfo place : Constants.PLACES) { + float[] results = new float[1]; + Location.distanceBetween( + location.getLatitude(), location.getLongitude(), + place.getLocation().latitude, place.getLocation().longitude, + results + ); + + if (results[0] <= place.getRadius()) { + String buildingName = place.getName(); + tvBuildingName.setText(buildingName + "에 있어요."); + if (!focusMode) { + tvBuildingName.setVisibility(View.VISIBLE); + } + return; // 반경 내 첫 번째 장소를 찾으면 종료 + } + } + tvBuildingName.setVisibility(View.GONE); // 반경 내 장소가 없을 경우 + }); + } + + // Marker 초기화 메서드 + private void initializeLocationMarker() { + // Marker 객체 생성 + locationMarker = new Marker(); + LatLng defaultPosition = new LatLng(0, 0); // 최초 좌표 + locationMarker.setPosition(defaultPosition); + locationMarker.setIcon(OverlayImage.fromResource(R.drawable.img_marker_blue)); // 마커 이미지 설정 + locationMarker.setWidth(120); // 마커 크기 조정 + locationMarker.setHeight(140); + locationMarker.setMap(naverMap); // 지도에 마커 추가 + + // 클릭 이벤트 설정 + locationMarker.setOnClickListener(overlay -> { + if (naverMap == null || getView() == null) { + // 지도 초기화가 완료되지 않은 경우 + Toast.makeText(requireContext(), "지도가 아직 초기화되지 않았습니다.", Toast.LENGTH_SHORT).show(); + return true; // 이벤트 소비 + } + + //다른 버튼 안 보이게 + showMarkerFocusMode(); + + // 현재 위치 가져오기 + LocationOverlay locationOverlay = naverMap.getLocationOverlay(); + LatLng currentLocation = locationOverlay.getPosition(); // 현재 위치 좌표 가져오기 + CameraUpdate update = CameraUpdate.scrollAndZoomTo(currentLocation, 20.0) + .animate(CameraAnimation.Easing);// 줌 레벨 17.0 + naverMap.moveCamera(update); + isMarkerClicked = true; + + FrameLayout mapContainer = requireActivity().findViewById(R.id.fragment_map); + + // 말풍선 View 인플레이트 + View balloonView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_text_message, mapContainer, false); + //balloonView.setId(R.id.dialog_text_message); // ID 설정 + mapContainer.addView(balloonView); // 말풍선 추가 + + // 배너 View 인플레이트 + View bannerView = LayoutInflater.from(requireContext()).inflate(R.layout.fragment_status_banner, mapContainer, false); + //bannerView.setId(R.id.fragment_status_banner); + mapContainer.addView(bannerView); + + // 프로필버튼 View 인플레이트 + View profileButton = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_show_profile, mapContainer, false); + //profileButton.setId(R.id.dialog_show_profile); + mapContainer.addView(profileButton); + // 클릭 이벤트 설정 + // 프로필 보기 버튼 클릭 이벤트 + profileButton.findViewById(R.id.showProfileButton).setOnClickListener(v -> { + // BottomSheetDialogFragment 호출 + EmptyBottomSheetProfile bottomSheet = new EmptyBottomSheetProfile(); + bottomSheet.show(getParentFragmentManager(), "ProfileBottomSheet"); + }); + + return true; // 클릭 이벤트 소비 + }); + + } + + private void showMarkerFocusMode() { + focusMode = true; + // 버튼 숨기기 + notificationButton.setVisibility(View.GONE); + promiseListButton.setVisibility(View.GONE); + signupNextButton.setVisibility(View.GONE); + createPromiseButton.setVisibility(View.GONE); + myPageButton.setVisibility(View.GONE); + myLocationButton.setVisibility(View.GONE); + tvBuildingName.setVisibility(View.GONE); + } + + + private void resetMarkerFocusMode() { + focusMode = false; + // 버튼 다시 표시 + notificationButton.setVisibility(View.VISIBLE); + promiseListButton.setVisibility(View.VISIBLE); + signupNextButton.setVisibility(View.VISIBLE); + createPromiseButton.setVisibility(View.VISIBLE); + myPageButton.setVisibility(View.VISIBLE); + myLocationButton.setVisibility(View.VISIBLE); + tvBuildingName.setVisibility(View.VISIBLE); + } +} diff --git a/app/src/main/java/com/example/mohassu/MapViewActivity.java b/app/src/main/java/com/example/mohassu/MapViewActivity.java index d35bd92..05262eb 100644 --- a/app/src/main/java/com/example/mohassu/MapViewActivity.java +++ b/app/src/main/java/com/example/mohassu/MapViewActivity.java @@ -1,18 +1,24 @@ package com.example.mohassu; +import com.example.mohassu.Constants; + import android.content.pm.PackageManager; import android.Manifest; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.location.Location; import android.os.Bundle; +import android.view.View; import android.widget.ImageButton; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; +import com.google.android.gms.location.GeofencingClient; +import com.google.android.gms.location.LocationServices; import com.naver.maps.geometry.LatLng; import com.naver.maps.map.CameraUpdate; import com.naver.maps.map.LocationTrackingMode; @@ -29,6 +35,8 @@ public class MapViewActivity extends AppCompatActivity implements OnMapReadyCall private FusedLocationSource locationSource; private static final int LOCATION_PERMISSION_REQUEST_CODE = 1000; + private GeofencingClient geofencingClient; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -41,6 +49,8 @@ protected void onCreate(Bundle savedInstanceState) { // Initialize LocationSource locationSource = new FusedLocationSource(this, LOCATION_PERMISSION_REQUEST_CODE); + + geofencingClient = LocationServices.getGeofencingClient(this); } @Override @@ -104,9 +114,26 @@ public void onLocationChange(@NonNull Location location) { LatLng currentLocation = new LatLng(location.getLatitude(), location.getLongitude()); locationOverlay.setPosition(currentLocation); // Update the position to the current location locationOverlay.setBearing(0); // Reapply the fixed bearing - }); + //Geofencing + // Check if user is within the geofence + for (PlaceInfo place : Constants.PLACES) { + float[] results = new float[1]; + android.location.Location.distanceBetween( + location.getLatitude(), location.getLongitude(), + place.getLocation().latitude, place.getLocation().longitude, + results + ); + + if (results[0] <= place.getRadius()) { + showBuildingName(place.getName()); + return; // 반경 내 첫 번째 장소를 찾으면 종료 + } + } + hideBuildingName(); // 반경 내 장소가 없을 경우 + }); + // Custom button to center on current location ImageButton myLocationButton = findViewById(R.id.btnNowLocation); myLocationButton.setOnClickListener(v -> { @@ -133,6 +160,18 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis } } + private void showBuildingName(String buildingName) { + TextView tvBuildingName = findViewById(R.id.tvBuildingName); + tvBuildingName.setText(buildingName + "에 있어요."); + tvBuildingName.setVisibility(View.VISIBLE); + } + + private void hideBuildingName() { + TextView tvBuildingName = findViewById(R.id.tvBuildingName); + tvBuildingName.setVisibility(View.GONE); + } + + @Override protected void onStart() { super.onStart(); diff --git a/app/src/main/java/com/example/mohassu/MyPageFragment/MyPageHomeFragment.java b/app/src/main/java/com/example/mohassu/MyPageFragment/MyPageHomeFragment.java index 3841b7a..52e6200 100644 --- a/app/src/main/java/com/example/mohassu/MyPageFragment/MyPageHomeFragment.java +++ b/app/src/main/java/com/example/mohassu/MyPageFragment/MyPageHomeFragment.java @@ -1,17 +1,29 @@ package com.example.mohassu.MyPageFragment; +import android.graphics.Bitmap; +import android.graphics.Canvas; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.FrameLayout; import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; import androidx.fragment.app.Fragment; import androidx.navigation.NavController; import androidx.navigation.Navigation; +import com.bumptech.glide.Glide; import com.example.mohassu.R; +import com.google.firebase.Firebase; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.firestore.DocumentSnapshot; +import com.google.firebase.firestore.FirebaseFirestore; public class MyPageHomeFragment extends Fragment { @Override @@ -23,6 +35,62 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + FirebaseAuth auth = FirebaseAuth.getInstance(); + FirebaseFirestore db = FirebaseFirestore.getInstance(); + FirebaseUser currentUser = auth.getCurrentUser(); + + if (currentUser != null) { + String uid = currentUser.getUid(); + + db.collection("users").document(uid) + .get() + .addOnCompleteListener(task -> { + if (task.isSuccessful()) { + DocumentSnapshot document = task.getResult(); + if (document.exists()) { + // Firestore에서 사용자 데이터 가져오기 + String nickName = document.getString("nickname"); + String name = document.getString("name"); + String email = document.getString("email"); + String birthDate = document.getString("birthDate"); + String photoUrl = document.getString("photoUrl"); + + Log.d("Firestore", "Name: " + name + ", Email: " + email + ", nickName" + nickName +", birthDate" + birthDate); + + // UI 업데이트 + TextView greetingTextView = view.findViewById(R.id.greetingText); + TextView userIdView = view.findViewById(R.id.userId); + TextView userNickNameView = view.findViewById(R.id.usernickName); + TextView userNameView = view.findViewById(R.id.userName); + TextView userBirthView = view.findViewById(R.id.userBirth); + ImageView profileImageView = view.findViewById(R.id.profileImage); + + if (photoUrl != null && !photoUrl.isEmpty()) { + Glide.with(this) + .load(photoUrl) + .placeholder(R.drawable.img_logo) // 로딩 중 대체 이미지 + .error(R.drawable.img_logo) // 로딩 실패 시 대체 이미지 + .into(profileImageView); + } else { + profileImageView.setImageResource(R.drawable.img_logo); // 기본 이미지 + } + + greetingTextView.setText(nickName +"님 반갑습니다!"); + userIdView.setText(email); + userNickNameView.setText(nickName); + userNameView.setText(name); + userBirthView.setText(birthDate); + } else { + Log.d("Firestore", "No such document!"); + } + } else { + Log.w("Firestore", "Error getting document.", task.getException()); + } + }); + } else { + Log.d("FirebaseAuth", "No user is logged in"); + } + // NavController 초기화 NavController navController = Navigation.findNavController(view); @@ -30,6 +98,12 @@ public void onViewCreated(View view, Bundle savedInstanceState) { navController.navigateUp(); }); + TextView logoutText = view.findViewById(R.id.logoutText); + logoutText.setOnClickListener(v -> { + auth.signOut(); // Firebase 인증 로그아웃 +// navController.navigate(R.id.actionLogout); // 로그인 화면으로 이동 + }); + // 다음 프레그먼트를 클릭 시 다음 Fragment로 이동 ImageButton profileEditButton = view.findViewById(R.id.btnProfileEdit); profileEditButton.setFocusable(false); @@ -51,4 +125,5 @@ public void onViewCreated(View view, Bundle savedInstanceState) { navController.navigate(R.id.actionSettingNotification); }); } + } diff --git a/app/src/main/java/com/example/mohassu/MyPageFragment/MyPageMyTimeTableEditFragment.java b/app/src/main/java/com/example/mohassu/MyPageFragment/MyPageMyTimeTableEditFragment.java index 88f8ae3..78751bb 100644 --- a/app/src/main/java/com/example/mohassu/MyPageFragment/MyPageMyTimeTableEditFragment.java +++ b/app/src/main/java/com/example/mohassu/MyPageFragment/MyPageMyTimeTableEditFragment.java @@ -3,6 +3,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -14,19 +15,25 @@ import androidx.navigation.Navigation; import com.example.mohassu.DialogFragment.ClassAddDialogFragment; +import com.example.mohassu.DialogFragment.ClassEditOrDeleteDialogFragment; import com.example.mohassu.R; import com.github.tlaabs.timetableview.Schedule; import com.github.tlaabs.timetableview.Time; import com.github.tlaabs.timetableview.TimetableView; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.firestore.DocumentSnapshot; +import com.google.firebase.firestore.FirebaseFirestore; import java.util.ArrayList; +import java.util.HashMap; public class MyPageMyTimeTableEditFragment extends Fragment { - private static final String PREFS_NAME = "TimetablePrefs"; //파이어베이스상 이메일을 넣어주면 될 듯 - private static final String TIMETABLE_KEY = "timetable"; + private static final String PREFS_NAME = "TimetablePrefs"; // 로컬 SharedPreferences에 저장 + private static final String TIMETABLE_KEY = "timetable"; // 로컬 SharedPreferences 키 private TimetableView timetable; + private FirebaseFirestore db; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -37,7 +44,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - timetable=view.findViewById(R.id.timetable); + db = FirebaseFirestore.getInstance(); + + timetable = view.findViewById(R.id.timetable); loadTimetable(); // NavController 초기화 @@ -52,62 +61,120 @@ public void onViewCreated(View view, Bundle savedInstanceState) { view.findViewById(R.id.btnAddClass).setOnClickListener(v -> { ClassAddDialogFragment classAddDialogFragment = new ClassAddDialogFragment(); classAddDialogFragment.setOnClassAddedListener((className, classPlace, day, startHour, startMinute, endHour, endMinute) -> { - // Validate inputs if (className.isEmpty() || classPlace.isEmpty() || startHour > endHour || (startHour == endHour && startMinute >= endMinute)) { - if (className.isEmpty() || classPlace.isEmpty()) { - Toast.makeText(requireContext(), "전부 입력해주세요!", Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(requireContext(), "시작 시간이 종료 시간보다 빠르거나 같아야 합니다.", Toast.LENGTH_SHORT).show(); - } + Toast.makeText(requireContext(), "올바른 수업 정보를 입력해주세요.", Toast.LENGTH_SHORT).show(); } else { addScheduleToTimetable(className, classPlace, day, startHour, startMinute, endHour, endMinute); - saveTimetable(); } }); classAddDialogFragment.show(requireActivity().getSupportFragmentManager(), "ClassAddDialog"); }); - // 다음 프레그먼트를 클릭 시 다음 Fragment로 이동 + timetable.setOnStickerSelectEventListener((idx, schedules) -> { + ClassEditOrDeleteDialogFragment classEditOrDeleteDialogFragment = ClassEditOrDeleteDialogFragment.newInstance(schedules.get(0)); + classEditOrDeleteDialogFragment.setOnClassEditOrDeleteListener(new ClassEditOrDeleteDialogFragment.OnClassEditOrDeleteListener() { + @Override + public void onEdit(Schedule editedSchedule) { + ArrayList updatedSchedules = new ArrayList<>(); + updatedSchedules.add(editedSchedule); + timetable.edit(idx, updatedSchedules); + Toast.makeText(requireContext(), "수업 정보가 수정되었습니다.", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onDelete() { + timetable.remove(idx); + Toast.makeText(requireContext(), "수업 정보가 삭제되었습니다.", Toast.LENGTH_SHORT).show(); + } + }); + classEditOrDeleteDialogFragment.show(requireActivity().getSupportFragmentManager(), "ClassEditDialog"); + }); + Button timeTableSaveButton = view.findViewById(R.id.btnSave); - timeTableSaveButton.setFocusable(false); timeTableSaveButton.setOnClickListener(v -> { + saveTimetable(); + saveTimetableToFirestore(); navController.navigate(R.id.actionSaveMyClass); + }); } private void loadTimetable() { SharedPreferences prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); String json = prefs.getString(TIMETABLE_KEY, null); - if (json != null) { timetable.load(json); - //Toast.makeText(requireContext(), "저장된 시간표를 불러왔습니다.", Toast.LENGTH_SHORT).show(); } } private void saveTimetable() { SharedPreferences prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); - String json = timetable.createSaveData(); editor.putString(TIMETABLE_KEY, json); editor.apply(); + Toast.makeText(requireContext(), "로컬에 시간표가 저장되었습니다.", Toast.LENGTH_SHORT).show(); + } + + private void saveTimetableToFirestore() { + String userId = FirebaseAuth.getInstance().getCurrentUser().getUid(); + String json = timetable.createSaveData(); + + // 1️Firestore의 timeTableData 필드 업데이트 + db.collection("users").document(userId) + .update("timetableData", json) + .addOnSuccessListener(aVoid -> Log.d("Firestore", "timeTableData 필드 업데이트 성공")) + .addOnFailureListener(e -> Log.e("Firestore", "timeTableData 업데이트 실패: " + e.getMessage())); + + // 2️Firestore의 timeTable 컬렉션 데이터 삭제 후 새로 추가 + db.collection("users") + .document(userId) + .collection("timetable") + .get() + .addOnSuccessListener(querySnapshot -> { + for (DocumentSnapshot document : querySnapshot.getDocuments()) { + document.getReference().delete() + .addOnSuccessListener(aVoid -> Log.d("Firestore", "기존 timeTable 문서 삭제 성공")) + .addOnFailureListener(e -> Log.e("Firestore", "기존 timeTable 문서 삭제 실패: " + e.getMessage())); + } - Toast.makeText(requireContext(), "시간표가 저장되었습니다.", Toast.LENGTH_SHORT).show(); + ArrayList schedules = timetable.getAllSchedulesInStickers(); + for (int i = 0; i < schedules.size(); i++) { + Schedule schedule = schedules.get(i); + HashMap scheduleMap = new HashMap<>(); + scheduleMap.put("classTitle", schedule.getClassTitle()); + scheduleMap.put("classPlace", schedule.getClassPlace()); + scheduleMap.put("professorName", schedule.getProfessorName()); + scheduleMap.put("day", schedule.getDay()); + scheduleMap.put("startTime", new HashMap() {{ + put("hour", schedule.getStartTime().getHour()); + put("minute", schedule.getStartTime().getMinute()); + }}); + scheduleMap.put("endTime", new HashMap() {{ + put("hour", schedule.getEndTime().getHour()); + put("minute", schedule.getEndTime().getMinute()); + }}); + + db.collection("users") + .document(userId) + .collection("timeTable") + .add(scheduleMap) + .addOnSuccessListener(documentReference -> Log.d("Firestore", "새로운 timeTable 문서 추가 성공")) + .addOnFailureListener(e -> Log.e("Firestore", "새로운 timeTable 문서 추가 실패: " + e.getMessage())); + } + }) + .addOnFailureListener(e -> Log.e("Firestore", "timeTable 문서 삭제 실패: " + e.getMessage())); } private void addScheduleToTimetable(String className, String classPlace, int day, int startHour, int startMinute, int endHour, int endMinute) { ArrayList schedules = new ArrayList<>(); - Schedule schedule = new Schedule(); schedule.setClassTitle(className); schedule.setClassPlace(classPlace); schedule.setDay(day); schedule.setStartTime(new Time(startHour, startMinute)); schedule.setEndTime(new Time(endHour, endMinute)); - schedules.add(schedule); - timetable.add(schedules); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/MyPageFragment/MyPageMyTimeTableFragment.java b/app/src/main/java/com/example/mohassu/MyPageFragment/MyPageMyTimeTableFragment.java index e53b0b3..f3e5960 100644 --- a/app/src/main/java/com/example/mohassu/MyPageFragment/MyPageMyTimeTableFragment.java +++ b/app/src/main/java/com/example/mohassu/MyPageFragment/MyPageMyTimeTableFragment.java @@ -3,6 +3,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -15,6 +16,8 @@ import com.example.mohassu.R; import com.github.tlaabs.timetableview.TimetableView; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.firestore.FirebaseFirestore; public class MyPageMyTimeTableFragment extends Fragment { @@ -52,12 +55,37 @@ public void onViewCreated(View view, Bundle savedInstanceState) { } private void loadTimetable() { - SharedPreferences prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - String json = prefs.getString(TIMETABLE_KEY, null); +// SharedPreferences prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); +// String json = prefs.getString(TIMETABLE_KEY, null); +// timetable.load(json); +// Toast.makeText(requireContext(), "저장된 시간표를 불러왔습니다.", Toast.LENGTH_SHORT).show(); - if (json != null) { - timetable.load(json); - //Toast.makeText(requireContext(), "저장된 시간표를 불러왔습니다.", Toast.LENGTH_SHORT).show(); - } + + + FirebaseFirestore db = FirebaseFirestore.getInstance(); + String userId = FirebaseAuth.getInstance().getCurrentUser().getUid(); // 현재 로그인한 사용자 ID 가져오기 + + db.collection("users") + .document(userId) + .get() + .addOnSuccessListener(documentSnapshot -> { + if (documentSnapshot.exists()) { + // timeTableData 필드 가져오기 + String timeTableData = documentSnapshot.getString("timetableData"); + if (timeTableData != null) { + timetable.load(timeTableData); + Toast.makeText(requireContext(), "저장된 시간표를 불러왔습니다.", Toast.LENGTH_SHORT).show(); + Log.d("Firestore", "TimeTableData: " + timeTableData); + // JSON 데이터 처리 로직 추가 가능 + } else { + Log.d("Firestore", "timeTableData 필드가 없습니다."); + } + } else { + Log.d("Firestore", "해당 문서가 존재하지 않습니다."); + } + }) + .addOnFailureListener(e -> { + Log.e("Firestore", "timeTableData 가져오기 실패: " + e.getMessage()); + }); } } diff --git a/app/src/main/java/com/example/mohassu/PlaceInfo.java b/app/src/main/java/com/example/mohassu/PlaceInfo.java new file mode 100644 index 0000000..918df36 --- /dev/null +++ b/app/src/main/java/com/example/mohassu/PlaceInfo.java @@ -0,0 +1,27 @@ +package com.example.mohassu; + +import com.naver.maps.geometry.LatLng; + +public class PlaceInfo { + private String name; + private LatLng location; + private float radius; + + public PlaceInfo(String name, LatLng location, float radius) { + this.name = name; + this.location = location; + this.radius = radius; + } + + public String getName() { + return name; + } + + public LatLng getLocation() { + return location; + } + + public float getRadius() { + return radius; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/TestFriendMarkerActivity.java b/app/src/main/java/com/example/mohassu/TestFriendMarkerActivity.java new file mode 100644 index 0000000..1fc047b --- /dev/null +++ b/app/src/main/java/com/example/mohassu/TestFriendMarkerActivity.java @@ -0,0 +1,22 @@ +package com.example.mohassu; + +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +import com.example.mohassu.MainFragment.MainHomeFriendTestFragment; + +public class TestFriendMarkerActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_test); + + if (savedInstanceState == null) { // Activity가 처음 실행된 경우 + getSupportFragmentManager().beginTransaction() + .replace(R.id.main, new MainHomeFriendTestFragment()) // MainHomeFragment로 전환 + .commit(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/UserProfileViewModel.java b/app/src/main/java/com/example/mohassu/UserProfileViewModel.java new file mode 100644 index 0000000..5a2111f --- /dev/null +++ b/app/src/main/java/com/example/mohassu/UserProfileViewModel.java @@ -0,0 +1,47 @@ +package com.example.mohassu; + +import android.net.Uri; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +public class UserProfileViewModel extends ViewModel { + private final MutableLiveData nickname = new MutableLiveData<>(); + private final MutableLiveData name = new MutableLiveData<>(); + private final MutableLiveData birthDate = new MutableLiveData<>(); + private final MutableLiveData photoUri = new MutableLiveData<>(); + + public void setNickname(String nickname) { + this.nickname.setValue(nickname); + } + + public LiveData getNickname() { + return nickname; + } + + public void setName(String name) { + this.name.setValue(name); + } + + public LiveData getName() { + return name; + } + + public void setBirthDate(String birthDate) { + this.birthDate.setValue(birthDate); + } + + public LiveData getBirthDate() { + return birthDate; + } + + public void setPhotoUri(Uri uri) { + this.photoUri.setValue(uri); + } + + public LiveData getPhotoUri() { + return photoUri; + } + +} diff --git a/app/src/main/java/com/example/mohassu/adapters/FriendAdapter.java b/app/src/main/java/com/example/mohassu/adapters/FriendAdapter.java new file mode 100644 index 0000000..1641cfc --- /dev/null +++ b/app/src/main/java/com/example/mohassu/adapters/FriendAdapter.java @@ -0,0 +1,119 @@ +package com.example.mohassu.adapters; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.example.mohassu.R; +import com.example.mohassu.models.Friend; + +import java.util.ArrayList; +import java.util.List; + +public class FriendAdapter extends RecyclerView.Adapter { + private List friendList; // 전체 친구 목록 + private List filteredList; // 필터링된 친구 목록 + private Context context; + private OnFriendClickListener onFriendClickListener; + + public FriendAdapter(Context context, List friendList, OnFriendClickListener listener) { + this.context = context; + this.friendList = new ArrayList<>(friendList); + this.filteredList = new ArrayList<>(friendList); + this.onFriendClickListener = listener; + } + + @NonNull + @Override + public FriendViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(context).inflate(R.layout.view_friend, parent, false); + return new FriendViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull FriendViewHolder holder, int position) { + Friend friend = filteredList.get(position); + holder.nicknameTextView.setText(friend.getNickname()); + + if (friend.getStatusMessage() != null && !friend.getStatusMessage().isEmpty()) + holder.statusTextView.setText(friend.getStatusMessage()); + else + holder.statusTextView.setText("상태 메시지가 없어요!"); + + if(friend.getCurrentClass() != null){ + holder.placeTextView.setText(friend.getCurrentClass().getClassPlace() + "에서 " +friend.getCurrentClass().getClassTitle() + "수업 중!!!"); + holder.timeTextView.setText(friend.getCurrentClass().getStartTime().getHour() + "시 " + friend.getCurrentClass().getStartTime().getMinute() +"분 부터 " + friend.getCurrentClass().getEndTime().getHour() + "시 "+friend.getCurrentClass().getEndTime().getMinute() + "분 까지"); + } + else{ + holder.placeTextView.setText("지금은 수업 중이 아닌디요??"); + holder.timeTextView.setText("친구 한테 연락해봐요!"); + } + Glide.with(context) + .load(friend.getPhotoUrl()) + .placeholder(R.drawable.img_logo) + .error(R.drawable.img_logo) + .into(holder.photoImageView); + + holder.itemView.setOnClickListener(v -> { + if (onFriendClickListener != null) { + onFriendClickListener.onFriendClick(friend); + } + }); + } + + @Override + public int getItemCount() { + return filteredList.size(); + } + + // 검색 기능 추가 + public void filter(String query) { + filteredList.clear(); + if (query.isEmpty()) { + filteredList.addAll(friendList); + } else { + for (Friend friend : friendList) { + if (friend.getNickname().toLowerCase().contains(query.toLowerCase())) { + filteredList.add(friend); + } + } + } + notifyDataSetChanged(); + } + + // 🔥 setData() 메서드 추가 (오류 수정) + public void setData(List newFriendList) { + this.friendList.clear(); + this.friendList.addAll(newFriendList); + + this.filteredList.clear(); + this.filteredList.addAll(newFriendList); + + notifyDataSetChanged(); + } + + public static class FriendViewHolder extends RecyclerView.ViewHolder { + TextView nicknameTextView, statusTextView, placeTextView, timeTextView; + ImageView photoImageView; + + public FriendViewHolder(@NonNull View itemView) { + super(itemView); + nicknameTextView = itemView.findViewById(R.id.nickname_text); + statusTextView = itemView.findViewById(R.id.state_text); + placeTextView = itemView.findViewById(R.id.state_place); + timeTextView = itemView.findViewById(R.id.state_time); + photoImageView = itemView.findViewById(R.id.profile_image2); + } + } + + public interface OnFriendClickListener { + void onFriendClick(Friend friend); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/models/Friend.java b/app/src/main/java/com/example/mohassu/models/Friend.java new file mode 100644 index 0000000..9112f16 --- /dev/null +++ b/app/src/main/java/com/example/mohassu/models/Friend.java @@ -0,0 +1,81 @@ +package com.example.mohassu.models; + +import java.io.Serializable; + +public class Friend implements Serializable { + private String name; + private String email; + private String photoUrl; + private String uid; + private String nickname; + private String statusMessage; + private ScheduleClass currentScheduleClass; // 현재 수업 정보 추가 + + public Friend(String uid, String name, String nickname, String email, String statusMessage, String photoUrl, ScheduleClass currentScheduleClass) { + this.uid = uid; + this.name = name; + this.nickname = nickname; + this.email = email; + this.statusMessage = statusMessage; + this.photoUrl = photoUrl; + this.currentScheduleClass = currentScheduleClass; + } + + + // Getter & Setter + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhotoUrl() { + return photoUrl; + } + + public void setPhotoUrl(String photoUrl) { + this.photoUrl = photoUrl; + } + + public String getUid() { + return uid; + } + + public void setUid(String uid) { + this.uid = uid; + } + + public String getNickname() { + return nickname; + } + + public void setNickname(String nickname) { + this.nickname = nickname; + } + + public String getStatusMessage() { + return statusMessage; + } + + public void setStatusMessage(String statusMessage) { + this.statusMessage = statusMessage; + } + + public ScheduleClass getCurrentClass() { + return currentScheduleClass; + } + + public void setCurrentClass(ScheduleClass currentScheduleClass) { + this.currentScheduleClass = currentScheduleClass; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/models/ScheduleClass.java b/app/src/main/java/com/example/mohassu/models/ScheduleClass.java new file mode 100644 index 0000000..9975cc9 --- /dev/null +++ b/app/src/main/java/com/example/mohassu/models/ScheduleClass.java @@ -0,0 +1,70 @@ +package com.example.mohassu.models; + +import java.io.Serializable; + +public class ScheduleClass implements Serializable { + private String classTitle; + private String classPlace; + private String professorName; + private int day; // 0 = 일요일, 1 = 월요일, ... + private Time startTime; + private Time endTime; + + public ScheduleClass(String classTitle, String classPlace, String professorName, int day, Time startTime, Time endTime) { + this.classTitle = classTitle; + this.classPlace = classPlace; + this.professorName = professorName; + this.day = day; + this.startTime = startTime; + this.endTime = endTime; + } + + // Getter and Setter methods + public String getClassTitle() { + return classTitle; + } + + public void setClassTitle(String classTitle) { + this.classTitle = classTitle; + } + + public String getClassPlace() { + return classPlace; + } + + public void setClassPlace(String classPlace) { + this.classPlace = classPlace; + } + + public String getProfessorName() { + return professorName; + } + + public void setProfessorName(String professorName) { + this.professorName = professorName; + } + + public int getDay() { + return day; + } + + public void setDay(int day) { + this.day = day; + } + + public Time getStartTime() { + return startTime; + } + + public void setStartTime(Time startTime) { + this.startTime = startTime; + } + + public Time getEndTime() { + return endTime; + } + + public void setEndTime(Time endTime) { + this.endTime = endTime; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mohassu/models/Time.java b/app/src/main/java/com/example/mohassu/models/Time.java new file mode 100644 index 0000000..58f0713 --- /dev/null +++ b/app/src/main/java/com/example/mohassu/models/Time.java @@ -0,0 +1,30 @@ +package com.example.mohassu.models; + +import java.io.Serializable; + +public class Time implements Serializable { + private int hour; + private int minute; + + public Time(int hour, int minute) { + this.hour = hour; + this.minute = minute; + } + + // Getter and Setter methods + public int getHour() { + return hour; + } + + public void setHour(int hour) { + this.hour = hour; + } + + public int getMinute() { + return minute; + } + + public void setMinute(int minute) { + this.minute = minute; + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/img_default.png b/app/src/main/res/drawable/img_default.png new file mode 100644 index 0000000..a4ddcc1 Binary files /dev/null and b/app/src/main/res/drawable/img_default.png differ diff --git a/app/src/main/res/drawable/pic_status_chat.png b/app/src/main/res/drawable/pic_status_chat.png index 5877d76..768bf81 100644 Binary files a/app/src/main/res/drawable/pic_status_chat.png and b/app/src/main/res/drawable/pic_status_chat.png differ diff --git a/app/src/main/res/drawable/pic_triangle.xml b/app/src/main/res/drawable/pic_triangle.xml new file mode 100644 index 0000000..569d1df --- /dev/null +++ b/app/src/main/res/drawable/pic_triangle.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/profile_container.png b/app/src/main/res/drawable/profile_container.png new file mode 100644 index 0000000..7227203 Binary files /dev/null and b/app/src/main/res/drawable/profile_container.png differ diff --git a/app/src/main/res/layout/activity_map_view.xml b/app/src/main/res/layout/activity_map_view.xml index 1a20379..43d6b51 100644 --- a/app/src/main/res/layout/activity_map_view.xml +++ b/app/src/main/res/layout/activity_map_view.xml @@ -11,6 +11,24 @@ android:layout_height="match_parent" android:name="com.naver.maps.map.MapFragment" /> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_add_friend.xml b/app/src/main/res/layout/dialog_add_friend.xml index f5486de..91503da 100644 --- a/app/src/main/res/layout/dialog_add_friend.xml +++ b/app/src/main/res/layout/dialog_add_friend.xml @@ -39,7 +39,7 @@ - + android:gravity="center_vertical" + android:background="@android:color/transparent" + > + android:textStyle="bold" /> -