From 32c3c927de4401178d1ab2d15b5881720a870ef9 Mon Sep 17 00:00:00 2001 From: Tianrui Qi Date: Sun, 17 Nov 2024 04:39:08 -0500 Subject: [PATCH] Sprint4: Implement TravDatabase (#84) --- .../com/example/sprint1/model/MainModel.java | 53 ++++++ .../example/sprint1/model/TravDatabase.java | 87 +++++++++ .../example/sprint1/model/UserDatabase.java | 51 ++++++ .../com/example/sprint1/view/HomeTra.java | 169 +++++++++++++++++- .../sprint1/viewmodel/MainViewModel.java | 55 ++++++ .../app/src/main/res/layout/home_tra.xml | 123 ++++++++++++- .../app/src/main/res/layout/home_tra_card.xml | 96 ++++++++++ .../app/src/main/res/values/strings.xml | 17 +- 8 files changed, 645 insertions(+), 6 deletions(-) create mode 100644 Project/Sprint4/app/src/main/java/com/example/sprint1/model/TravDatabase.java create mode 100644 Project/Sprint4/app/src/main/res/layout/home_tra_card.xml diff --git a/Project/Sprint4/app/src/main/java/com/example/sprint1/model/MainModel.java b/Project/Sprint4/app/src/main/java/com/example/sprint1/model/MainModel.java index 7acbfa0..ecd6783 100644 --- a/Project/Sprint4/app/src/main/java/com/example/sprint1/model/MainModel.java +++ b/Project/Sprint4/app/src/main/java/com/example/sprint1/model/MainModel.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.HashMap; +import java.util.Map; public class MainModel { @@ -11,6 +12,7 @@ public class MainModel { private final DestDatabase destDatabase = new DestDatabase(); private final DiniDatabase diniDatabase = new DiniDatabase(); private final AccoDatabase accoDatabase = new AccoDatabase(); + private final TravDatabase travDatabase = new TravDatabase(); /* Singleton Design */ @@ -195,6 +197,57 @@ public void getAccommodation( }); } + public void addTravel( + String start, String end, + String destination, String accommodation, String dining, String note, + Callback callback + ) { + // Retrieve the current username + String username = this.userDatabase.getUsernameCurr(); + if (username == null || username.isEmpty()) { + callback.onResult(false); // Fail if the current username is not set + return; + } + + Map travelData = new HashMap<>(); + travelData.put("username", username); + travelData.put("start", start); + travelData.put("end", end); + travelData.put("destination", destination); + travelData.put("accommodation", accommodation); + travelData.put("dining", dining); + travelData.put("note", note); + + // Add the travel post to the TravelDatabase + this.travDatabase.addTravel( + travelData, key -> { + if (key != null) { + // Add the travel key to the current user's travel list + this.userDatabase.addTravel(key, success -> { + if (success) { + notifyObservers("TravelAdded", key); + } + callback.onResult(success); + }); + } else { + callback.onResult(false); // Return failure if the key is null + } + } + ); + } + + public void getTravel( + Callback>> callback + ) { + // Fetch all travel posts from the TravelDatabase + this.travDatabase.getTravel(data -> { + if (data != null) { + notifyObservers("TravelFetched", data); + } + callback.onResult(data); // Return the fetched data + }); + } + /* Callbacks */ public interface Callback { diff --git a/Project/Sprint4/app/src/main/java/com/example/sprint1/model/TravDatabase.java b/Project/Sprint4/app/src/main/java/com/example/sprint1/model/TravDatabase.java new file mode 100644 index 0000000..8e2b5b5 --- /dev/null +++ b/Project/Sprint4/app/src/main/java/com/example/sprint1/model/TravDatabase.java @@ -0,0 +1,87 @@ +package com.example.sprint1.model; + +import androidx.annotation.NonNull; + +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.database.ValueEventListener; + +import java.util.HashMap; +import java.util.Map; + +public class TravDatabase { + + /* Instance fields */ + + private final DatabaseReference travelDatabase; + + /* Constructors */ + + public TravDatabase() { + this.travelDatabase = FirebaseDatabase.getInstance().getReference("travel"); + } + + /* Main Features */ + + public void addTravel( + Map travelData, + Callback callback + ) { + // Generate a unique key for the travel post + String key = this.travelDatabase.push().getKey(); + + if (key == null) { + callback.onResult(null); + return; + } + + // Save the travel post to Firebase + this.travelDatabase.child(key).setValue(travelData).addOnCompleteListener(task -> { + if (task.isSuccessful()) { + callback.onResult(key); // Return the travel post ID if successful + } else { + callback.onResult(null); // Return null if the operation fails + } + }); + } + + public void getTravel( + Callback>> callback + ) { + this.travelDatabase.addListenerForSingleValueEvent(new ValueEventListener() { + @Override + public void onDataChange(@NonNull DataSnapshot dataSnapshot) { + HashMap> travelPosts = new HashMap<>(); + + // Iterate through each travel post + for (DataSnapshot postSnapshot : dataSnapshot.getChildren()) { + HashMap postDetails = new HashMap<>(); + for (DataSnapshot detailSnapshot : postSnapshot.getChildren()) { + String key = detailSnapshot.getKey(); + String value = detailSnapshot.getValue(String.class); + if (key != null && value != null) { + postDetails.put(key, value); + } + } + // Add the travel post details to the result + travelPosts.put(postSnapshot.getKey(), postDetails); + } + + callback.onResult(travelPosts); + } + + @Override + public void onCancelled(@NonNull DatabaseError error) { + callback.onResult(null); // Return null if the operation fails + } + }); + } + + /* Callbacks */ + + public interface Callback { + void onResult(T callback); + } +} \ No newline at end of file diff --git a/Project/Sprint4/app/src/main/java/com/example/sprint1/model/UserDatabase.java b/Project/Sprint4/app/src/main/java/com/example/sprint1/model/UserDatabase.java index 645e6bc..2548f75 100644 --- a/Project/Sprint4/app/src/main/java/com/example/sprint1/model/UserDatabase.java +++ b/Project/Sprint4/app/src/main/java/com/example/sprint1/model/UserDatabase.java @@ -664,6 +664,57 @@ public void onCancelled(@NonNull DatabaseError error) { }); } + public void addTravel( + String key, + Callback callback + ) { + // Reference to the current user's travel list in the database + DatabaseReference ref = this.userDatabase.child(this.usernameCurr).child("travel"); + + // Retrieve the existing travel list + ref.addListenerForSingleValueEvent(new ValueEventListener() { + @Override + public void onDataChange(@NonNull DataSnapshot dataSnapshot) { + ArrayList travelKeys = new ArrayList<>(); + + // If travel data exists, populate the list + if (dataSnapshot.exists()) { + for (DataSnapshot child : dataSnapshot.getChildren()) { + String travelKey = child.getValue(String.class); + if (travelKey != null) { + travelKeys.add(travelKey); + } + } + } + + // Add the new key if it's not already in the list + if (!travelKeys.contains(key)) { + travelKeys.add(key); + } + + // Update the travel list in Firebase + ref.setValue(travelKeys).addOnCompleteListener(task -> { + if (task.isSuccessful()) { + callback.onResult(true); // Success + } else { + callback.onResult(false); // Failure + } + }); + } + + @Override + public void onCancelled(@NonNull DatabaseError error) { + callback.onResult(false); + } + }); + } + + /* Getter */ + + public String getUsernameCurr() { + return usernameCurr; + } + /* Callbacks */ public interface Callback { diff --git a/Project/Sprint4/app/src/main/java/com/example/sprint1/view/HomeTra.java b/Project/Sprint4/app/src/main/java/com/example/sprint1/view/HomeTra.java index 227ade9..b41e984 100644 --- a/Project/Sprint4/app/src/main/java/com/example/sprint1/view/HomeTra.java +++ b/Project/Sprint4/app/src/main/java/com/example/sprint1/view/HomeTra.java @@ -2,22 +2,183 @@ import android.content.Intent; import android.os.Bundle; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import com.example.sprint1.R; +import com.example.sprint1.viewmodel.MainViewModel; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; public class HomeTra extends AppCompatActivity { + private final MainViewModel mainViewModel = new MainViewModel(); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.home_tra); + this.listTravelPosts(); + this.buttonAddTravelPost(); this.buttonNavigationBar(); } + private void listTravelPosts() { + LinearLayout travelContainer = findViewById(R.id.travel_posts_container); + + // Clear previous records + travelContainer.removeAllViews(); + + // Fetch travel posts from ViewModel + mainViewModel.getTravel(travelPosts -> { + if (travelPosts == null || travelPosts.isEmpty()) { + return; + } + + // Convert the travel posts into a list for sorting + List>> sortedTravelPosts = + new ArrayList<>(travelPosts.entrySet()); + + SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy", Locale.US); + + // Sort travel posts by the start date + sortedTravelPosts.sort((entry1, entry2) -> { + try { + String start1 = entry1.getValue() != null + ? entry1.getValue().get("start") : null; + String start2 = entry2.getValue() != null + ? entry2.getValue().get("start") : null; + + Date date1 = (start1 != null && !start1.isEmpty()) + ? dateFormat.parse(start1) : null; + Date date2 = (start2 != null && !start2.isEmpty()) + ? dateFormat.parse(start2) : null; + + if (date1 == null && date2 == null) { + return 0; + } else if (date1 == null) { + return 1; + } else if (date2 == null) { + return -1; + } + + return date1.compareTo(date2); + } catch (Exception e) { + return 0; + } + }); + + // Display sorted travel posts + for (Map.Entry> entry : sortedTravelPosts) { + HashMap travelInfo = entry.getValue(); + + // Inflate the travel post card layout + View travelCard = getLayoutInflater().inflate( + R.layout.home_tra_card, travelContainer, false + ); + + // Populate travel post details + TextView usernameText = travelCard.findViewById(R.id.Home_Tra_ListPost_Username); + TextView startText = travelCard.findViewById(R.id.Home_Tra_ListPost_Start); + TextView endText = travelCard.findViewById(R.id.Home_Tra_ListPost_End); + TextView destinationText = travelCard.findViewById( + R.id.Home_Tra_ListPost_Destination + ); + TextView accommodationText = travelCard.findViewById( + R.id.Home_Tra_ListPost_Accommodation + ); + TextView diningText = travelCard.findViewById(R.id.Home_Tra_ListPost_Dining); + TextView noteText = travelCard.findViewById(R.id.Home_Tra_ListPost_Note); + + // Use string resources with placeholders + usernameText.setText(getString( + R.string.Home_Tra_ListPost_Username, travelInfo.get("username"))); + startText.setText(getString( + R.string.Home_Tra_ListPost_Start, travelInfo.get("start"))); + endText.setText(getString( + R.string.Home_Tra_ListPost_End, travelInfo.get("end"))); + destinationText.setText(getString( + R.string.Home_Tra_ListPost_Destination, travelInfo.get("destination"))); + accommodationText.setText(getString( + R.string.Home_Tra_ListPost_Accommodation, travelInfo.get("accommodation"))); + diningText.setText(getString( + R.string.Home_Tra_ListPost_Dining, travelInfo.get("dining"))); + noteText.setText(getString( + R.string.Home_Tra_ListPost_Note, travelInfo.get("note"))); + + // Add the card to the container + travelContainer.addView(travelCard); + } + }); + } + + private void buttonAddTravelPost() { + Button addPostButton = findViewById(R.id.Home_Tra_AddPost); + LinearLayout addPostForm = findViewById(R.id.Home_Tra_AddTravel_Form); + + EditText inputStart = findViewById(R.id.Home_Tra_AddTravel_Start); + EditText inputEnd = findViewById(R.id.Home_Tra_AddTravel_End); + EditText inputDestination = findViewById(R.id.Home_Tra_AddTravel_Destination); + EditText inputAccommodation = findViewById(R.id.Home_Tra_AddTravel_Accommodation); + EditText inputDining = findViewById(R.id.Home_Tra_AddTravel_Dining); + EditText inputNote = findViewById(R.id.Home_Tra_AddTravel_Note); + + addPostButton.setOnClickListener(v -> { + if (addPostForm.getVisibility() == View.GONE) { + addPostForm.setVisibility(View.VISIBLE); + } else { + addPostForm.setVisibility(View.GONE); + clearAddPostForm( + inputStart, inputEnd, + inputDestination, inputAccommodation, inputDining, inputNote + ); + } + }); + + findViewById(R.id.Home_Tra_AddTravel_Cancel).setOnClickListener(v -> { + addPostForm.setVisibility(View.GONE); + clearAddPostForm( + inputStart, inputEnd, + inputDestination, inputAccommodation, inputDining, inputNote + ); + }); + + findViewById(R.id.Home_Tra_AddTravel_Submit).setOnClickListener(v -> { + String start = inputStart.getText().toString().trim(); + String end = inputEnd.getText().toString().trim(); + String destination = inputDestination.getText().toString().trim(); + String accommodation = inputAccommodation.getText().toString().trim(); + String dining = inputDining.getText().toString().trim(); + String note = inputNote.getText().toString().trim(); + + mainViewModel.addTravel( + start, end, destination, accommodation, dining, note, success -> { + if (success) { + this.listTravelPosts(); + addPostForm.setVisibility(View.GONE); + clearAddPostForm( + inputStart, inputEnd, inputDestination, inputAccommodation, + inputDining, inputNote + ); + } + } + ); + }); + } + private void buttonNavigationBar() { findViewById(R.id.view_tra_button_log).setOnClickListener( v -> startActivity(new Intent(HomeTra.this, HomeLog.class)) @@ -32,4 +193,10 @@ private void buttonNavigationBar() { v -> startActivity(new Intent(HomeTra.this, HomeAcc.class)) ); } -} + + private void clearAddPostForm(EditText... fields) { + for (EditText field : fields) { + field.setText(""); + } + } +} \ No newline at end of file diff --git a/Project/Sprint4/app/src/main/java/com/example/sprint1/viewmodel/MainViewModel.java b/Project/Sprint4/app/src/main/java/com/example/sprint1/viewmodel/MainViewModel.java index 2d48847..979a49b 100644 --- a/Project/Sprint4/app/src/main/java/com/example/sprint1/viewmodel/MainViewModel.java +++ b/Project/Sprint4/app/src/main/java/com/example/sprint1/viewmodel/MainViewModel.java @@ -398,6 +398,61 @@ public void getAccommodation( this.mainModel.getAccommodation(callback::onResult); } + public void addTravel( + String start, String end, + String destination, String accommodation, String dining, String note, + Callback callback + ) { + // Validate inputs + if ( + start == null || start.trim().isEmpty() + || end == null || end.trim().isEmpty() + || destination == null || destination.trim().isEmpty() + || accommodation == null || accommodation.trim().isEmpty() + || dining == null || dining.trim().isEmpty() + || note == null || note.trim().isEmpty() + ) { + callback.onResult(false); + return; + } + + try { + // Check if start and end times are valid + DateFormat dateFormat = DateFormat.getDateInstance( + DateFormat.SHORT, java.util.Locale.US + ); + dateFormat.setLenient(false); + Date startDate = dateFormat.parse(start); + Date endDate = dateFormat.parse(end); + + if (startDate == null || endDate == null || !endDate.after(startDate)) { + callback.onResult(false); // Invalid date range + return; + } + + // Calculate the trip duration + int durationInDays = MainViewModel.calDuration(startDate, endDate); + if (durationInDays < 1) { + callback.onResult(false); // Duration must be at least 1 day + return; + } + } catch (ParseException e) { + callback.onResult(false); // Invalid date format + return; + } + + // Add the travel data + this.mainModel.addTravel( + start, end, destination, accommodation, dining, note, callback::onResult + ); + } + + public void getTravel( + Callback>> callback + ) { + this.mainModel.getTravel(callback::onResult); + } + /* Helper Methods */ private static int calDuration(Date startDate, Date endDate) { diff --git a/Project/Sprint4/app/src/main/res/layout/home_tra.xml b/Project/Sprint4/app/src/main/res/layout/home_tra.xml index f7bbdd3..2324933 100644 --- a/Project/Sprint4/app/src/main/res/layout/home_tra.xml +++ b/Project/Sprint4/app/src/main/res/layout/home_tra.xml @@ -5,7 +5,9 @@ android:layout_height="match_parent" tools:context=".view.HomeTra"> + + app:layout_constraintTop_toTopOf="parent" /> + + + + + + + + + + + + + + + + + + + + + + + +