diff --git a/LICENSE.md b/LICENSE.md index eaca692c..90e05c0f 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,7 +1,7 @@ CMPUT 301 Student Submission License Version 2.0 -Copyright 2025 `` +Copyright 2025 `xindi li` Unauthorized redistribution is forbidden under all circumstances. Use of this software without explicit authorization from the author **and** the CMPUT 301 diff --git a/README.md b/README.md index c0674453..409c5f01 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,12 @@ ## References and Resources -List any resources used here, or simply put `N/A` if not applicable. + +N/A ## Verbal Collaboration | Student Name | CCID | | ------------ | --------- | -| `student` | `student` | +| `Xindi Li` | `xindi8` | | `` | `` | diff --git a/code/app/src/main/java/com/example/listycitylab3/AddCityFragment.java b/code/app/src/main/java/com/example/listycitylab3/AddCityFragment.java new file mode 100644 index 00000000..04602cbb --- /dev/null +++ b/code/app/src/main/java/com/example/listycitylab3/AddCityFragment.java @@ -0,0 +1,52 @@ +package com.example.listycitylab3; + +import static java.security.AccessController.getContext; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; + + +public class AddCityFragment extends DialogFragment { + interface AddCityDialogListener { + void addCity(City city); + } + private AddCityDialogListener listener; + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + if (context instanceof AddCityDialogListener) { + listener = (AddCityDialogListener) context; + } else { + throw new RuntimeException(context + "must implement AddCityDialogListener"); + } + } + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + View view = + LayoutInflater.from(getContext()).inflate(R.layout.fragment_add_city, null); + EditText editCityName = view.findViewById(R.id.add_text_city_text); + EditText editProvinceName = view.findViewById(R.id.add_text_province_text); + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + return builder + .setView(view) + .setTitle("Add a city") + .setNegativeButton("Cancel", null) + .setPositiveButton("Add", (dialog, which) -> { + String cityName = editCityName.getText().toString(); + String provinceName = editProvinceName.getText().toString(); + listener.addCity(new City(cityName, provinceName)); + }) + .create(); + } +} + diff --git a/code/app/src/main/java/com/example/listycitylab3/City.java b/code/app/src/main/java/com/example/listycitylab3/City.java new file mode 100644 index 00000000..392b0b65 --- /dev/null +++ b/code/app/src/main/java/com/example/listycitylab3/City.java @@ -0,0 +1,26 @@ +package com.example.listycitylab3; + +import java.io.Serializable; + +public class City implements Serializable { + private String name; + private String province; + public City(String name, String province) { + this.name = name; + this.province = province; + } + public String getName() { + return name; + } + public String getProvince() { + return province; + } + + public void setName(String name) { + this.name = name; + } + + public void setProvince(String province) { + this.province = province; + } +} diff --git a/code/app/src/main/java/com/example/listycitylab3/CityArrayAdapter.java b/code/app/src/main/java/com/example/listycitylab3/CityArrayAdapter.java new file mode 100644 index 00000000..20901b54 --- /dev/null +++ b/code/app/src/main/java/com/example/listycitylab3/CityArrayAdapter.java @@ -0,0 +1,38 @@ +package com.example.listycitylab3; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; + +public class CityArrayAdapter extends ArrayAdapter { + public CityArrayAdapter(Context context, ArrayList cities) { + super(context, 0, cities); + } + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup + parent) { + View view; + if (convertView == null) { + view = LayoutInflater.from(getContext()).inflate(R.layout.content, + parent, false); + } else { + view = convertView; + } + City city = getItem(position); + TextView cityName = view.findViewById(R.id.city_text); + TextView provinceName = view.findViewById(R.id.province_text); + + cityName.setText(city.getName()); + provinceName.setText(city.getProvince()); + return view; + } +} \ No newline at end of file diff --git a/code/app/src/main/java/com/example/listycitylab3/EditFragment.java b/code/app/src/main/java/com/example/listycitylab3/EditFragment.java new file mode 100644 index 00000000..d7e204cc --- /dev/null +++ b/code/app/src/main/java/com/example/listycitylab3/EditFragment.java @@ -0,0 +1,92 @@ +package com.example.listycitylab3; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; + + + +// A DialogFragment that lets the user edit an existing City object +public class EditFragment extends DialogFragment { + + // Callback interface so the host Activity can receive the edited City + public interface EditCityDialogListener { + void onCityEdited(int position, City updatedCity); + } + + private EditCityDialogListener listener; // reference to the host Activity + + // Factory method to create a new instance of the EditFragment + // Takes a City object and its position in the list, puts them in a Bundle + public static EditFragment newInstance(City city, int position) { + Bundle args = new Bundle(); + args.putSerializable("city", city); // City must implement Serializable + args.putInt("pos", position); // keep track of which item is being edited + EditFragment f = new EditFragment(); + f.setArguments(args); // attach arguments to the fragment + return f; + } + + // Called when the fragment is attached to its host Activity + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + // Ensure the host Activity implements the callback interface + if (context instanceof EditCityDialogListener) { + listener = (EditCityDialogListener) context; + } else { + throw new RuntimeException(context + " must implement EditCityDialogListener"); + } + } + + // Create and return the actual dialog UI + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + // Retrieve the City object and its position from arguments + City city = (City) requireArguments().getSerializable("city"); + int position = requireArguments().getInt("pos"); + + // Inflate the custom layout for editing a city + View view = LayoutInflater.from(requireContext()) + .inflate(R.layout.fragment_edit_city, null); + + // Get references to the EditText fields + EditText editCityName = view.findViewById(R.id.edit_text_city_text); + EditText editProvinceName = view.findViewById(R.id.edit_text_province_text); + + // Pre-fill the text fields with the current city data (if provided) + if (city != null) { + editCityName.setText(city.getName()); + editProvinceName.setText(city.getProvince()); + } + + // Build and return the AlertDialog + return new AlertDialog.Builder(requireContext()) + .setTitle("Edit City") // dialog title + .setView(view) // set the custom layout + .setPositiveButton("Confirm", (d, w) -> { + // When user clicks Confirm, grab the new values + String newName = editCityName.getText().toString().trim(); + String newProv = editProvinceName.getText().toString().trim(); + + // Create a new City object with updated values + // (or you could directly mutate the existing City via setters) + City updated = new City(newName, newProv); + + // Notify the Activity through the callback + listener.onCityEdited(position, updated); + }) + .setNegativeButton("Cancel", null) // Dismiss if Cancel pressed + .create(); + } +} + diff --git a/code/app/src/main/java/com/example/listycitylab3/MainActivity.java b/code/app/src/main/java/com/example/listycitylab3/MainActivity.java index 12fdd6ac..f4f674a2 100644 --- a/code/app/src/main/java/com/example/listycitylab3/MainActivity.java +++ b/code/app/src/main/java/com/example/listycitylab3/MainActivity.java @@ -1,36 +1,69 @@ package com.example.listycitylab3; import android.os.Bundle; -import android.widget.ArrayAdapter; import android.widget.ListView; import androidx.appcompat.app.AppCompatActivity; +import com.google.android.material.floatingactionbutton.FloatingActionButton; + import java.util.ArrayList; -import java.util.Arrays; -public class MainActivity extends AppCompatActivity { +public class MainActivity extends AppCompatActivity + implements AddCityFragment.AddCityDialogListener, + EditFragment.EditCityDialogListener { - private ArrayList dataList; + private ArrayList dataList; private ListView cityList; - private ArrayAdapter cityAdapter; + private CityArrayAdapter cityAdapter; + + // AddCity callback + @Override + public void addCity(City city) { + cityAdapter.add(city); + cityAdapter.notifyDataSetChanged(); + } + + // EditCity callback + @Override + public void onCityEdited(int position, City updatedCity) { + // Replace the item at the exact position and refresh + dataList.set(position, updatedCity); + cityAdapter.notifyDataSetChanged(); + } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - String[] cities = { - "Edmonton", "Vancouver", "Moscow", - "Sydney", "Berlin", "Vienna", - "Tokyo", "Beijing", "Osaka", "New Delhi" - }; + // Seed data + String[] cities = {"Edmonton", "Vancouver", "Toronto"}; + String[] provinces = {"AB", "BC", "ON"}; dataList = new ArrayList<>(); - dataList.addAll(Arrays.asList(cities)); - + for (int i = 0; i < cities.length; i++) { + dataList.add(new City(cities[i], provinces[i])); + } + + // List + adapter cityList = findViewById(R.id.city_list); - cityAdapter = new ArrayAdapter<>(this, R.layout.content, dataList); + + cityAdapter = new CityArrayAdapter(this, dataList); cityList.setAdapter(cityAdapter); + + // Add City + FloatingActionButton fab = findViewById(R.id.button_add_city); + fab.setOnClickListener(v -> + new AddCityFragment().show(getSupportFragmentManager(), "Add City") + ); + + // Edit City + cityList.setOnItemClickListener((parent, view, position, id) -> { + City selected = dataList.get(position); + EditFragment.newInstance(selected, position) + .show(getSupportFragmentManager(), "Edit City"); + }); } -} \ No newline at end of file +} + diff --git a/code/app/src/main/res/layout/activity_main.xml b/code/app/src/main/res/layout/activity_main.xml index 60977773..46127097 100644 --- a/code/app/src/main/res/layout/activity_main.xml +++ b/code/app/src/main/res/layout/activity_main.xml @@ -1,31 +1,32 @@ + - - - - - + + android:id="@+id/city_list" /> + diff --git a/code/app/src/main/res/layout/content.xml b/code/app/src/main/res/layout/content.xml index 930ceece..5650dabb 100644 --- a/code/app/src/main/res/layout/content.xml +++ b/code/app/src/main/res/layout/content.xml @@ -1,8 +1,21 @@ - \ No newline at end of file + android:layout_height="wrap_content" + android:orientation="horizontal"> + + + \ No newline at end of file diff --git a/code/app/src/main/res/layout/fragment_add_city.xml b/code/app/src/main/res/layout/fragment_add_city.xml new file mode 100644 index 00000000..598d88e2 --- /dev/null +++ b/code/app/src/main/res/layout/fragment_add_city.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/code/app/src/main/res/layout/fragment_edit_city.xml b/code/app/src/main/res/layout/fragment_edit_city.xml new file mode 100644 index 00000000..fcd8bf33 --- /dev/null +++ b/code/app/src/main/res/layout/fragment_edit_city.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file