diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..530de1f --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..6c93e6d Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/example/acloc/MainActivity.java b/app/src/main/java/com/example/acloc/MainActivity.java new file mode 100644 index 0000000..d7edf67 --- /dev/null +++ b/app/src/main/java/com/example/acloc/MainActivity.java @@ -0,0 +1,275 @@ +package com.example.acloc; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.RelativeLayout; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.view.menu.MenuBuilder; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; + +import com.example.acloc.activities.ManageRolesActivity; +import com.example.acloc.api.ApiClient; +import com.example.acloc.dialog.AlertChangePasswordDialog; +import com.example.acloc.dialog.AlertViewOrUpdateProfileDialog; +import com.example.acloc.fragments.FavoriteFragment; +import com.example.acloc.fragments.MapFragment; +import com.example.acloc.fragments.MyReportsFragment; +import com.example.acloc.interfaces.ApiService; +import com.example.acloc.utility.DialogUtils; +import com.example.acloc.utility.Helper; +import com.example.acloc.utility.SharedPref; +import com.google.android.material.bottomnavigation.BottomNavigationView; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.Locale; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class MainActivity extends AppCompatActivity { + private static final String TAG = MainActivity.class.getSimpleName(); + private RelativeLayout rlMainActivity; + private BottomNavigationView bottomNavigationView; + private Toolbar toolbar; + private Dialog dialog; + private Context context; + + @Override + protected void onCreate(Bundle savedInstanceState) { + // Get saved language from SharedPreferences + String savedLanguage = SharedPref.getLanguage(this); + + // Check if current language matches saved + if (!Locale.getDefault().getLanguage().equals(savedLanguage)) { + setLocale(savedLanguage); // Apply only if needed + } + + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + initToolbar(); + initUI(); + initObj(); + initListeners(); + // Load default fragment + loadDefaultFragment(savedInstanceState); + getRoleUuids(); + } + + private void initToolbar() { + toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + } + + private void initUI() { + rlMainActivity = findViewById(R.id.rlMainActivity); + bottomNavigationView = findViewById(R.id.bottomNavView); + } + + @SuppressLint("RestrictedApi") + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_dashboard, menu); + if (menu instanceof MenuBuilder) { + MenuBuilder m = (MenuBuilder) menu; + m.setOptionalIconsVisible(true); + } + + // show/hide the "Manage Roles" menu item + MenuItem manageNotesItem = menu.findItem(R.id.menu_manageRoles); + if (!"admin".equalsIgnoreCase(SharedPref.getRole(context))) { + manageNotesItem.setVisible(false); // hide for non-admins + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + int id = item.getItemId(); + + if (id == R.id.menu_profile) { + dialog = new AlertViewOrUpdateProfileDialog(context) + .openProfileDialog(); + return true; + } else if (id == R.id.menu_changePassword) { + dialog = new AlertChangePasswordDialog(context) + .openChangePasswordDialog(); + } else if (id == R.id.menu_changeLanguage) { + changeLanguage(); + } else if (id == R.id.menu_manageRoles) { + Helper.goTo(MainActivity.this, ManageRolesActivity.class); + } else if (id == R.id.menu_logout) { + AlertDialog dialog = DialogUtils.logoutDialog(context); + dialog.show(); + } + return super.onOptionsItemSelected(item); + } + + private void changeLanguage() { + String currentLanguage = Locale.getDefault().getLanguage(); + String newLanguage = currentLanguage.equals("es") ? "en" : "es"; + + AlertDialog dialog = DialogUtils.confirmationDialog( + this, + getString(R.string.change_language_confirmation), + (dialogInterface, i) -> { + SharedPref.setLanguage(this, newLanguage); // Save to SharedPref + setLocale(newLanguage); // Apply language + + // Restart app to apply change + Intent intent = new Intent(this, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + finish(); + } + ); + dialog.show(); + } + + // Method to update the language dynamically + private void setLocale(String lang) { + Locale locale = new Locale(lang); + Locale.setDefault(locale); + + Configuration config = new Configuration(); + config.setLocale(locale); + + getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics()); + } + + private void initObj() { + context = this; + } + + private void initListeners() { + // Set item selected listener for bottom navigation + bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() { + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem item) { + Fragment selectedFragment = null; + String title = ""; + + int id = item.getItemId(); + if (id == R.id.menu_myReports) { + selectedFragment = new MyReportsFragment(); + title = getString(R.string.My_Report); + } else if (id == R.id.menu_map) { + selectedFragment = new MapFragment(); + title = getString(R.string.Map); + } else if (id == R.id.menu_favorite) { + selectedFragment = new FavoriteFragment(); + title = getString(R.string.Favorite); + } + + if (selectedFragment != null) { + loadFragment(selectedFragment, title); + } + return true; + } + }); + } + + private void loadFragment(Fragment fragment, String title) { + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.flMainContainer, fragment); + transaction.commit(); + + if (getSupportActionBar() != null) { + getSupportActionBar().setTitle(title); // Correct way to set title with support action bar + } + } + + private void loadDefaultFragment(Bundle savedInstanceState) { + if (savedInstanceState == null) { + Fragment defaultFragment = new MapFragment(); + String defaultTitle = getString(R.string.Map); + + loadFragment(defaultFragment, defaultTitle); + bottomNavigationView.setSelectedItemId(R.id.menu_map); + } + } + + // to change the fragment + public void openFragmentFromChild(Fragment fragment, String title, int navItemId) { + Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.flMainContainer); + if (currentFragment != null && currentFragment.getClass().equals(fragment.getClass())) { + return; + } + + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out); + transaction.replace(R.id.flMainContainer, fragment); + transaction.commit(); + + if (getSupportActionBar() != null) { + getSupportActionBar().setTitle(title); + } + + bottomNavigationView.setSelectedItemId(navItemId); + } + + private void getRoleUuids() { + DialogUtils.showLoadingDialog(context, ""); + + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.getRoles(token); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful() && response.body() != null) { + JsonObject responseBody = response.body(); + JsonObject data = responseBody.getAsJsonObject("_data"); + + if (data != null && data.has("role")) { + JsonArray rolesArray = data.getAsJsonArray("role"); + + for (JsonElement element : rolesArray) { + JsonObject roleObj = element.getAsJsonObject(); + String roleName = roleObj.get("name").getAsString(); + String roleUuid = roleObj.get("uuid").getAsString(); + + if ("admin".equalsIgnoreCase(roleName)) { + SharedPref.setAdminRoleUuid(context, roleUuid); + } else if ("viewer".equalsIgnoreCase(roleName)) { + SharedPref.setViewerRoleUuid(context, roleUuid); + } + } + Log.d(TAG, "Admin UUID: " + SharedPref.getAdminRoleUuid(context)); + Log.d(TAG, "Viewer UUID: " + SharedPref.getViewerRoleUuid(context)); + } else { + Log.d(TAG, "No roles found."); + } + } else { + Log.d(TAG, "Failed to load roles. Try again."); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Get Roles Failure: ", t); +// Helper.makeSnackBar(rlMainActivity, context.getString(R.string.Network_error_Try_again)); + } + }); + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/acloc/activities/AddNewPlaceActivity.java b/app/src/main/java/com/example/acloc/activities/AddNewPlaceActivity.java new file mode 100644 index 0000000..b907eb1 --- /dev/null +++ b/app/src/main/java/com/example/acloc/activities/AddNewPlaceActivity.java @@ -0,0 +1,266 @@ +package com.example.acloc.activities; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.provider.MediaStore; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; +import android.widget.RelativeLayout; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.AppCompatButton; +import androidx.appcompat.widget.Toolbar; + +import com.example.acloc.R; +import com.example.acloc.api.ApiClient; +import com.example.acloc.interfaces.ApiService; +import com.example.acloc.model.Place; +import com.example.acloc.utility.Constants; +import com.example.acloc.utility.DialogUtils; +import com.example.acloc.utility.Helper; +import com.example.acloc.utility.SharedPref; +import com.google.android.material.textfield.TextInputEditText; +import com.google.gson.JsonObject; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class AddNewPlaceActivity extends AppCompatActivity implements View.OnClickListener { + private static final String TAG = AddNewPlaceActivity.class.getSimpleName(); + private RelativeLayout rlAddPlace; + private ImageView ivPlacePhoto; + private TextInputEditText etPlaceName, etLatitude, etLongitude, etAddress, etPlaceDescription; + private AppCompatButton btnSubmit; + private Dialog dialog; + private Context context; + private double lat, lng; + private String placeName, address; + + private Place entity; + private String place_uuid; + private static final int PICK_IMAGE_REQUEST = 200; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_add_new_place); + initToolbar(); + initUI(); + initObj(); + setListeners(); + loadIntentData(); + setDataToText(); + } + + private void initToolbar() { + try { + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(false); + getSupportActionBar().setTitle(getString(R.string.Add_Place)); + } + } catch (Exception e) { + Log.e(TAG, "Error in AddNewPlaceActivity", e); + } + } + + private void initUI() { + rlAddPlace = findViewById(R.id.rlAddPlace); + ivPlacePhoto = findViewById(R.id.ivPlacePhoto); + etPlaceName = findViewById(R.id.etPlaceName); + etLatitude = findViewById(R.id.etLatitude); + etLongitude = findViewById(R.id.etLongitude); + etAddress = findViewById(R.id.etAddress); + etPlaceDescription = findViewById(R.id.etPlaceDescription); + btnSubmit = findViewById(R.id.btnSubmit); + } + + private void loadIntentData() { + entity = (Place) getIntent().getSerializableExtra(Constants.PLACE); + place_uuid = entity.getUuid(); // setting place uuid first + if (entity != null) { + Log.d(TAG, + "PLACE ENTITY DATA \n " + + "Place uuid: " + entity.getUuid() + "\n " + + "Place name: " + entity.getName() + "\n " + + "Place address: " + entity.getAddress() + "\n " + + "Place created by: " + entity.getCreatedBy() + "\n " + + "Place latitude: " + entity.getLatitude() + "\n " + + "Place longitude: " + entity.getLongitude() + "\n " + + "Place description: " + entity.getDescription() + "\n " + ); + } + } + + private void setListeners() { + ivPlacePhoto.setOnClickListener(this); + btnSubmit.setOnClickListener(this); + } + + private void initObj() { + context = this; + entity = new Place(); + } + + private void setDataToText() { + etPlaceName.setText(entity.getName()); + etLatitude.setText(entity.getLatitude()); + etLongitude.setText(entity.getLongitude()); + etAddress.setText(entity.getAddress()); + etPlaceDescription.setText(entity.getDescription()); + + } + + @SuppressLint("NonConstantResourceId") + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.ivPlacePhoto) { + onClickIvPlacePhoto(); + } else if (id == R.id.btnSubmit) { + onClickBtnSubmit(); + } + } + + private void onClickIvPlacePhoto() { + // Open the gallery to select an image + Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + intent.setType("image/*"); // Restrict to images only + startActivityForResult(intent, PICK_IMAGE_REQUEST); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null && data.getData() != null) { + // Get the image URI + Uri selectedImageUri = data.getData(); + + // Set the image in the ImageView + ivPlacePhoto.setImageURI(selectedImageUri); + } + } + + private void onClickBtnSubmit() { + View[] views = {etPlaceName, etLatitude, etLongitude, etAddress, etPlaceDescription}; + if (Helper.isEmptyFieldValidation(views)) { + setInputDataToEntity(); + + if (place_uuid != null && !place_uuid.isEmpty()) { + // Update existing place + updatePlaceRetrofit(place_uuid, entity.getName(), entity.getDescription(), + entity.getAddress(), entity.getLatitude(), entity.getLongitude(), entity.getCreatedBy()); + } else { + // Insert new place + insertPlaceRetrofit(entity.getName(), entity.getDescription(), + entity.getAddress(), entity.getLatitude(), entity.getLongitude(), entity.getCreatedBy()); + } + } + } + + private void setInputDataToEntity() { + entity.setName(Helper.getStringFromInput(etPlaceName)); + entity.setDescription(Helper.getStringFromInput(etPlaceDescription)); + entity.setAddress(Helper.getStringFromInput(etAddress)); + entity.setLatitude(Helper.getStringFromInput(etLatitude)); + entity.setLongitude(Helper.getStringFromInput(etLongitude)); + entity.setCreatedBy(SharedPref.getUserUid(context)); + entity.setUuid(place_uuid); + } + + private void insertPlaceRetrofit(String name, String description, String address, + String latitude, String longitude, String createdBy) { + DialogUtils.showLoadingDialog(context, context.getString(R.string.Please_wait)); + + JsonObject placeBody = new JsonObject(); + placeBody.addProperty("name", name); + placeBody.addProperty("description", description); + placeBody.addProperty("address", address); + placeBody.addProperty("latitude", latitude); + placeBody.addProperty("longitude", longitude); + placeBody.addProperty("createdBy", createdBy); + + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.insertPlace(token, placeBody); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful() && response.body() != null) { + JsonObject responseBody = response.body(); + JsonObject data = responseBody.getAsJsonObject("_data"); + if (data != null && data.has("places")) { + JsonObject placeObject = data.getAsJsonArray("places").get(0).getAsJsonObject(); + place_uuid = placeObject.get("uuid").getAsString(); + Log.d(TAG, "Place UUID: " + place_uuid); + Helper.makeSnackBar(rlAddPlace, context.getString(R.string.Place_inserted_successfully)); + rlAddPlace.postDelayed(() -> { + finish(); + }, 500); + + } else { + Helper.makeSnackBar(rlAddPlace, context.getString(R.string.Failed_to_extract_place_Try_again)); + } + } else { + Helper.makeSnackBar(rlAddPlace, context.getString(R.string.Insert_failed_Server_error)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Insert Place Error: ", t); + Helper.makeSnackBar(rlAddPlace, context.getString(R.string.Network_error_Try_again)); + } + }); + } + + private void updatePlaceRetrofit(String uuid, String name, String description, String address, + String latitude, String longitude, String createdBy) { + + DialogUtils.showLoadingDialog(context, getString(R.string.Updating_place)); + + JsonObject placeBody = new JsonObject(); + placeBody.addProperty("name", name); + placeBody.addProperty("description", description); + placeBody.addProperty("address", address); + placeBody.addProperty("latitude", latitude); + placeBody.addProperty("longitude", longitude); + placeBody.addProperty("createdBy", createdBy); + + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.updatePlace(token, uuid, placeBody); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful()) { + Helper.makeSnackBar(rlAddPlace, getString(R.string.Place_updated_successfully)); + rlAddPlace.postDelayed(() -> { + finish(); + }, 500); + } else { + Helper.makeSnackBar(rlAddPlace, getString(R.string.Update_failed_Server_error_Try_again)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Update Place Error: ", t); + Helper.makeSnackBar(rlAddPlace, context.getString(R.string.Network_error_Try_again)); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/acloc/activities/AddReportActivity.java b/app/src/main/java/com/example/acloc/activities/AddReportActivity.java new file mode 100644 index 0000000..55b8966 --- /dev/null +++ b/app/src/main/java/com/example/acloc/activities/AddReportActivity.java @@ -0,0 +1,348 @@ +package com.example.acloc.activities; + +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.graphics.PorterDuff; +import android.net.Uri; + +import android.os.Bundle; +import android.provider.MediaStore; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; +import android.widget.RelativeLayout; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.AppCompatButton; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; + +import com.example.acloc.R; +import com.example.acloc.api.ApiClient; +import com.example.acloc.interfaces.ApiService; +import com.example.acloc.model.Place; +import com.example.acloc.model.Report; +import com.example.acloc.utility.Constants; +import com.example.acloc.utility.DialogUtils; +import com.example.acloc.utility.Helper; +import com.example.acloc.utility.SharedPref; +import com.google.android.material.textfield.TextInputEditText; +import com.google.gson.JsonObject; + + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class AddReportActivity extends AppCompatActivity implements View.OnClickListener { + + public static final String TAG = AddReportActivity.class.getSimpleName(); + private RelativeLayout rlAddReport; + private TextInputEditText etDescription, etPlaceName; + private ImageView ivReportPhoto; + private ImageView ivThumbsUp, ivThumbsAverage, ivThumbsDown; + private AppCompatButton btnSubmit; + private Context context; + private Dialog dialog; + private Place placeEntity; + private Report reportEntity; + private String report_type_uuid, place_uuid; + private int reportRating; + private String report_uuid; + + private static final int PICK_IMAGE_REQUEST = 100; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_add_report); + + initToolbar(); + initUI(); + loadIntentData(); + initListener(); + initObj(); + } + + private void initToolbar() { + try { + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(false); + getSupportActionBar().setTitle(getString(R.string.Add_Report)); + } + } catch (Exception e) { + Log.e(TAG, "Error in AddReportActivity", e); + } + } + + private void initUI() { + rlAddReport = findViewById(R.id.rlAddReport); + ivReportPhoto = findViewById(R.id.ivReportPhoto); + etPlaceName = findViewById(R.id.etPlaceName); + etDescription = findViewById(R.id.etDescription); + ivThumbsUp = findViewById(R.id.ivThumbsUp); + ivThumbsAverage = findViewById(R.id.ivThumbsAverage); + ivThumbsDown = findViewById(R.id.ivThumbsDown); + btnSubmit = findViewById(R.id.btnSubmit); + } + + private void loadIntentData() { + placeEntity = (Place) getIntent().getSerializableExtra(Constants.PLACE); + if (placeEntity != null) { + place_uuid = placeEntity.getUuid(); + etPlaceName.setText(placeEntity.getName()); //Just to display place name in report +// Log.d(TAG, +// "PLACE ENTITY DATA \n " + +// "Place uuid: " + placeEntity.getUuid() + "\n " + +// "Place name: " + placeEntity.getName() + "\n " + +// "Place address: " + placeEntity.getAddress() + "\n " + +// "Place created by: " + placeEntity.getCreatedBy() + "\n " + +// "Place latitude: " + placeEntity.getLatitude() + "\n " + +// "Place longitude: " + placeEntity.getLongitude() + "\n " + +// "Place description: " + placeEntity.getDescription() + "\n " +// ); + } + + reportEntity = (Report) getIntent().getSerializableExtra(Constants.REPORT); + if (reportEntity != null) { + report_uuid = reportEntity.getUuid(); // setting place uuid first + place_uuid = reportEntity.getPlaceUuid(); + Log.d(TAG, "" + + "place uuid: " + reportEntity.getPlaceUuid() + + "\n fkplace " + reportEntity.getFkPlace()); + setDataToEditText(); + } + } + + private void setDataToEditText() { + etPlaceName.setText(reportEntity.getPlaceName()); + etDescription.setText(reportEntity.getDescription()); + int rating = reportEntity.getReportRating(); + if (rating == 1) { + ivThumbsDown.setColorFilter(ContextCompat.getColor(this, R.color.red), PorterDuff.Mode.SRC_IN); + ivThumbsUp.setColorFilter(null); // reset the other + ivThumbsAverage.setColorFilter(null); + reportRating = Constants.BAD_RATING; //1 + } else if (rating == 2) { + ivThumbsAverage.setColorFilter(ContextCompat.getColor(this, R.color.yellow), PorterDuff.Mode.SRC_IN); + ivThumbsUp.setColorFilter(null); // reset the others + ivThumbsDown.setColorFilter(null); + reportRating = Constants.AVERAGE_RATING; //2 + } else if (rating == 3) { + ivThumbsUp.setColorFilter(ContextCompat.getColor(this, R.color.green), PorterDuff.Mode.SRC_IN); + ivThumbsDown.setColorFilter(null); // reset the other + ivThumbsAverage.setColorFilter(null); + reportRating = Constants.GOOD_RATING; //3 + } + } + + private void initListener() { + ivThumbsUp.setOnClickListener(this); + ivThumbsAverage.setOnClickListener(this); + ivThumbsDown.setOnClickListener(this); + ivReportPhoto.setOnClickListener(this); + btnSubmit.setOnClickListener(this); + } + + private void initObj() { + context = this; + reportEntity = new Report(); + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.ivThumbsUp) { + onClickThumbsUp(); + } else if (id == R.id.ivThumbsAverage) { + onClickThumbsAverage(); + } else if (id == R.id.ivThumbsDown) { + onClickThumbsDown(); + } else if (id == R.id.ivReportPhoto) { + onClickIvReportPhoto(); + } else if (id == R.id.btnSubmit) { + onClickBtnSubmit(); + } + } + + private void onClickIvReportPhoto() { + // Open the gallery to select an image + Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + intent.setType("image/*"); // Restrict to images only + startActivityForResult(intent, PICK_IMAGE_REQUEST); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null && data.getData() != null) { + // Get the image URI + Uri selectedImageUri = data.getData(); + + // Set the image in the ImageView + ivReportPhoto.setImageURI(selectedImageUri); + } + } + + private void onClickThumbsUp() { + ivThumbsUp.setColorFilter(ContextCompat.getColor(this, R.color.green), PorterDuff.Mode.SRC_IN); + ivThumbsDown.setColorFilter(null); // reset the other + ivThumbsAverage.setColorFilter(null); + reportRating = Constants.GOOD_RATING; //3 + } + + private void onClickThumbsAverage() { + ivThumbsAverage.setColorFilter(ContextCompat.getColor(this, R.color.yellow), PorterDuff.Mode.SRC_IN); + ivThumbsUp.setColorFilter(null); // reset the others + ivThumbsDown.setColorFilter(null); + reportRating = Constants.AVERAGE_RATING; //2 + } + + private void onClickThumbsDown() { + ivThumbsDown.setColorFilter(ContextCompat.getColor(this, R.color.red), PorterDuff.Mode.SRC_IN); + ivThumbsUp.setColorFilter(null); // reset the other + ivThumbsAverage.setColorFilter(null); + reportRating = Constants.BAD_RATING; //1 + + } + + private void onClickBtnSubmit() { + View[] views = {etPlaceName, etDescription}; + if (Helper.isEmptyFieldValidation(views) && isValidateRating()) { + setInputDataToEntity(); + + if (report_uuid != null && !report_uuid.isEmpty()) { + // Update existing report + updateReportRetrofit( + report_uuid, + place_uuid, + SharedPref.getUserUid(context), + String.valueOf(reportEntity.getReportRating()), + reportEntity.getDescription(), + reportEntity.getCreatedBy() + ); + } else { + insertReportRetrofit( + placeEntity.getUuid(), + SharedPref.getUserUid(context), + String.valueOf(reportEntity.getReportRating()), + reportEntity.getDescription(), + reportEntity.getCreatedBy() + ); + } + } + } + + private void setInputDataToEntity() { + reportEntity.setDescription(Helper.getStringFromInput(etDescription)); + reportEntity.setReportRating(reportRating); + reportEntity.setCreatedBy(SharedPref.getUserUid(context)); + } + + private boolean isValidateRating() { + if (reportRating != 0) { + return true; + } else { + Helper.makeSnackBar(rlAddReport, getString(R.string.Please_select_rating)); + return false; + } + } + + private void insertReportRetrofit(String placeUuid, String userUuid, String rating, + String description, String createdBy) { + + DialogUtils.showLoadingDialog(context, getString(R.string.Please_wait)); + + JsonObject reportBody = new JsonObject(); + reportBody.addProperty("place_uuid", placeUuid); + reportBody.addProperty("user_uuid", userUuid); + reportBody.addProperty("rating", rating); + reportBody.addProperty("description", description); + reportBody.addProperty("createdBy", createdBy); + + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.insertReport(token, reportBody); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful() && response.body() != null) { + JsonObject responseBody = response.body(); + JsonObject data = responseBody.getAsJsonObject("_data"); + if (data != null && data.has("reports")) { + JsonObject reportObject = data.getAsJsonArray("reports").get(0).getAsJsonObject(); + String report_uuid = reportObject.get("uuid").getAsString(); + Log.d(TAG, "Report UUID: " + report_uuid); + Helper.makeSnackBar(rlAddReport, getString(R.string.Report_submitted_successfully)); + rlAddReport.postDelayed(() -> { + finish(); + }, 500); + // Delay .5 sec +// rlAddReport.postDelayed(() -> { +// Helper.goToAndFinish(AddReportActivity.this, MainActivity.class); +// }, 500); + } else { + Log.d(TAG, "Failed to extract report UUID."); + Helper.makeSnackBar(rlAddReport, getString(R.string.Something_went_wrong_Try_again)); + } + } else { + Helper.makeSnackBar(rlAddReport, getString(R.string.Report_submission_failed_Try_again)); + Log.e(TAG, "Insert Report Error: " + response.code()); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Insert Report Failure: ", t); + Helper.makeSnackBar(rlAddReport, context.getString(R.string.Network_error_Try_again)); + } + }); + } + + private void updateReportRetrofit(String uuid, String placeUuid, String userUuid, String rating, + String description, String createdBy) { + + DialogUtils.showLoadingDialog(context, getString(R.string.Updating_report)); + + JsonObject reportBody = new JsonObject(); + reportBody.addProperty("place_uuid", placeUuid); + reportBody.addProperty("user_uuid", userUuid); + reportBody.addProperty("rating", rating); + reportBody.addProperty("description", description); + reportBody.addProperty("createdBy", createdBy); + + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.updateReport(token, uuid, reportBody); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful()) { + Helper.makeSnackBar(rlAddReport, getString(R.string.Report_updated_successfully)); + rlAddReport.postDelayed(() -> { + finish(); //to go back to the previous activity + }, 500); + } else { + Helper.makeSnackBar(rlAddReport, getString(R.string.Update_failed_Server_error_Try_again)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Update Place Error: ", t); + Helper.makeSnackBar(rlAddReport, context.getString(R.string.Network_error_Try_again)); + } + }); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/acloc/activities/LoginActivity.java b/app/src/main/java/com/example/acloc/activities/LoginActivity.java new file mode 100644 index 0000000..e814eaa --- /dev/null +++ b/app/src/main/java/com/example/acloc/activities/LoginActivity.java @@ -0,0 +1,201 @@ +package com.example.acloc.activities; + +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.AppCompatButton; +import androidx.appcompat.widget.Toolbar; + +import com.example.acloc.MainActivity; +import com.example.acloc.R; +import com.example.acloc.api.ApiClient; +import com.example.acloc.interfaces.ApiService; +import com.example.acloc.model.User; +import com.example.acloc.utility.DialogUtils; +import com.example.acloc.utility.Helper; +import com.example.acloc.utility.SharedPref; +import com.google.android.material.textfield.TextInputEditText; +import com.google.gson.JsonObject; + +import java.io.IOException; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class LoginActivity extends AppCompatActivity implements View.OnClickListener { + private static final String TAG = LoginActivity.class.getSimpleName(); + private RelativeLayout rlLogin; + private TextInputEditText etUsername, etPassword; + private AppCompatButton btnLogin; + private Context context; + private TextView tvRegisterRedirect; + private User entity; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + + checkForLoginStatusAndNavigate(); + } + + private void checkForLoginStatusAndNavigate() { + boolean loginStatus = SharedPref.getIsLoggedIn(this); + boolean isTokenValid = SharedPref.isAccessTokenValid(this); + + if (loginStatus && isTokenValid) { + Helper.goTo(this, MainActivity.class); + finish(); + } else { + // Optionally force logout if token expired + SharedPref.setIsLoggedIn(this, false); + SharedPref.setAccessToken(this, ""); // clear token + setContentView(R.layout.activity_login); + initToolbar(); + initUI(); + initObj(); + initListeners(); + } + } + + + private void initToolbar() { + try { + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(false); + getSupportActionBar().setTitle(getString(R.string.LOGIN)); + } + } catch (Exception e) { + Log.e(TAG, "Error in LoginActivity", e); + } + } + + private void initUI() { + rlLogin = findViewById(R.id.rlLogin); + etUsername = findViewById(R.id.etUsername); + etPassword = findViewById(R.id.etPassword); + btnLogin = findViewById(R.id.btnLogin); + tvRegisterRedirect = findViewById(R.id.tvRegisterRedirect); + } + + private void initListeners() { + btnLogin.setOnClickListener(this); + tvRegisterRedirect.setOnClickListener(this); + } + + private void initObj() { + context = this; + entity = new User(); + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.tvRegisterRedirect) { + onClickRegisterRedirect(); + } else if (id == R.id.btnLogin) { + onClickBtnLogin(); + } + } + + private void onClickRegisterRedirect() { + Helper.goToAndFinish(LoginActivity.this, RegisterActivity.class); + } + + private void onClickBtnLogin() { + View[] views = {etUsername, etPassword}; + if (Helper.isEmptyFieldValidation(views)) { + setInputDataToEntity(); + loginUserWithRetrofit(); + } + } + + private void setInputDataToEntity() { + entity.setUsername(Helper.getStringFromInput(etUsername)); + entity.setPassword(Helper.getStringFromInput(etPassword)); + } + + private void loginUserWithRetrofit() { + DialogUtils.showLoadingDialog(context, getString(R.string.Please_wait)); + + JsonObject jsonParam = new JsonObject(); + jsonParam.addProperty("username", entity.getUsername()); + jsonParam.addProperty("password", entity.getPassword()); + + ApiService apiService = ApiClient.getClient().create(ApiService.class); + Call call = apiService.loginUser(jsonParam); + + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + + if (response.isSuccessful() && response.body() != null) { + JsonObject json = response.body(); + + if (json.has("user")) { + JsonObject userObj = json.getAsJsonObject("user"); + + // 1. Extract user data from _data + if (userObj.has("_data")) { + JsonObject data = userObj.getAsJsonObject("_data"); + + String uuid = data.has("uuid") ? data.get("uuid").getAsString() : ""; + String username = data.has("username") ? data.get("username").getAsString() : ""; + String email = data.has("email") ? data.get("email").getAsString() : ""; + String role = data.has("role") ? data.get("role").getAsString() : ""; + + SharedPref.setUuid(context, uuid); + SharedPref.setUsername(context, username); + SharedPref.setUserEmail(context, email); + SharedPref.setRole(context, role); + } + + // 2. Extract tokens + String accessToken = userObj.has("accessToken") ? userObj.get("accessToken").getAsString() : ""; + String refreshToken = userObj.has("refreshToken") ? userObj.get("refreshToken").getAsString() : ""; + + if (!accessToken.isEmpty()) SharedPref.setAccessToken(context, accessToken); + if (!refreshToken.isEmpty()) + SharedPref.setRefreshToken(context, refreshToken); + + Helper.makeSnackBar(rlLogin, getString(R.string.Login_Successful)); + SharedPref.setIsLoggedIn(context, true); + Helper.goToAndFinish(LoginActivity.this, MainActivity.class); + } else { + Helper.makeSnackBar(rlLogin, getString(R.string.Invalid_Credentials_Please_try_again)); + } + } else { + try { + if (response.errorBody() != null) { + String errorBody = response.errorBody().string(); + Log.e(TAG, "Login Error: " + errorBody); + Helper.makeSnackBar(rlLogin, getString(R.string.Login_failed) + errorBody + " Try again"); + } + } catch (IOException e) { + Log.e(TAG, "Login error reading response", e); +// Helper.makeSnackBar(rlLogin, getString(R.string.Something_went_wrong)); + Helper.makeSnackBar(rlLogin, e.toString() + " Try again"); + } + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Login API call failed", t); +// Helper.makeSnackBar(rlLogin, getString(R.string.Something_went_wrong)); + Helper.makeSnackBar(rlLogin, "API Failure: " + t.toString() + " Try again"); + } + }); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/acloc/activities/ManageRolesActivity.java b/app/src/main/java/com/example/acloc/activities/ManageRolesActivity.java new file mode 100644 index 0000000..7607ea7 --- /dev/null +++ b/app/src/main/java/com/example/acloc/activities/ManageRolesActivity.java @@ -0,0 +1,247 @@ +package com.example.acloc.activities; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.RelativeLayout; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.acloc.R; +import com.example.acloc.adapter.UserAdapter; +import com.example.acloc.api.ApiClient; +import com.example.acloc.interfaces.ApiService; +import com.example.acloc.model.User; +import com.example.acloc.utility.DialogUtils; +import com.example.acloc.utility.Helper; +import com.example.acloc.utility.SharedPref; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.ArrayList; +import java.util.List; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class ManageRolesActivity extends AppCompatActivity { + + public static final String TAG = ManageRolesActivity.class.getSimpleName(); + private RelativeLayout rlManageRoles; + private TextInputLayout tilSearch; + private TextInputEditText etSearchUser; + private RecyclerView rvUsers; + private List userList; + private UserAdapter adapter; + private Context context; + private User userEntity; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_manage_roles); + + initToolbar(); + initUI(); + initListener(); + initObj(); + } + + @Override + public void onResume() { + super.onResume(); + if (adapter != null) { + setDataVisibility(false); + userList.clear(); + } + if (context != null) loadData(); + } + + private void setDataVisibility(boolean isDataAvailable) { + if (isDataAvailable) { + rvUsers.setVisibility(View.VISIBLE); + } else { + // rvReports.setVisibility(View.GONE); + + } + } + + private void initToolbar() { + try { + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(false); + getSupportActionBar().setTitle(getString(R.string.Manage_Roles)); + } + } catch (Exception e) { + Log.e(TAG, "Error in ManageRoles", e); + } + } + + private void initUI() { + rlManageRoles = findViewById(R.id.rlManageRoles); + tilSearch = findViewById(R.id.tilSearch); + etSearchUser = findViewById(R.id.etSearchUser); + rvUsers = findViewById(R.id.rvUsers); + } + + @SuppressLint("ClickableViewAccessibility") + private void initListener() { + + //Search drawable on click + etSearchUser.setOnTouchListener((v, event) -> { + if (event.getAction() == MotionEvent.ACTION_UP) { + int drawableEnd = etSearchUser.getCompoundDrawables()[2] != null + ? etSearchUser.getWidth() - etSearchUser.getPaddingEnd() + : 0; + + if (event.getRawX() >= (etSearchUser.getRight() - etSearchUser.getCompoundDrawables()[2].getBounds().width())) { + // Search icon clicked + filterUserList(Helper.getStringFromInput(etSearchUser)); + return true; + } + } + return false; + }); + + //Enter + etSearchUser.setOnEditorActionListener((v, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + filterUserList(Helper.getStringFromInput(etSearchUser)); + return true; + } + return false; + }); + } + + private void initObj() { + context = this; + userEntity = new User(); + } + + private void loadData() { + try { + if (userList == null) { + userList = new ArrayList<>(); + } + getAllUsers(); + } catch (Exception e) { + Log.e(TAG, "Error in ManageRoles", e); + Helper.makeSnackBar(rlManageRoles, getString(R.string.Something_went_wrong_Try_again)); + } + } + + private void setUpRecyclerView(List userList) { + try { + if (adapter != null) { + adapter.updateUserList(userList); + } else { + adapter = new UserAdapter(context, userList); + rvUsers.setAdapter(adapter); + rvUsers.setLayoutManager(Helper.getVerticalManager(context)); + adapter.notifyDataSetChanged(); + } + if (userList != null && !userList.isEmpty()) { + setDataVisibility(true); // Data available + } else { + setDataVisibility(false); // No data + } + } catch (Exception e) { + Log.e(TAG, "Error in ManageRoles", e); + Helper.showToast(context, getString(R.string.Something_went_wrong_Try_again)); + setDataVisibility(false); + } + } + + private void filterUserList(String query) { + if (TextUtils.isEmpty(query)) { + setUpRecyclerView(userList); // Show full list if query is empty + return; + } + + List filteredList = new ArrayList<>(); + for (User user : userList) { + if (user.getUsername() != null && user.getUsername().toLowerCase().contains(query.toLowerCase())) { + filteredList.add(user); + } + } + + setUpRecyclerView(filteredList); + } + + public void getAllUsers() { + DialogUtils.showLoadingDialog(context, getString(R.string.Loading_users)); + + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.getAllUsers(token); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful() && response.body() != null) { + JsonObject responseBody = response.body(); + JsonObject data = responseBody.getAsJsonObject("_data"); + + if (data != null && data.has("users")) { + userList.clear(); // clear previous list + + for (JsonElement element : data.getAsJsonArray("users")) { + JsonObject userObj = element.getAsJsonObject(); + String username = userObj.get("username").getAsString(); + // Skip the current logged-in user + if (username.equals(SharedPref.getUserName(context))) { + continue; + } + + User user = new User(); + user.setUsername(username); + user.setEmail(userObj.has("email") && !userObj.get("email").isJsonNull() + ? userObj.get("email").getAsString() : null); + user.setUuid(userObj.get("uuid").getAsString()); + user.setFkRole(userObj.get("fk_role").getAsString()); + user.setRole(userObj.get("role").getAsString()); + + userList.add(user); + } + + if (!userList.isEmpty()) { + setUpRecyclerView(userList); + } else { + setDataVisibility(false); + } + + } else { + setDataVisibility(false); + Helper.makeSnackBar(rlManageRoles, getString(R.string.No_users_found)); + } + + } else { + setDataVisibility(false); + Helper.makeSnackBar(rlManageRoles, getString(R.string.Failed_to_load_users_Try_again)); + Log.e(TAG, "Get users error: " + response.code()); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Get users failure: ", t); + Helper.makeSnackBar(rlManageRoles, context.getString(R.string.Network_error_Try_again)); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/acloc/activities/PlaceDetailActivity.java b/app/src/main/java/com/example/acloc/activities/PlaceDetailActivity.java new file mode 100644 index 0000000..494ad16 --- /dev/null +++ b/app/src/main/java/com/example/acloc/activities/PlaceDetailActivity.java @@ -0,0 +1,430 @@ +package com.example.acloc.activities; + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.AppCompatButton; +import androidx.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.acloc.R; +import com.example.acloc.adapter.PlaceReportsAdapter; +import com.example.acloc.api.ApiClient; +import com.example.acloc.interfaces.ApiService; +import com.example.acloc.model.Place; +import com.example.acloc.model.Report; +import com.example.acloc.utility.Constants; +import com.example.acloc.utility.DialogUtils; +import com.example.acloc.utility.Helper; +import com.example.acloc.utility.SharedPref; +import com.google.android.material.textview.MaterialTextView; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class PlaceDetailActivity extends AppCompatActivity implements View.OnClickListener { + public static final String TAG = PlaceDetailActivity.class.getSimpleName(); + private RelativeLayout rlPlaceDetails; + private MaterialTextView etPlaceName, etAddress, etPlaceDescription; + private ImageView ivFavorite, ivEdit; + private AppCompatButton btnSubmit; + private Dialog dialog; + private Context context; + private Place placeEntity; + private String place_uuid; + private boolean isFavorite = false; // Track favorite current state default false + private RecyclerView rvReports; + private TextView tvNoData; + private PlaceReportsAdapter adapter; + private Report reportEntity; + private List reportList; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_place_detail); + + initToolbar(); + initUI(); + loadIntentData(); // load data + initListener(); // set fav, edit btn On Clicks + initObj(); + } + + @Override + public void onResume() { + super.onResume(); + if (adapter != null) { + setDataVisibility(false); + adapter.clearReports(); // Safe way to clear and notify + } + + if (context != null) loadData(); + } + + + private void setDataVisibility(boolean isDataAvailable) { + if (isDataAvailable) { + rvReports.setVisibility(View.VISIBLE); + tvNoData.setVisibility(View.GONE); + } else { + tvNoData.setVisibility(View.VISIBLE); + // rvReports.setVisibility(View.GONE); + + } + } + + private void initToolbar() { + try { + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(false); + getSupportActionBar().setTitle(getString(R.string.Place_Details)); + } + } catch (Exception e) { + Log.e(TAG, "Error in PlaceDetailActivity", e); + } + } + + private void initUI() { + rlPlaceDetails = findViewById(R.id.rlPlaceDetails); + etPlaceName = findViewById(R.id.etPlaceName); + etPlaceDescription = findViewById(R.id.etPlaceDescription); + etAddress = findViewById(R.id.etAddress); + ivFavorite = findViewById(R.id.ivFavorite); + ivEdit = findViewById(R.id.ivEdit); + btnSubmit = findViewById(R.id.btnSubmit); + rvReports = findViewById(R.id.rvReports); + tvNoData = findViewById(R.id.tvNoData); + } + + private void initListener() { + ivFavorite.setOnClickListener(this); + ivEdit.setOnClickListener(this); + btnSubmit.setOnClickListener(this); + } + + private void initObj() { + context = this; + checkIfPlaceIsFavorite(SharedPref.getUserUid(context), place_uuid); + } + + private void loadIntentData() { + placeEntity = (Place) getIntent().getSerializableExtra(Constants.PLACE); + if (placeEntity != null) { + place_uuid = placeEntity.getUuid(); + etPlaceName.setText(placeEntity.getName()); + etAddress.setText(placeEntity.getAddress()); + etPlaceDescription.setText(placeEntity.getDescription()); + Log.d(TAG, + "Place uuid: " + placeEntity.getUuid() + "\n " + + "Place name: " + placeEntity.getName() + "\n " + + "Place name: " + placeEntity.getAddress() + "\n " + + "Place created by: " + placeEntity.getCreatedBy() + "\n " + + "Place latitude: " + placeEntity.getLatitude() + "\n " + + "Place longitude: " + placeEntity.getLongitude() + "\n " + + "Place description: " + placeEntity.getDescription() + "\n " + ); + } + } + + private void loadData() { + try { + if (reportList == null) { + reportList = new ArrayList<>(); + } + getReportsByPlaceUuid(placeEntity.getUuid()); + } catch (Exception e) { + Log.e(TAG, "Error in PlaceDetailActivity", e); + Helper.makeSnackBar(rlPlaceDetails, getString(R.string.Something_went_wrong_Try_again)); + } + } + + private void setUpRecyclerView(List latestReports) { + try { + if (adapter != null) { + adapter.updateReportsList(latestReports); + } else { + adapter = new PlaceReportsAdapter(context, latestReports); + rvReports.setAdapter(adapter); + rvReports.setLayoutManager(Helper.getVerticalManager(context)); + adapter.notifyDataSetChanged(); + } + if (latestReports != null && !latestReports.isEmpty()) { + setDataVisibility(true); // Data available + } else { + setDataVisibility(false); // No data + } + } catch (Exception e) { + Log.e(TAG, "Error in PlaceDetailActivity", e); + Helper.showToast(context, getString(R.string.Something_went_wrong_Try_again)); + setDataVisibility(false); + } + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.ivFavorite) { + onClickFavorite(); + } else if (id == R.id.ivEdit) { + onClickBtnEdit(); + } else if (id == R.id.btnSubmit) { + onClickBtnSubmit(); + } + } + + private void onClickBtnEdit() { + Helper.goTo(context, AddNewPlaceActivity.class, Constants.PLACE, placeEntity); + } + + private void onClickBtnSubmit() { + Helper.goTo(context, AddReportActivity.class, Constants.PLACE, placeEntity); + } + + private void onClickFavorite() { + if (isFavorite) { + removePlaceFromFavorites(SharedPref.getUserUid(context), place_uuid); + } else { + addPlaceToFavorites(SharedPref.getUserUid(context), place_uuid); + } + } + + private void addPlaceToFavorites(String userUuid, String placeUuid) { + DialogUtils.showLoadingDialog(context, getString(R.string.Adding_to_favorites)); + + JsonObject body = new JsonObject(); + body.addProperty("place_uuid", placeUuid); + Log.d(TAG, "Request Body: " + body.toString()); + + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.addPlaceToFavorites(token, userUuid, body); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful()) { + Log.d(TAG, "Place added to fav!"); + isFavorite = true; // Update state + updateFavoriteIcon(); // Update icon + Helper.makeSnackBar(rlPlaceDetails, getString(R.string.Place_added_to_favorites)); + } else { + Log.d(TAG, "Failed to add favorite!"); + try { + if (response.errorBody() != null) { + String errorBody = response.errorBody().string(); + Log.e(TAG, "Error Body: " + errorBody); + + // Try restoring if it's a duplicate (409 Conflict) + if (response.code() == 409 && errorBody.contains("ER_DUP_ENTRY")) { + Log.d(TAG, "Duplicate entry detected, trying to restore..."); + restorePlaceToFavorites(userUuid, placeUuid); + return; + } + } + } catch (IOException e) { + Log.e(TAG, "ERROR: ", e); + } + + Helper.makeSnackBar(rlPlaceDetails, getString(R.string.Failed_to_add_favorite_Server_error)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Add Favorite Error: ", t); + Helper.makeSnackBar(rlPlaceDetails, context.getString(R.string.Network_error_Try_again)); + } + }); + } + + private void restorePlaceToFavorites(String userUuid, String placeUuid) { + DialogUtils.showLoadingDialog(context, getString(R.string.Restoring_favorite)); + JsonObject body = new JsonObject(); + body.addProperty("place_uuid", placeUuid); + + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.restorePlaceToFavorites(token, userUuid, body); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful()) { + Log.d(TAG, "Place restored to favorites!"); + Helper.makeSnackBar(rlPlaceDetails, getString(R.string.Place_restored_to_favorites)); + } else { + Log.e(TAG, "Restore failed. Response code: " + response.code()); + Helper.makeSnackBar(rlPlaceDetails, getString(R.string.Failed_to_restore_favorite)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Restore Favorite Error: ", t); + Helper.makeSnackBar(rlPlaceDetails, context.getString(R.string.Network_error_Try_again)); + } + }); + } + + private void removePlaceFromFavorites(String userUuid, String placeUuid) { + DialogUtils.showLoadingDialog(context, getString(R.string.Removing_from_favorites)); + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.removePlaceFromFavorites(token, userUuid, placeUuid); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful()) { + Log.d(TAG, "Place removed from fav!"); + isFavorite = false; // Update state + updateFavoriteIcon(); // Update icon + Helper.makeSnackBar(rlPlaceDetails, getString(R.string.Removed_from_favorites)); + } else { + Log.d(TAG, "Failed to remove from fav"); + Helper.makeSnackBar(rlPlaceDetails, getString(R.string.Failed_to_remove_from_favorites)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Remove Favorite Error: ", t); + Helper.makeSnackBar(rlPlaceDetails, context.getString(R.string.Network_error_Try_again)); + } + }); + } + + private void checkIfPlaceIsFavorite(String userUuid, String currentPlaceUuid) { + DialogUtils.showLoadingDialog(context, getString(R.string.Checking_favorite_status)); + + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.getFavoritePlaces(token, userUuid); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful() && response.body() != null) { + JsonObject body = response.body(); + JsonArray favoritesArray = body.getAsJsonObject("_data").getAsJsonArray("favorites"); + + isFavorite = false; + for (JsonElement item : favoritesArray) { + JsonObject favoriteObj = item.getAsJsonObject(); + String favPlaceUuid = favoriteObj.get("place_uuid").getAsString(); + if (favPlaceUuid.equals(currentPlaceUuid)) { + isFavorite = true; + break; + } + } + updateFavoriteIcon(); + + } else { + Log.e(TAG, "Failed to fetch favorites. Code: " + response.code()); + isFavorite = false; + updateFavoriteIcon(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Error fetching favorite places", t); + isFavorite = false; + updateFavoriteIcon(); + } + }); + } + + private void updateFavoriteIcon() { + if (isFavorite) { + ivFavorite.setImageResource(R.drawable.ic_favorite); + } else { + ivFavorite.setImageResource(R.drawable.ic_favorite_border); + } + } + + private void getReportsByPlaceUuid(String placeUuid) { + DialogUtils.showLoadingDialog(context, getString(R.string.Loading_reports)); + + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + // Make the API call to fetch reports by place UUID + Call call = apiService.getPlaceReports(token, placeUuid); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful() && response.body() != null) { + JsonObject responseBody = response.body(); + JsonObject data = responseBody.getAsJsonObject("_data"); + + if (data != null && data.has("reports")) { + reportList.clear(); // Clear previous list + + for (JsonElement element : data.getAsJsonArray("reports")) { + JsonObject reportObject = element.getAsJsonObject(); + Report report = new Report(); + + report.setUuid(reportObject.get("uuid").getAsString()); + report.setReportRating(reportObject.get("rating").getAsInt()); + report.setDescription(reportObject.get("description").getAsString()); + report.setPlaceName(reportObject.get("place_name").getAsString()); + report.setPlaceUuid(reportObject.get("place_uuid").getAsString()); + + reportList.add(report); + } + // to get latest report first + Collections.reverse(reportList); + List latestReports = reportList.size() > 5 ? reportList.subList(0, 5) : reportList; + + // Update the RecyclerView with latest reports + setUpRecyclerView(latestReports); + } else { + setDataVisibility(false); + Helper.makeSnackBar(rlPlaceDetails, getString(R.string.No_reports_found)); + } + } else { + setDataVisibility(false); + Helper.makeSnackBar(rlPlaceDetails, getString(R.string.Failed_to_load_reports_Try_again_)); + Log.e(TAG, "Get Reports Error: " + response.code()); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Get Reports Failure: ", t); + Helper.makeSnackBar(rlPlaceDetails, context.getString(R.string.Network_error_Try_again)); + } + }); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/acloc/activities/RegisterActivity.java b/app/src/main/java/com/example/acloc/activities/RegisterActivity.java new file mode 100644 index 0000000..0e3ec8e --- /dev/null +++ b/app/src/main/java/com/example/acloc/activities/RegisterActivity.java @@ -0,0 +1,169 @@ +package com.example.acloc.activities; + +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.AppCompatButton; +import androidx.appcompat.widget.Toolbar; + +import com.example.acloc.R; +import com.example.acloc.api.ApiClient; +import com.example.acloc.interfaces.ApiService; +import com.example.acloc.model.User; +import com.example.acloc.utility.Constants; +import com.example.acloc.utility.DialogUtils; +import com.example.acloc.utility.Helper; +import com.google.android.material.textfield.TextInputEditText; +import com.google.gson.JsonObject; + +import java.io.IOException; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class RegisterActivity extends AppCompatActivity implements View.OnClickListener { + private static final String TAG = RegisterActivity.class.getSimpleName(); + private RelativeLayout rlRegister; + private TextInputEditText etName, etEmail, etPassword; + private AppCompatButton btnRegister; + private TextView tvLoginRedirect; + private Context context; + private User entity; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_register); + + initToolbar(); + initUI(); + initObj(); + initListeners(); + } + + private void initToolbar() { + try { + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(false); + getSupportActionBar().setTitle(getString(R.string.REGISTER)); + } + } catch (Exception e) { + Log.e(TAG, "Error in RegisterActivity", e); + } + } + + private void initUI() { + rlRegister = findViewById(R.id.rlRegister); + etName = findViewById(R.id.etName); + etEmail = findViewById(R.id.etEmail); + etPassword = findViewById(R.id.etPassword); + btnRegister = findViewById(R.id.btnRegister); + tvLoginRedirect = findViewById(R.id.tvLoginRedirect); + } + + private void initListeners() { + btnRegister.setOnClickListener(this); + tvLoginRedirect.setOnClickListener(this); + } + + private void initObj() { + context = this; + entity = new User(); + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.tvLoginRedirect) { + onClickLoginRedirect(); + } else if (id == R.id.btnRegister) { + onClickBtnRegister(); + } + } + + private void onClickLoginRedirect() { + Helper.goToAndFinish(RegisterActivity.this, LoginActivity.class); + } + + private void onClickBtnRegister() { + View[] views = {etName, etEmail, etPassword}; + if (Helper.isEmptyFieldValidation(views) && Helper.isEmailValid(etEmail) && Helper.isPasswordValid(etPassword)) { + setInputDataToEntity(); + registerUserWithRetrofit(); + } + } + + private void setInputDataToEntity() { + entity.setUsername(Helper.getStringFromInput(etName)); + entity.setEmail(Helper.getStringFromInput(etEmail)); + entity.setPassword(Helper.getStringFromInput(etPassword)); + } + + private void registerUserWithRetrofit() { + DialogUtils.showLoadingDialog(context, getString(R.string.Please_wait)); + + JsonObject jsonParam = new JsonObject(); + jsonParam.addProperty("username", entity.getUsername()); + jsonParam.addProperty("email", entity.getEmail()); + jsonParam.addProperty("password", entity.getPassword()); + jsonParam.addProperty("fk_role", Constants.ADMIN); //Default Admin for now (BUT acc to api it will be Viewer) + + ApiService apiService = ApiClient.getClient().create(ApiService.class); + Call call = apiService.registerUser(jsonParam); + + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + + if (response.isSuccessful() && response.body() != null) { + JsonObject json = response.body(); + + if (json.has("code") && json.get("code").getAsInt() == 409) { + Helper.makeSnackBar(rlRegister, getString(R.string.User_already_exists_Please_login)); + return; + } + + if (json.has("_data") && json.getAsJsonObject("_data").has("message")) { + Helper.makeSnackBar(rlRegister, getString(R.string.Registration_Successful)); + Helper.goToAndFinish(RegisterActivity.this, LoginActivity.class); + } else { + Helper.makeSnackBar(rlRegister, getString(R.string.Invalid_Credentials_Please_try_again)); + } + } else { + try { + if (response.errorBody() != null) { + String errorBody = response.errorBody().string(); + Log.e(TAG, "Error Response: " + errorBody); + Helper.makeSnackBar(rlRegister, "Server Error " + errorBody + " Try again"); + } else { +// Helper.makeSnackBar(rlRegister, getString(R.string.Something_went_wrong)); + Helper.makeSnackBar(rlRegister, "Server Error " + " Try again"); + } + } catch (IOException e) { + Log.e(TAG, "Error reading errorBody", e); +// Helper.makeSnackBar(rlRegister, getString(R.string.Something_went_wrong)); + Helper.makeSnackBar(rlRegister, "API Failure: " + e.toString() + " Try again"); + } + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "API Failure", t); +// Helper.makeSnackBar(rlRegister, getString(R.string.Something_went_wrong)); + Helper.makeSnackBar(rlRegister, "API Failure: " + t.toString() + " Try again"); + } + + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/acloc/adapter/FavoriteAdapter.java b/app/src/main/java/com/example/acloc/adapter/FavoriteAdapter.java new file mode 100644 index 0000000..4e2e086 --- /dev/null +++ b/app/src/main/java/com/example/acloc/adapter/FavoriteAdapter.java @@ -0,0 +1,199 @@ +package com.example.acloc.adapter; + +import android.annotation.SuppressLint; + +import android.app.Activity; +import android.content.Context; +import android.util.Log; +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.example.acloc.R; +import com.example.acloc.activities.PlaceDetailActivity; +import com.example.acloc.api.ApiClient; +import com.example.acloc.interfaces.ApiService; +import com.example.acloc.model.Favorite; +import com.example.acloc.model.Place; +import com.example.acloc.utility.Constants; +import com.example.acloc.utility.DialogUtils; +import com.example.acloc.utility.Helper; +import com.example.acloc.utility.SharedPref; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.List; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class FavoriteAdapter extends RecyclerView.Adapter { + + public static final String TAG = FavoriteAdapter.class.getSimpleName(); + private final Context context; + private List favoriteList; + + public FavoriteAdapter(Context context, List favoriteList) { + this.context = context; + this.favoriteList = favoriteList; + } + + @SuppressLint("NotifyDataSetChanged") + public void updateFavoriteList(List favoriteList) { + try { + if (favoriteList != null) { + this.favoriteList = favoriteList; + notifyDataSetChanged(); + } + } catch (Exception exception) { + Log.e(TAG, "Error in FavoriteAdapter", exception); + } + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(context); + View detailItem = inflater.inflate(R.layout.list_view_favorite, parent, false); + return new ViewHolder(detailItem); + } + + @Override + public void onBindViewHolder(@NonNull FavoriteAdapter.ViewHolder holder, int position) { + try { + if (!favoriteList.isEmpty()) { + Favorite favorite = favoriteList.get(position); + holder.tvPlaceName.setText(favorite.getPlaceName()); + holder.tvDescription.setText(favorite.getPlaceDescription()); + + // handle favorite on click + holder.ivFavorite.setOnClickListener(v -> { + removePlaceFromFavorites(SharedPref.getUserUid(context), favorite.getPlaceUuid(), favorite.getUuid()); + }); + + holder.itemView.setOnClickListener(v -> getPlaceByUuid(favorite.getPlaceUuid())); + } + } catch (Exception e) { + Log.e(TAG, "Error in Favorite Adapter", e); + } + } + + @Override + public int getItemCount() { + return favoriteList.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + private final TextView tvPlaceName, tvDescription; + private final ImageView ivFavorite; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + tvPlaceName = itemView.findViewById(R.id.tvPlaceName); + tvDescription = itemView.findViewById(R.id.tvDescription); + ivFavorite = itemView.findViewById(R.id.ivFavorite); + } + } + + private void removePlaceFromFavorites(String userUuid, String placeUuid, String favoriteUuid) { + DialogUtils.showLoadingDialog(context, context.getString(R.string.Removing_from_favorites)); + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.removePlaceFromFavorites(token, userUuid, placeUuid); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + View rootView = ((Activity) context).findViewById(android.R.id.content); + + if (response.isSuccessful()) { + // Find position of the favorite to be removed + int positionToRemove = -1; + for (int i = 0; i < favoriteList.size(); i++) { + if (favoriteList.get(i).getUuid().equals(favoriteUuid)) { + positionToRemove = i; + break; + } + } + + // Remove the favorite from the list + if (positionToRemove != -1) { + favoriteList.remove(positionToRemove); + notifyItemRemoved(positionToRemove); // Notify the adapter that the item was removed + } + + Log.d(TAG, "Favorite removed!"); + Helper.makeSnackBar(rootView, context.getString(R.string.Favorite_removed)); + } else { + Log.d(TAG, "Failed to remove Favorite"); + Helper.makeSnackBar(rootView, context.getString(R.string.Failed_to_remove_Favorite)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + View rootView = ((Activity) context).findViewById(android.R.id.content); + Log.e(TAG, "Remove Favorite Error: ", t); + Helper.makeSnackBar(rootView, context.getString(R.string.Network_error_Try_again)); + } + }); + } + + private void getPlaceByUuid(String placeUuid) { + DialogUtils.showLoadingDialog(context, context.getString(R.string.Please_wait)); + + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.getPlaceFromUuid(token, placeUuid); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful() && response.body() != null) { + JsonObject responseBody = response.body(); + JsonObject data = responseBody.getAsJsonObject("_data"); + + if (data != null && data.has("places")) { + Place place = new Place(); + + for (JsonElement element : data.getAsJsonArray("places")) { + JsonObject placeObject = element.getAsJsonObject(); + + place.setUuid(placeObject.get("uuid").getAsString()); + place.setName(placeObject.get("name").getAsString()); + place.setDescription(placeObject.get("description").getAsString()); + place.setAddress(placeObject.get("address").getAsString()); + place.setLatitude(placeObject.get("latitude").getAsString()); + place.setLongitude(placeObject.get("longitude").getAsString()); + } + Helper.goTo(context, PlaceDetailActivity.class, Constants.PLACE, place); + } else { +// Helper.makeSnackBar(rlPlaceDetails, getString(R.string.No_reports_found)); + Log.e(TAG, "No place found"); + + } + } else { +// Helper.makeSnackBar(rlPlaceDetails, getString(R.string.Failed_to_load_reports_Try_again_)); + Log.e(TAG, "Get Reports Error: " + response.code()); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Get Reports Failure: ", t); +// Helper.makeSnackBar(rlPlaceDetails, context.getString(R.string.Network_error_Try_again)); + } + }); + } + +} diff --git a/app/src/main/java/com/example/acloc/adapter/MyReportsAdapter.java b/app/src/main/java/com/example/acloc/adapter/MyReportsAdapter.java new file mode 100644 index 0000000..8d05c56 --- /dev/null +++ b/app/src/main/java/com/example/acloc/adapter/MyReportsAdapter.java @@ -0,0 +1,169 @@ +package com.example.acloc.adapter; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.util.Log; +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.core.content.ContextCompat; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.acloc.R; +import com.example.acloc.activities.AddReportActivity; +import com.example.acloc.api.ApiClient; +import com.example.acloc.interfaces.ApiService; +import com.example.acloc.model.Report; +import com.example.acloc.utility.Constants; +import com.example.acloc.utility.DialogUtils; +import com.example.acloc.utility.Helper; +import com.example.acloc.utility.SharedPref; + +import java.util.List; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class MyReportsAdapter extends RecyclerView.Adapter { + public static final String TAG = MyReportsAdapter.class.getSimpleName(); + private final Context context; + private List reportList; + + public MyReportsAdapter(Context context, List reportList) { + this.context = context; + this.reportList = reportList; + } + + @SuppressLint("NotifyDataSetChanged") + public void updateReportList(List reportList) { + try { + if (reportList != null) { + this.reportList = reportList; + notifyDataSetChanged(); + } + } catch (Exception exception) { + Log.e(TAG, "Error in MyReportsAdapter", exception); + } + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(context); + View detailItem = inflater.inflate(R.layout.list_view_my_report, parent, false); + return new ViewHolder(detailItem); + } + + @SuppressLint("SetTextI18n") + @Override + public void onBindViewHolder(@NonNull MyReportsAdapter.ViewHolder holder, int position) { + try { + if (!reportList.isEmpty()) { + Report report = reportList.get(position); + holder.tvPlaceName.setText(report.getPlaceName()); + holder.tvDescription.setText(report.getDescription()); + if (report.getReportRating() == 1) { + holder.tvRating.setText(context.getString(R.string.Rating_BAD)); + holder.tvRating.setBackgroundTintList(ContextCompat.getColorStateList(context, R.color.red)); + } else if (report.getReportRating() == 2) { + holder.tvRating.setText(context.getString(R.string.Rating_AVERAGE)); + holder.tvRating.setBackgroundTintList(ContextCompat.getColorStateList(context, R.color.yellow)); + } else if (report.getReportRating() == 3) { + holder.tvRating.setText(context.getString(R.string.Rating_GOOD)); + holder.tvRating.setBackgroundTintList(ContextCompat.getColorStateList(context, R.color.green)); + } + + holder.ivDelete.setOnClickListener(v -> { + String confirmationText = context.getString(R.string.Delete_report); + + AlertDialog dialog = DialogUtils.confirmationDialog( + context, + confirmationText, + (dialogInterface, i) -> { + removeReport(report.getUuid()); + } + ); + dialog.show(); + }); + + holder.ivEdit.setOnClickListener(v -> { + Helper.goTo(context, AddReportActivity.class, Constants.REPORT, report); + }); + } + } catch (Exception e) { + Log.e(TAG, "Error in MyReports Adapter", e); + } + } + + @Override + public int getItemCount() { + return reportList.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + private final TextView tvPlaceName, tvDescription, tvRating; + private final ImageView ivEdit, ivDelete; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + tvPlaceName = itemView.findViewById(R.id.tvPlaceName); + tvDescription = itemView.findViewById(R.id.tvDescription); + tvRating = itemView.findViewById(R.id.tvRating); + ivEdit = itemView.findViewById(R.id.ivEdit); + ivDelete = itemView.findViewById(R.id.ivDelete); + } + } + + private void removeReport(String reportUuid) { + DialogUtils.showLoadingDialog(context, context.getString(R.string.Removing_Report)); + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.removeReport(token, reportUuid); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + View rootView = ((Activity) context).findViewById(android.R.id.content); + + if (response.isSuccessful()) { + // Find position of the report to be deleted + int positionToRemove = -1; + for (int i = 0; i < reportList.size(); i++) { + if (reportList.get(i).getUuid().equals(reportUuid)) { + positionToRemove = i; + break; + } + } + + // Remove the report from the list + if (positionToRemove != -1) { + reportList.remove(positionToRemove); + notifyItemRemoved(positionToRemove); // Notify the adapter that the item was removed + } + + Log.d(TAG, "Report removed!"); + Helper.makeSnackBar(rootView, context.getString(R.string.Report_removed)); + } else { + Log.d(TAG, "Failed to remove report"); + Helper.makeSnackBar(rootView, context.getString(R.string.Failed_to_remove_report)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + View rootView = ((Activity) context).findViewById(android.R.id.content); + Log.e(TAG, "Remove Report Error: ", t); + Helper.makeSnackBar(rootView, context.getString(R.string.Network_error_Try_again)); + } + }); + } +} diff --git a/app/src/main/java/com/example/acloc/adapter/PlaceReportsAdapter.java b/app/src/main/java/com/example/acloc/adapter/PlaceReportsAdapter.java new file mode 100644 index 0000000..10f097e --- /dev/null +++ b/app/src/main/java/com/example/acloc/adapter/PlaceReportsAdapter.java @@ -0,0 +1,93 @@ +package com.example.acloc.adapter; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.Log; +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.example.acloc.R; +import com.example.acloc.model.Report; + +import java.util.List; + +public class PlaceReportsAdapter extends RecyclerView.Adapter { + public static final String TAG = PlaceReportsAdapter.class.getSimpleName(); + private final Context context; + private List reportList; + + public PlaceReportsAdapter(Context context, List reportList) { + this.context = context; + this.reportList = reportList; + } + public void clearReports() { + reportList.clear(); + notifyDataSetChanged(); + } + + @SuppressLint("NotifyDataSetChanged") + public void updateReportsList(List reportList) { + try { + if (reportList != null) { + this.reportList = reportList; + notifyDataSetChanged(); + } + } catch (Exception exception) { + Log.e(TAG, "Error in PlaceReportsAdapter", exception); + } + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(context); + View detailItem = inflater.inflate(R.layout.list_view_place_reports, parent, false); + return new ViewHolder(detailItem); + } + + @SuppressLint("SetTextI18n") + @Override + public void onBindViewHolder(@NonNull PlaceReportsAdapter.ViewHolder holder, int position) { + try { + if (!reportList.isEmpty()) { + Report report = reportList.get(position); + holder.tvDescription.setText(report.getDescription()); + if (report.getReportRating() == 1) { + holder.tvRating.setText(context.getString(R.string.Rating_BAD)); + holder.ivRating.setImageResource(R.drawable.ic_thumbs_down);} else if (report.getReportRating() == 2) { + holder.tvRating.setText(context.getString(R.string.Rating_AVERAGE)); + holder.ivRating.setImageResource(R.drawable.ic_thumb_up_average); + } else if (report.getReportRating() == 3) { + holder.tvRating.setText(context.getString(R.string.Rating_GOOD)); + holder.ivRating.setImageResource(R.drawable.ic_thumbs_up); + } + + } + } catch (Exception e) { + Log.e(TAG, "Error in PlaceReportsAdapter", e); + } + } + + @Override + public int getItemCount() { + return reportList.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + private final TextView tvDescription, tvRating; + private final ImageView ivRating; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + tvDescription = itemView.findViewById(R.id.tvDescription); + tvRating = itemView.findViewById(R.id.tvRating); + ivRating = itemView.findViewById(R.id.ivRating); + } + } +} diff --git a/app/src/main/java/com/example/acloc/adapter/UserAdapter.java b/app/src/main/java/com/example/acloc/adapter/UserAdapter.java new file mode 100644 index 0000000..4edfefc --- /dev/null +++ b/app/src/main/java/com/example/acloc/adapter/UserAdapter.java @@ -0,0 +1,156 @@ +package com.example.acloc.adapter; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.content.Context; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.acloc.R; +import com.example.acloc.activities.ManageRolesActivity; +import com.example.acloc.api.ApiClient; +import com.example.acloc.interfaces.ApiService; +import com.example.acloc.model.User; +import com.example.acloc.utility.Constants; +import com.example.acloc.utility.DialogUtils; +import com.example.acloc.utility.Helper; +import com.example.acloc.utility.SharedPref; +import com.google.gson.JsonObject; + +import java.util.List; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class UserAdapter extends RecyclerView.Adapter { + public static final String TAG = UserAdapter.class.getSimpleName(); + private final Context context; + private List userList; + + public UserAdapter(Context context, List userList) { + this.context = context; + this.userList = userList; + } + + @SuppressLint("NotifyDataSetChanged") + public void updateUserList(List userList) { + try { + if (userList != null) { + this.userList = userList; + notifyDataSetChanged(); + } + } catch (Exception exception) { + Log.e(TAG, "Error in UserAdapter", exception); + } + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(context); + View detailItem = inflater.inflate(R.layout.list_view_users, parent, false); + return new ViewHolder(detailItem); + } + + @Override + public void onBindViewHolder(@NonNull UserAdapter.ViewHolder holder, int position) { + try { + if (!userList.isEmpty()) { + User user = userList.get(position); + holder.tvUsername.setText(user.getUsername()); + + ArrayAdapter roleAdapter = new ArrayAdapter<>(context, android.R.layout.simple_dropdown_item_1line, Constants.ROLES_OPTIONS); + holder.acRole.setAdapter(roleAdapter); + holder.acRole.setThreshold(1); + + if (user.getRole() != null) { + holder.acRole.setText(user.getRole(), false); + } + + // Show dropdown on click or focus + holder.acRole.setOnClickListener(v -> holder.acRole.showDropDown()); + holder.acRole.setOnFocusChangeListener((v, hasFocus) -> { + if (hasFocus) holder.acRole.showDropDown(); + }); + + // Handle item selection + holder.acRole.setOnItemClickListener((parent, view, posInDropdown, id) -> { + String selectedRole = parent.getItemAtPosition(posInDropdown).toString(); + + AlertDialog dialog = DialogUtils.confirmationDialog(context, context.getString(R.string.Change_Role_to) + " " + selectedRole, (dialogInterface, i) -> { + if (selectedRole.equalsIgnoreCase(Constants.ADMIN)) { + updateRole(user.getUuid(), SharedPref.getAdminRoleUuid(context), holder.itemView); + } else if (selectedRole.equalsIgnoreCase(Constants.VIEWER)) { + updateRole(user.getUuid(), SharedPref.getViewerRoleUuid(context), holder.itemView); + } + }); + + dialog.show(); + }); + } + } catch (Exception e) { + Log.e(TAG, "Error in User Adapter", e); + } + } + + @Override + public int getItemCount() { + return userList.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + private final TextView tvUsername; + private AutoCompleteTextView acRole; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + tvUsername = itemView.findViewById(R.id.tvUsername); + acRole = itemView.findViewById(R.id.acRole); + } + } + + private void updateRole(String uuid, String fkRole, View rootView) { + + DialogUtils.showLoadingDialog(context, context.getString(R.string.Updating_Role)); + + JsonObject userBody = new JsonObject(); + userBody.addProperty("role", fkRole); + + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.updateRole(token, uuid, userBody); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful()) { + Helper.makeSnackBar(rootView, context.getString(R.string.User_Role_update_successfully)); + // Reload userList after update + if (context instanceof ManageRolesActivity) { + ((ManageRolesActivity) context).getAllUsers(); + } + } else { + Helper.makeSnackBar(rootView, context.getString(R.string.Update_failed_Server_error_Try_again)); + notifyDataSetChanged(); // Reload previous userList + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Update Role Error: ", t); + Helper.makeSnackBar(rootView, context.getString(R.string.Network_error_Try_again)); + } + }); + } +} diff --git a/app/src/main/java/com/example/acloc/api/ApiClient.java b/app/src/main/java/com/example/acloc/api/ApiClient.java new file mode 100644 index 0000000..04e5a6d --- /dev/null +++ b/app/src/main/java/com/example/acloc/api/ApiClient.java @@ -0,0 +1,20 @@ +package com.example.acloc.api; + +import static com.example.acloc.utility.Constants.BASE_URL; + +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class ApiClient { + private static Retrofit retrofit; + + public static Retrofit getClient() { + if (retrofit == null) { + retrofit = new Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + } + return retrofit; + } +} diff --git a/app/src/main/java/com/example/acloc/dialog/AlertChangePasswordDialog.java b/app/src/main/java/com/example/acloc/dialog/AlertChangePasswordDialog.java new file mode 100644 index 0000000..0920695 --- /dev/null +++ b/app/src/main/java/com/example/acloc/dialog/AlertChangePasswordDialog.java @@ -0,0 +1,167 @@ +package com.example.acloc.dialog; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.AppCompatButton; + +import com.example.acloc.R; +import com.example.acloc.api.ApiClient; +import com.example.acloc.interfaces.ApiService; +import com.example.acloc.utility.DialogUtils; +import com.example.acloc.utility.Helper; +import com.example.acloc.utility.SharedPref; +import com.google.android.material.textfield.TextInputEditText; +import com.google.gson.JsonObject; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class AlertChangePasswordDialog implements View.OnClickListener { + private static final String TAG = AlertChangePasswordDialog.class.getSimpleName(); + private View alertView; + private TextInputEditText etOldPassword, etNewPassword; + private AppCompatButton btnCancel, btnUpdate; + private Dialog dialog; + private Context context; + + public AlertChangePasswordDialog(Context context) { + this.context = context; + } + + public Dialog openChangePasswordDialog() { + try { + try { + LayoutInflater layoutInflater = ((Activity) context).getLayoutInflater(); + alertView = layoutInflater.inflate(R.layout.alert_dialog_change_password, null); + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context); + if (alertView.getParent() != null) { + ((ViewGroup) alertView.getParent()).removeView(alertView); + } + alertBuilder.setView(alertView); + + initUI(); + setListeners(); + dialog = alertBuilder.create(); + + } catch (Exception e) { + Log.e(TAG, "Error in AlertChangePasswordDialog: ", e); + } + + dialog.show(); + } catch (Exception e) { + Log.e(TAG, "Error in AlertChangePasswordDialog: ", e); + } + return dialog; + } + + private void initUI() { + etOldPassword = alertView.findViewById(R.id.etOldPassword); + etNewPassword = alertView.findViewById(R.id.etNewPassword); + btnCancel = alertView.findViewById(R.id.btnCancel); + btnUpdate = alertView.findViewById(R.id.btnUpdate); + } + + private void setListeners() { + btnCancel.setOnClickListener(this); + btnUpdate.setOnClickListener(this); + } + + @SuppressLint("NonConstantResourceId") + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.btnCancel) { + if (dialog != null && dialog.isShowing()) { + dialog.dismiss(); + } + } else if (id == R.id.btnUpdate) { + onClickBtnUpdate(); + } + } + + private void onClickBtnUpdate() { + View[] views = {etOldPassword, etNewPassword}; + if (Helper.isEmptyFieldValidation(views) && Helper.isPasswordValid(etNewPassword)) { + String username = SharedPref.getUserName(context); + String oldPassword = Helper.getStringFromInput(etOldPassword); + String newPassword = Helper.getStringFromInput(etNewPassword); + String uuid = SharedPref.getUserUid(context); + + verifyOldPasswordRetrofit(uuid, username, oldPassword, newPassword); + } + } + + private void verifyOldPasswordRetrofit(String uuid, String username, String oldPassword, String newPassword) { + DialogUtils.showLoadingDialog(context, context.getString(R.string.Verifying_old_password)); + + JsonObject loginBody = new JsonObject(); + loginBody.addProperty("username", username); + loginBody.addProperty("password", oldPassword); + + ApiService apiService = ApiClient.getClient().create(ApiService.class); + Call call = apiService.verifyOldPassword(loginBody); + + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + + if (response.isSuccessful() && response.body() != null && response.body().has("user")) { + // Proceed to change password + changePasswordRetrofit(uuid, newPassword); + } else { + Helper.makeSnackBar(alertView, context.getString(R.string.Invalid_old_password)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Old password verification failed", t); + Helper.makeSnackBar(alertView, context.getString(R.string.Network_error_Try_again)); + } + }); + } + + private void changePasswordRetrofit(String uuid, String newPassword) { + DialogUtils.showLoadingDialog(context, context.getString(R.string.Changing_password)); + + JsonObject body = new JsonObject(); + body.addProperty("password", newPassword); + + String token = "Bearer " + SharedPref.getAccessToken(context); + + ApiService apiService = ApiClient.getClient().create(ApiService.class); + Call call = apiService.changePassword(token, uuid, body); + + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + + if (response.isSuccessful() && response.body() != null && + response.body().toString().contains("User modified")) { + Helper.makeSnackBar(alertView, context.getString(R.string.Password_updated_successfully)); + } else { + Helper.makeSnackBar(alertView, context.getString(R.string.Password_update_failed)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Password change failed", t); + Helper.makeSnackBar(alertView, context.getString(R.string.Network_error_Try_again)); + } + }); + } +} diff --git a/app/src/main/java/com/example/acloc/dialog/AlertViewAddNewPlaceDialog.java b/app/src/main/java/com/example/acloc/dialog/AlertViewAddNewPlaceDialog.java new file mode 100644 index 0000000..3722e30 --- /dev/null +++ b/app/src/main/java/com/example/acloc/dialog/AlertViewAddNewPlaceDialog.java @@ -0,0 +1,234 @@ +package com.example.acloc.dialog; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.AppCompatButton; + +import com.example.acloc.MainActivity; +import com.example.acloc.R; +import com.example.acloc.api.ApiClient; +import com.example.acloc.interfaces.ApiService; +import com.example.acloc.model.Place; +import com.example.acloc.utility.DialogUtils; +import com.example.acloc.utility.Helper; +import com.example.acloc.utility.SharedPref; +import com.google.android.material.textfield.TextInputEditText; +import com.google.gson.JsonObject; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class AlertViewAddNewPlaceDialog implements View.OnClickListener { + private static final String TAG = AlertViewAddNewPlaceDialog.class.getSimpleName(); + private View alertView; + private ImageView ivPlacePhoto; + private TextInputEditText etPlaceName, etLatitude, etLongitude, etAddress, etPlaceDescription; + private AppCompatButton btnSubmit; + private Dialog dialog; + private Context context; + private double lat, lng; + private String placeName, address; + + private Place entity; + private String place_uuid; + + + public AlertViewAddNewPlaceDialog(Context context, double lat, double lng, String placeName, String address, String description, String uuid) { + this.context = context; + this.lat = lat; + this.lng = lng; + this.placeName = placeName; + this.address = address; + this.entity = new Place(); + this.entity.setDescription(description); + this.entity.setName(placeName); + this.place_uuid = uuid; + } + + public Dialog openPlaceDialog() { + try { + try { + LayoutInflater layoutInflater = ((Activity) context).getLayoutInflater(); + alertView = layoutInflater.inflate(R.layout.alert_dialog_add_new_place, null); + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context); + if (alertView.getParent() != null) { + ((ViewGroup) alertView.getParent()).removeView(alertView); + } + alertBuilder.setView(alertView); + + initUI(); + setListeners(); + setDataToText(); + + dialog = alertBuilder.create(); + } catch (Exception e) { + Log.e(TAG, "Error in AlertPlaceDialog: ", e); + } + + dialog.show(); + } catch (Exception e) { + Log.e(TAG, "Error in AlertPlaceDialog: ", e); + } + return dialog; + } + + private void initUI() { + ivPlacePhoto = alertView.findViewById(R.id.ivPlacePhoto); + etPlaceName = alertView.findViewById(R.id.etPlaceName); + etLatitude = alertView.findViewById(R.id.etLatitude); + etLongitude = alertView.findViewById(R.id.etLongitude); + etAddress = alertView.findViewById(R.id.etAddress); + etPlaceDescription = alertView.findViewById(R.id.etPlaceDescription); + btnSubmit = alertView.findViewById(R.id.btnSubmit); + } + + private void setListeners() { + btnSubmit.setOnClickListener(this); + } + + private void setDataToText() { + etPlaceName.setText(String.valueOf(placeName)); + etLatitude.setText(String.valueOf(lat)); + etLongitude.setText(String.valueOf(lng)); + etAddress.setText(String.valueOf(address)); + etPlaceDescription.setText(entity.getDescription()); + } + + @SuppressLint("NonConstantResourceId") + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.btnSubmit) { + onClickBtnSubmit(); + } + } + + private void onClickBtnSubmit() { + View[] views = {etPlaceName, etLatitude, etLongitude, etAddress, etPlaceDescription}; + if (Helper.isEmptyFieldValidation(views)) { + setInputDataToEntity(); + + if (place_uuid != null && !place_uuid.isEmpty()) { + // Update existing place + updatePlaceRetrofit(place_uuid, entity.getName(), entity.getDescription(), + entity.getAddress(), entity.getLatitude(), entity.getLongitude(), entity.getCreatedBy()); + } else { + // Insert new place + insertPlaceRetrofit(entity.getName(), entity.getDescription(), + entity.getAddress(), entity.getLatitude(), entity.getLongitude(), entity.getCreatedBy()); + } +// Helper.goTo(context, AddReportActivity.class, Constants.PLACE, entity); + } + } + + private void setInputDataToEntity() { + entity.setName(Helper.getStringFromInput(etPlaceName)); + entity.setDescription(Helper.getStringFromInput(etPlaceDescription)); + entity.setAddress(Helper.getStringFromInput(etAddress)); + entity.setLatitude(Helper.getStringFromInput(etLatitude)); + entity.setLongitude(Helper.getStringFromInput(etLongitude)); + entity.setCreatedBy(SharedPref.getUserUid(context)); + entity.setUuid(place_uuid); + } + + private void insertPlaceRetrofit(String name, String description, String address, + String latitude, String longitude, String createdBy) { + DialogUtils.showLoadingDialog(context, "Please wait..."); + + JsonObject placeBody = new JsonObject(); + placeBody.addProperty("name", name); + placeBody.addProperty("description", description); + placeBody.addProperty("address", address); + placeBody.addProperty("latitude", latitude); + placeBody.addProperty("longitude", longitude); + placeBody.addProperty("createdBy", createdBy); + + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.insertPlace(token, placeBody); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful() && response.body() != null) { + JsonObject responseBody = response.body(); + JsonObject data = responseBody.getAsJsonObject("_data"); + if (data != null && data.has("places")) { + JsonObject placeObject = data.getAsJsonArray("places").get(0).getAsJsonObject(); + place_uuid = placeObject.get("uuid").getAsString(); + Log.d(TAG, "Place UUID: " + place_uuid); + Helper.makeSnackBar(alertView, "Place inserted successfully!"); + alertView.postDelayed(() -> { + Helper.goToAndFinish(alertView.getContext(), MainActivity.class); + }, 500); + + } else { + Helper.makeSnackBar(alertView, "Failed to extract place UUID."); + } + } else { + Helper.makeSnackBar(alertView, "Insert failed. Server error."); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Insert Place Error: ", t); + Helper.makeSnackBar(alertView, context.getString(R.string.Network_error_Try_again)); + } + }); + } + + private void updatePlaceRetrofit(String uuid, String name, String description, String address, + String latitude, String longitude, String createdBy) { + + DialogUtils.showLoadingDialog(context, "Updating place..."); + + JsonObject placeBody = new JsonObject(); + placeBody.addProperty("name", name); + placeBody.addProperty("description", description); + placeBody.addProperty("address", address); + placeBody.addProperty("latitude", latitude); + placeBody.addProperty("longitude", longitude); + placeBody.addProperty("createdBy", createdBy); + + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.updatePlace(token, uuid, placeBody); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful()) { + Helper.makeSnackBar(alertView, "Place updated successfully!"); + alertView.postDelayed(() -> { + Helper.goToAndFinish(alertView.getContext(), MainActivity.class); + }, 500); + } else { + Helper.makeSnackBar(alertView, "Update failed. Server error."); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Update Place Error: ", t); + Helper.makeSnackBar(alertView, context.getString(R.string.Network_error_Try_again)); + } + }); + } + + +} diff --git a/app/src/main/java/com/example/acloc/dialog/AlertViewOrUpdateProfileDialog.java b/app/src/main/java/com/example/acloc/dialog/AlertViewOrUpdateProfileDialog.java new file mode 100644 index 0000000..2871041 --- /dev/null +++ b/app/src/main/java/com/example/acloc/dialog/AlertViewOrUpdateProfileDialog.java @@ -0,0 +1,179 @@ +package com.example.acloc.dialog; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.AppCompatButton; + +import com.example.acloc.R; +import com.example.acloc.api.ApiClient; +import com.example.acloc.interfaces.ApiService; +import com.example.acloc.model.User; +import com.example.acloc.utility.DialogUtils; +import com.example.acloc.utility.Helper; +import com.example.acloc.utility.SharedPref; +import com.google.android.material.textfield.TextInputEditText; +import com.google.gson.JsonObject; + +import java.io.IOException; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class AlertViewOrUpdateProfileDialog implements View.OnClickListener { + private static final String TAG = AlertViewOrUpdateProfileDialog.class.getSimpleName(); + private View alertView; + private TextInputEditText etUsername, etEmail; + private AppCompatButton btnCancel, btnUpdate; + private Dialog dialog; + private Context context; + private User entity; + + public AlertViewOrUpdateProfileDialog(Context context) { + this.context = context; + this.entity = new User(); + } + + public Dialog openProfileDialog() { + try { + try { + LayoutInflater layoutInflater = ((Activity) context).getLayoutInflater(); + alertView = layoutInflater.inflate(R.layout.alert_dialog_profile, null); + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context); + if (alertView.getParent() != null) { + ((ViewGroup) alertView.getParent()).removeView(alertView); + } + alertBuilder.setView(alertView); + + initUI(); + setListeners(); + dialog = alertBuilder.create(); + + //get user profile + getProfile(); + + } catch (Exception e) { + Log.e(TAG, "Error in AlertProfileDialog: ", e); + } + + dialog.show(); + } catch (Exception e) { + Log.e(TAG, "Error in AlertProfileDialog: ", e); + } + return dialog; + } + + private void setDataToText() { + try { + if (entity != null) { + etUsername.setText(entity.getUsername()); + etEmail.setText(entity.getEmail()); + } + } catch (Exception e) { + Log.e(TAG, "Error in setDataToText: ", e); + } + } + + private void getProfile() { + entity.setUsername(SharedPref.getUserName(context)); + entity.setEmail(SharedPref.getUserEmail(context)); + setDataToText(); + } + + private void initUI() { + etUsername = alertView.findViewById(R.id.etUsername); + etEmail = alertView.findViewById(R.id.etEmail); + btnCancel = alertView.findViewById(R.id.btnCancel); + btnUpdate = alertView.findViewById(R.id.btnUpdate); + } + + private void setListeners() { + btnCancel.setOnClickListener(this); + btnUpdate.setOnClickListener(this); + } + + @SuppressLint("NonConstantResourceId") + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.btnCancel) { + if (dialog != null && dialog.isShowing()) { + dialog.dismiss(); + } + } else if (id == R.id.btnUpdate) { + onClickBtnUpdate(); + } + } + + private void onClickBtnUpdate() { + View[] views = {etUsername, etEmail}; + if (Helper.isEmptyFieldValidation(views) && Helper.isEmailValid(etEmail)) { + setInputDataToEntity(); + String uuid = SharedPref.getUserUid(context); + updateUserWithRetrofit(uuid, entity.getUsername(), entity.getEmail()); + } + } + + private void setInputDataToEntity() { + entity.setUsername(Helper.getStringFromInput(etUsername)); + entity.setEmail(Helper.getStringFromInput(etEmail)); + } + + private void updateUserWithRetrofit(String uuid, String username, String email) { + DialogUtils.showLoadingDialog(context, context.getString(R.string.Updating)); + + JsonObject jsonBody = new JsonObject(); + jsonBody.addProperty("username", username); + jsonBody.addProperty("email", email); + + String accessToken = SharedPref.getAccessToken(context); + String bearerToken = "Bearer " + accessToken; + + ApiService apiService = ApiClient.getClient().create(ApiService.class); + Call call = apiService.updateUser(bearerToken, uuid, jsonBody); + + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + + if (response.isSuccessful() && response.body() != null) { + JsonObject responseBody = response.body(); + if (responseBody.toString().contains("User modified")) { + Helper.makeSnackBar(alertView, context.getString(R.string.Profile_Updated_Successfully)); + SharedPref.setUsername(context, username); + SharedPref.setUserEmail(context, email); + } else { + Helper.makeSnackBar(alertView, context.getString(R.string.Try_again_later)); + } + } else { + try { + if (response.errorBody() != null) { + String error = response.errorBody().string(); + Log.e(TAG, "Update Failed: " + error); + Helper.makeSnackBar(alertView, context.getString(R.string.Update_Failed)); + } + } catch (IOException e) { + Log.e(TAG, "Error parsing error response", e); + } + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Update API failed", t); + Helper.makeSnackBar(alertView, context.getString(R.string.Something_went_wrong_Try_again)); + } + }); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/acloc/fragments/FavoriteFragment.java b/app/src/main/java/com/example/acloc/fragments/FavoriteFragment.java new file mode 100644 index 0000000..2873184 --- /dev/null +++ b/app/src/main/java/com/example/acloc/fragments/FavoriteFragment.java @@ -0,0 +1,196 @@ +package com.example.acloc.fragments; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.RecyclerView; + +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; + +import com.example.acloc.R; +import com.example.acloc.adapter.FavoriteAdapter; +import com.example.acloc.api.ApiClient; +import com.example.acloc.interfaces.ApiService; +import com.example.acloc.model.Favorite; +import com.example.acloc.utility.DialogUtils; +import com.example.acloc.utility.Helper; +import com.example.acloc.utility.SharedPref; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.ArrayList; +import java.util.List; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class FavoriteFragment extends Fragment { + public static final String TAG = FavoriteFragment.class.getSimpleName(); + private View view; + private FrameLayout rlFavorite; + private RecyclerView rvFavorite; + private TextView tvNoData; + private Context context; + private FavoriteAdapter adapter; + private Favorite favoriteEntity; + private List favoriteList; + + public FavoriteFragment() { + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + view = inflater.inflate(R.layout.fragment_favorite, container, false); + initUI(); + initObj(); + initListener(); + return view; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + this.context = context; + } + + @Override + public void onResume() { + super.onResume(); + if (adapter != null) { + setDataVisibility(false); + favoriteList.clear(); + } + if (context != null) loadData(); + } + + private void setDataVisibility(boolean isDataAvailable) { + if (isDataAvailable) { + rvFavorite.setVisibility(View.VISIBLE); + tvNoData.setVisibility(View.GONE); + } else { + rvFavorite.setVisibility(View.GONE); + tvNoData.setVisibility(View.VISIBLE); + } + } + + private void initUI() { + rlFavorite = view.findViewById(R.id.rlFavorite); + rvFavorite = view.findViewById(R.id.rvFavorite); + tvNoData = view.findViewById(R.id.tvNoData); + } + + private void initObj() { + context = getContext(); + favoriteEntity = new Favorite(); + } + + private void initListener() { + + } + + private void loadData() { + try { + if (favoriteList == null) { + favoriteList = new ArrayList<>(); + } + String userUuid = SharedPref.getUserUid(context); + getFavoriteByUserUuid(userUuid); + } catch (Exception e) { + Log.e(TAG, "Error in FavoriteFragment", e); + Helper.makeSnackBar(view, getString(R.string.Something_went_wrong_Try_again)); + } + } + + + @SuppressLint("NotifyDataSetChanged") + private void setUpRecyclerView() { + try { + if (adapter != null) { + adapter.updateFavoriteList(favoriteList); + } else { + adapter = new FavoriteAdapter(context, favoriteList); + rvFavorite.setAdapter(adapter); + rvFavorite.setLayoutManager(Helper.getVerticalManager(context)); + adapter.notifyDataSetChanged(); + } + setDataVisibility(true); + } catch (Exception e) { + Log.e(TAG, "Error in FavoriteFragment", e); + Helper.showToast(context, getString(R.string.Something_went_wrong_Try_again)); + setDataVisibility(false); + } + } + + private void getFavoriteByUserUuid(String userUuid) { + DialogUtils.showLoadingDialog(context, context.getString(R.string.Loading_Favorites)); + + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.getUserFavorites(token, userUuid); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful() && response.body() != null) { + JsonObject responseBody = response.body(); + JsonObject data = responseBody.getAsJsonObject("_data"); + + if (data != null && data.has("favorites")) { + favoriteList.clear(); // Clear previous list + + for (JsonElement element : data.getAsJsonArray("favorites")) { + JsonObject favoriteObject = element.getAsJsonObject(); + Favorite favorite = new Favorite(); + + favorite.setUuid(favoriteObject.get("uuid").getAsString()); + favorite.setActive(favoriteObject.get("active").getAsString()); + favorite.setPlaceUuid(favoriteObject.get("place_uuid").getAsString()); + favorite.setPlaceName(favoriteObject.get("place_name").getAsString()); + favorite.setPlaceAddress(favoriteObject.get("place_address").getAsString()); + favorite.setPlaceLat(favoriteObject.get("place_latitude").getAsString()); + favorite.setPlaceLng(favoriteObject.get("place_longitude").getAsString()); + favorite.setPlaceDescription(favoriteObject.get("place_description").getAsString()); + + favoriteList.add(favorite); + } + + // Set up recyclerview + if (!favoriteList.isEmpty()) { + setUpRecyclerView(); + } else { + setDataVisibility(false); + } + + } else { + setDataVisibility(false); + Helper.makeSnackBar(rlFavorite, context.getString(R.string.No_Favorite_found)); + } + } else { + setDataVisibility(false); + Helper.makeSnackBar(rlFavorite, context.getString(R.string.Failed_to_load_favorite_Try_again)); + Log.e(TAG, "Get favorite Error: " + response.code()); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Get favorite Failure: ", t); + Helper.makeSnackBar(rlFavorite, context.getString(R.string.Network_error_Try_again)); + } + }); + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/acloc/fragments/MapFragment.java b/app/src/main/java/com/example/acloc/fragments/MapFragment.java new file mode 100644 index 0000000..919e503 --- /dev/null +++ b/app/src/main/java/com/example/acloc/fragments/MapFragment.java @@ -0,0 +1,381 @@ +package com.example.acloc.fragments; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.location.Address; +import android.location.Geocoder; +import android.location.Location; +import android.net.Uri; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; + +import android.provider.Settings; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RelativeLayout; + +import com.example.acloc.R; +import com.example.acloc.activities.AddNewPlaceActivity; +import com.example.acloc.activities.PlaceDetailActivity; +import com.example.acloc.interfaces.ApiService; +import com.example.acloc.model.Place; +import com.example.acloc.utility.Constants; +import com.example.acloc.utility.Helper; +import com.example.acloc.utility.SharedPref; +import com.google.android.gms.location.FusedLocationProviderClient; +import com.google.android.gms.location.LocationServices; +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.MarkerOptions; +import com.google.android.material.textfield.TextInputEditText; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + + +public class MapFragment extends Fragment { + public static final String TAG = MapFragment.class.getSimpleName(); + private static final int LOCATION_PERMISSION_REQUEST_CODE = 1001; + + private RelativeLayout rlMap; + private View view; + private TextInputEditText etSearchLocation; + private GoogleMap googleMap; + private FusedLocationProviderClient fusedLocationClient; + private Geocoder geocoder; + private Dialog dialog; + private Context context; + + private List placeList = new ArrayList<>(); + + + public MapFragment() { + } + + @Override + public void onResume() { + super.onResume(); + fetchAndShowAllPlaces(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + view = inflater.inflate(R.layout.fragment_map, container, false); + initUI(); + initListener(); + initObj(); + return view; + } + + private void initUI() { + rlMap = view.findViewById(R.id.rlMap); + etSearchLocation = view.findViewById(R.id.etSearchLocation); + } + + private void initObj() { + context = getContext(); + } + + @SuppressLint("ClickableViewAccessibility") + private void initListener() { + SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager() + .findFragmentById(R.id.mapFrame); + + if (mapFragment != null) { + mapFragment.getMapAsync(map -> { + googleMap = map; + initMap(); + }); + } + + // Handle search icon click + etSearchLocation.setOnTouchListener((v, event) -> { + if (event.getAction() == MotionEvent.ACTION_UP) { + Drawable drawableEnd = etSearchLocation.getCompoundDrawables()[2]; // Right drawable + + if (drawableEnd != null) { + int drawableWidth = drawableEnd.getBounds().width(); + int touchAreaStart = etSearchLocation.getWidth() - drawableWidth - etSearchLocation.getPaddingEnd(); + + if (event.getX() >= touchAreaStart) { + v.performClick(); // important for accessibility + String address = Helper.getStringFromInput(etSearchLocation); + if (!address.isEmpty()) { + searchLocationByAddress(address); + } + return true; + } + } + } + return false; + }); + + // trigger on Enter/Done key + etSearchLocation.setOnEditorActionListener((v, actionId, event) -> { + String address = Helper.getStringFromInput(etSearchLocation); + if (!address.isEmpty()) { + searchLocationByAddress(address); + } + return true; + }); + } + + private void initMap() { + fetchAndShowAllPlaces(); + + fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireActivity()); + geocoder = new Geocoder(requireContext(), Locale.getDefault()); + + if (ContextCompat.checkSelfPermission(requireContext(), android.Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED) { + + googleMap.setMyLocationEnabled(true); + + fusedLocationClient.getLastLocation().addOnSuccessListener(location -> { + if (location != null) { + LatLng currentLatLng = new LatLng(location.getLatitude(), location.getLongitude()); + googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(currentLatLng, 14f)); + showNearbyPlaces(currentLatLng); + } + }); + + googleMap.setOnMapClickListener(latLng -> { + Place existingPlace = getPlaceIfExists(latLng); + if (existingPlace != null) { + Helper.goTo(getContext(), PlaceDetailActivity.class, Constants.PLACE, existingPlace); //Navigate to PlaceDetailActivity if the place already exists + return; +// +// dialog = new AlertViewAddNewPlaceDialog(requireContext(), +// Double.parseDouble(existingPlace.getLatitude()), +// Double.parseDouble(existingPlace.getLongitude()), +// existingPlace.getName(), +// existingPlace.getAddress(), +// existingPlace.getDescription(), +// existingPlace.getUuid()) +// .openPlaceDialog(); + } else { + try { + List
addressList = geocoder.getFromLocation(latLng.latitude, latLng.longitude, 1); + if (addressList != null && !addressList.isEmpty()) { + Address address = addressList.get(0); + String addressLine = address.getAddressLine(0); + String placeName = address.getFeatureName(); + +// dialog = new AlertViewAddNewPlaceDialog(requireContext(), +// latLng.latitude, +// latLng.longitude, +// placeName, +// addressLine, +// "", // Empty description +// null // UUID +// ).openPlaceDialog(); + + Place placeEntity = new Place(); + placeEntity.setLatitude(String.valueOf(latLng.latitude)); + placeEntity.setLongitude(String.valueOf(latLng.longitude)); + placeEntity.setName(placeName); + placeEntity.setAddress(addressLine); + placeEntity.setDescription(""); // Empty description + placeEntity.setUuid(null); // No UUID for new place + Helper.goTo(getContext(), AddNewPlaceActivity.class, Constants.PLACE, placeEntity); + } else { + Place placeEntity = new Place(); + placeEntity.setLatitude(String.valueOf(latLng.latitude)); + placeEntity.setLongitude(String.valueOf(latLng.longitude)); + placeEntity.setName(""); + placeEntity.setAddress("Address not found"); + placeEntity.setDescription(""); // Empty description + placeEntity.setUuid(null); // No UUID for a new place + Helper.goTo(getContext(), AddNewPlaceActivity.class, Constants.PLACE, placeEntity); + + } + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + + googleMap.setOnMarkerClickListener(marker -> { + marker.hideInfoWindow(); + + LatLng latLng = marker.getPosition(); + Place existingPlace = getPlaceIfExists(latLng); + + if (existingPlace != null) { + Helper.goTo(getContext(), PlaceDetailActivity.class, Constants.PLACE, existingPlace); + return true; + } + + return true; + }); + + + } else { + requestPermissions(new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE); + } + } + + private Place getPlaceIfExists(LatLng clickedLatLng) { + final float[] result = new float[1]; + for (Place place : placeList) { + double lat = Double.parseDouble(place.getLatitude()); + double lng = Double.parseDouble(place.getLongitude()); + + Location.distanceBetween( + clickedLatLng.latitude, clickedLatLng.longitude, + lat, lng, + result); + + if (result[0] < 10) { // less than 10 meters + return place; + } + } + return null; + } + + private void showNearbyPlaces(LatLng latLng) { + googleMap.clear(); +// googleMap.addMarker(new MarkerOptions() +// .position(latLng) +// .title("You are here")); + } + + private void searchLocationByAddress(String address) { + try { + List
addresses = geocoder.getFromLocationName(address, 1); + if (addresses != null && !addresses.isEmpty()) { + Address location = addresses.get(0); + LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); + googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 14f)); + showNearbyPlaces(latLng); + } else { + Helper.makeSnackBar(rlMap, "Location not found"); + } + } catch (IOException e) { + Log.d(TAG, getString(R.string.Something_went_wrong_Try_again)); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + if (requestCode == LOCATION_PERMISSION_REQUEST_CODE + && grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + initMap(); + } else { + boolean showRationale = shouldShowRequestPermissionRationale(android.Manifest.permission.ACCESS_FINE_LOCATION); + if (!showRationale) { + showPermissionRequiredDialog(); + } else { + Helper.showToast(context, getString(R.string.Location_permission_is_required)); + } } + } + private void showPermissionRequiredDialog() { + new AlertDialog.Builder(requireContext()) + .setTitle(getString(R.string.Location_Permission_Required)) + .setMessage(getString(R.string.Location_permission_rationale)) + .setCancelable(false) + .setPositiveButton(getString(R.string.Go_to_Settings), (dialog, which) -> { + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + Uri uri = Uri.fromParts("package", requireContext().getPackageName(), null); + intent.setData(uri); + startActivity(intent); + }) + .setNegativeButton(getString(R.string.Exit_App), (dialog, which) -> requireActivity().finish()) + .show(); + } + + private void fetchAndShowAllPlaces() { + String token = "Bearer " + SharedPref.getAccessToken(context); // get saved token + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("https://locationapi-m13l.onrender.com/") + .addConverterFactory(GsonConverterFactory.create()) + .build(); + + ApiService apiService = retrofit.create(ApiService.class); + Call call = apiService.getAllPlaces(token); + + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + try { + JSONObject jsonObject = new JSONObject(response.body().string()); + JSONArray placesArray = jsonObject.getJSONObject("_data").getJSONArray("places"); + + for (int i = 0; i < placesArray.length(); i++) { + JSONObject placeObj = placesArray.getJSONObject(i); + String name = placeObj.getString("name"); + String address = placeObj.getString("address"); + String description = placeObj.optString("description", ""); + String createdBy = placeObj.optString("createdBy", ""); + String uuid = placeObj.optString("uuid", ""); + double lat = placeObj.getDouble("latitude"); + double lng = placeObj.getDouble("longitude"); + + LatLng latLng = new LatLng(lat, lng); + googleMap.addMarker(new MarkerOptions() + .position(latLng) + .title(name) + .snippet(address)); + + // Add to memory list + Place place = new Place(); + place.setName(name); + place.setAddress(address); + place.setLatitude(String.valueOf(lat)); + place.setLongitude(String.valueOf(lng)); + place.setDescription(description); + place.setCreatedBy(createdBy); + place.setUuid(uuid); + + placeList.add(place); + } + + + } catch (Exception e) { + e.printStackTrace(); + } + } else { + Helper.makeSnackBar(rlMap, getString(R.string.Failed_to_load_places)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + t.printStackTrace(); + Helper.makeSnackBar(rlMap, getString(R.string.Network_error_Try_again)); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/acloc/fragments/MyReportsFragment.java b/app/src/main/java/com/example/acloc/fragments/MyReportsFragment.java new file mode 100644 index 0000000..9ec16d6 --- /dev/null +++ b/app/src/main/java/com/example/acloc/fragments/MyReportsFragment.java @@ -0,0 +1,209 @@ +package com.example.acloc.fragments; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.RecyclerView; + +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; + +import com.example.acloc.MainActivity; +import com.example.acloc.R; +import com.example.acloc.adapter.MyReportsAdapter; +import com.example.acloc.api.ApiClient; +import com.example.acloc.interfaces.ApiService; +import com.example.acloc.model.Report; +import com.example.acloc.utility.DialogUtils; +import com.example.acloc.utility.Helper; +import com.example.acloc.utility.SharedPref; +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.ArrayList; +import java.util.List; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class MyReportsFragment extends Fragment implements View.OnClickListener { + public static final String TAG = MyReportsFragment.class.getSimpleName(); + private View view; + private FrameLayout rlMyReport; + private RecyclerView rvReport; + private TextView tvNoData; + private ExtendedFloatingActionButton extendedFbReport; + private Context context; + private MyReportsAdapter adapter; + private Report reportEntity; + private List reportList; + + public MyReportsFragment() { + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + view = inflater.inflate(R.layout.fragment_my_reports, container, false); + initUI(); + initObj(); + initListener(); + return view; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + this.context = context; + } + + @Override + public void onResume() { + super.onResume(); + if (adapter != null) { + setDataVisibility(false); + reportList.clear(); + } + if (context != null) loadData(); + } + + private void setDataVisibility(boolean isDataAvailable) { + if (isDataAvailable) { + rvReport.setVisibility(View.VISIBLE); + tvNoData.setVisibility(View.GONE); + } else { + rvReport.setVisibility(View.GONE); + tvNoData.setVisibility(View.VISIBLE); + } + } + + private void initUI() { + rlMyReport = view.findViewById(R.id.rlMyReport); + rvReport = view.findViewById(R.id.rvReport); + tvNoData = view.findViewById(R.id.tvNoData); + extendedFbReport = view.findViewById(R.id.extendedFbReport); + } + + private void initObj() { + context = getContext(); + reportEntity = new Report(); + } + + private void initListener() { + extendedFbReport.setOnClickListener(this); + } + + private void loadData() { + try { + if (reportList == null) { + reportList = new ArrayList<>(); + } + String userUuid = SharedPref.getUserUid(context); + getReportsByUserUuid(userUuid); + } catch (Exception e) { + Log.e(TAG, "Error in MyReportsFragment", e); + Helper.makeSnackBar(view, getString(R.string.Something_went_wrong_Try_again)); + } + } + + @SuppressLint("NotifyDataSetChanged") + private void setUpRecyclerView() { + try { + if (adapter != null) { + adapter.updateReportList(reportList); + } else { + adapter = new MyReportsAdapter(context, reportList); + rvReport.setAdapter(adapter); + rvReport.setLayoutManager(Helper.getVerticalManager(context)); + adapter.notifyDataSetChanged(); + } + setDataVisibility(true); + } catch (Exception e) { + Log.e(TAG, "Error in MyReportFragment", e); + Helper.makeSnackBar(rlMyReport, getString(R.string.Something_went_wrong_Try_again)); + setDataVisibility(false); + } + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.extendedFbReport) { + onClickExtendedFbReport(); + } + } + + //Open Map Fragment to add new place report + private void onClickExtendedFbReport() { + ((MainActivity) requireActivity()) + .openFragmentFromChild(new MapFragment(), getString(R.string.Map), R.id.menu_map); + } + + private void getReportsByUserUuid(String userUuid) { + DialogUtils.showLoadingDialog(context, getString(R.string.Loading_reports)); + + String token = "Bearer " + SharedPref.getAccessToken(context); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + Call call = apiService.getUserReports(token, userUuid); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + DialogUtils.dismissDialog(); + if (response.isSuccessful() && response.body() != null) { + JsonObject responseBody = response.body(); + JsonObject data = responseBody.getAsJsonObject("_data"); + + if (data != null && data.has("reports")) { + reportList.clear(); // Clear previous list + + for (JsonElement element : data.getAsJsonArray("reports")) { + JsonObject reportObject = element.getAsJsonObject(); + Report report = new Report(); + + report.setUuid(reportObject.get("uuid").getAsString()); + report.setReportRating(reportObject.get("rating").getAsInt()); + report.setDescription(reportObject.get("description").getAsString()); + report.setPlaceName(reportObject.get("place_name").getAsString()); + report.setPlaceUuid(reportObject.get("place_uuid").getAsString()); + + reportList.add(report); + } + + // Set up recyclerview + if (!reportList.isEmpty()) { + setUpRecyclerView(); + } else { + setDataVisibility(false); + } + + } else { + setDataVisibility(false); + Helper.makeSnackBar(rlMyReport, getString(R.string.No_reports_found)); + } + } else { + setDataVisibility(false); + Helper.makeSnackBar(rlMyReport, getString(R.string.Failed_to_load_reports_Try_again_)); + Log.e(TAG, "Get Reports Error: " + response.code()); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + DialogUtils.dismissDialog(); + Log.e(TAG, "Get Reports Failure: ", t); + Helper.makeSnackBar(rlMyReport, context.getString(R.string.Network_error_Try_again)); + } + }); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/acloc/interfaces/ApiService.java b/app/src/main/java/com/example/acloc/interfaces/ApiService.java new file mode 100644 index 0000000..cdb7e87 --- /dev/null +++ b/app/src/main/java/com/example/acloc/interfaces/ApiService.java @@ -0,0 +1,139 @@ +package com.example.acloc.interfaces; + +import com.google.gson.JsonObject; + +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.Header; +import retrofit2.http.POST; +import retrofit2.http.PUT; +import retrofit2.http.Path; + +public interface ApiService { + @POST("signin") + Call registerUser(@Body JsonObject userData); + + @POST("login") + Call loginUser(@Body JsonObject body); + + @PUT("users/{uuid}") + Call updateUser( + @Header("Authorization") String bearerToken, + @Path("uuid") String uuid, + @Body JsonObject body + ); + + @POST("login") + Call verifyOldPassword(@Body JsonObject loginBody); + + @PUT("users/{uuid}") + Call changePassword( + @Header("Authorization") String token, + @Path("uuid") String uuid, + @Body JsonObject body + ); + + @POST("places") + Call insertPlace( + @Header("Authorization") String token, + @Body JsonObject body + ); + + @POST("report_types") + Call insertReportType( + @Header("Authorization") String token, + @Body JsonObject body + ); + + @PUT("places/{uuid}") + Call updatePlace(@Header("Authorization") String token, + @Path("uuid") String uuid, + @Body JsonObject placeData); + + + @GET("places") + Call getAllPlaces(@Header("Authorization") String token); + + @POST("users/{user_uuid}/places") + Call addPlaceToFavorites( + @Header("Authorization") String token, + @Path("user_uuid") String userUuid, + @Body JsonObject placeBody + ); + + @DELETE("users/{user_uuid}/places/{place_uuid}") + Call removePlaceFromFavorites( + @Header("Authorization") String token, + @Path("user_uuid") String userUuid, + @Path("place_uuid") String placeUuid + ); + + @GET("users/{user_uuid}/places") + Call getFavoritePlaces( + @Header("Authorization") String token, + @Path("user_uuid") String userUuid + ); + + @PUT("users/{user_uuid}/places") + Call restorePlaceToFavorites( + @Header("Authorization") String authToken, + @Path("user_uuid") String userUuid, + @Body JsonObject body + ); + + @POST("reports") + Call insertReport( + @Header("Authorization") String token, + @Body JsonObject reportBody + ); + + @GET("users/{user_uuid}/reports") + Call getUserReports( + @Header("Authorization") String token, + @Path("user_uuid") String userUuid + ); + + @DELETE("reports/{report_uuid}") + Call removeReport( + @Header("Authorization") String token, + @Path("report_uuid") String reportUuid + ); + + @PUT("/reports/{uuid}") + Call updateReport(@Header("Authorization") String token, + @Path("uuid") String uuid, + @Body JsonObject placeData); + + @GET("places/{place_uuid}/reports") + Call getPlaceReports( + @Header("Authorization") String token, + @Path("place_uuid") String placeUuid + ); + + @GET("users/{user_uuid}/places") + Call getUserFavorites( + @Header("Authorization") String token, + @Path("user_uuid") String userUuid + ); + + @GET("roles") + Call getRoles(@Header("Authorization") String token); + + @GET("users") + Call getAllUsers(@Header("Authorization") String token); + + + @PUT("users/{uuid}") + Call updateRole(@Header("Authorization") String token, + @Path("uuid") String uuid, + @Body JsonObject userData); + + @GET("places/{uuid}") + Call getPlaceFromUuid( + @Header("Authorization") String token, + @Path("uuid") String uuid + ); +} diff --git a/app/src/main/java/com/example/acloc/model/Favorite.java b/app/src/main/java/com/example/acloc/model/Favorite.java new file mode 100644 index 0000000..7cfbe82 --- /dev/null +++ b/app/src/main/java/com/example/acloc/model/Favorite.java @@ -0,0 +1,79 @@ +package com.example.acloc.model; + +import java.io.Serializable; + +public class Favorite implements Serializable { + String uuid, active, placeUuid, placeName, placeAddress, placeLat, placeLng, placeDescription, userUsername; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getActive() { + return active; + } + + public void setActive(String active) { + this.active = active; + } + + public String getPlaceUuid() { + return placeUuid; + } + + public void setPlaceUuid(String placeUuid) { + this.placeUuid = placeUuid; + } + + public String getPlaceName() { + return placeName; + } + + public void setPlaceName(String placeName) { + this.placeName = placeName; + } + + public String getPlaceAddress() { + return placeAddress; + } + + public void setPlaceAddress(String placeAddress) { + this.placeAddress = placeAddress; + } + + public String getPlaceLat() { + return placeLat; + } + + public void setPlaceLat(String placeLat) { + this.placeLat = placeLat; + } + + public String getPlaceLng() { + return placeLng; + } + + public void setPlaceLng(String placeLng) { + this.placeLng = placeLng; + } + + public String getPlaceDescription() { + return placeDescription; + } + + public void setPlaceDescription(String placeDescription) { + this.placeDescription = placeDescription; + } + + public String getUserUsername() { + return userUsername; + } + + public void setUserUsername(String userUsername) { + this.userUsername = userUsername; + } +} diff --git a/app/src/main/java/com/example/acloc/model/Place.java b/app/src/main/java/com/example/acloc/model/Place.java new file mode 100644 index 0000000..849dcc4 --- /dev/null +++ b/app/src/main/java/com/example/acloc/model/Place.java @@ -0,0 +1,63 @@ +package com.example.acloc.model; + +import java.io.Serializable; + +public class Place implements Serializable { + String uuid, name, description, address, latitude, longitude, createdBy; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getLatitude() { + return latitude; + } + + public void setLatitude(String latitude) { + this.latitude = latitude; + } + + public String getLongitude() { + return longitude; + } + + public void setLongitude(String longitude) { + this.longitude = longitude; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } +} diff --git a/app/src/main/java/com/example/acloc/model/Report.java b/app/src/main/java/com/example/acloc/model/Report.java new file mode 100644 index 0000000..8cb6ac8 --- /dev/null +++ b/app/src/main/java/com/example/acloc/model/Report.java @@ -0,0 +1,81 @@ +package com.example.acloc.model; + +import java.io.Serializable; + +public class Report implements Serializable { + String uuid, fkUser, fkPlace, fkReportType, description, createdBy; + String placeName, placeUuid; + int reportRating; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getFkUser() { + return fkUser; + } + + public void setFkUser(String fkUser) { + this.fkUser = fkUser; + } + + public String getFkPlace() { + return fkPlace; + } + + public void setFkPlace(String fkPlace) { + this.fkPlace = fkPlace; + } + + public String getFkReportType() { + return fkReportType; + } + + public void setFkReportType(String fkReportType) { + this.fkReportType = fkReportType; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public int getReportRating() { + return reportRating; + } + + public void setReportRating(int reportRating) { + this.reportRating = reportRating; + } + + public String getPlaceName() { + return placeName; + } + + public void setPlaceName(String placeName) { + this.placeName = placeName; + } + + public String getPlaceUuid() { + return placeUuid; + } + + public void setPlaceUuid(String placeUuid) { + this.placeUuid = placeUuid; + } +} diff --git a/app/src/main/java/com/example/acloc/model/User.java b/app/src/main/java/com/example/acloc/model/User.java new file mode 100644 index 0000000..9fd67a0 --- /dev/null +++ b/app/src/main/java/com/example/acloc/model/User.java @@ -0,0 +1,56 @@ +package com.example.acloc.model; + +import java.io.Serializable; + +public class User implements Serializable { + private String username, email, password; + private String role, uuid, fkRole ; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getFkRole() { + return fkRole; + } + + public void setFkRole(String fkRole) { + this.fkRole = fkRole; + } +} diff --git a/app/src/main/java/com/example/acloc/utility/Constants.java b/app/src/main/java/com/example/acloc/utility/Constants.java new file mode 100644 index 0000000..740d5d7 --- /dev/null +++ b/app/src/main/java/com/example/acloc/utility/Constants.java @@ -0,0 +1,20 @@ +package com.example.acloc.utility; + +import java.util.Arrays; +import java.util.List; + +public class Constants { + public static final String SHARED_PREF = "ACLOC"; + public static final String ADMIN = "admin"; + public static final String VIEWER = "viewer"; + public static final String PLACE = "Place"; + public static final String REPORT = "Report"; + public static final String SOMETHING_WENT_WRONG = "Something went wrong"; + + public static final int GOOD_RATING = 3; + public static final int AVERAGE_RATING = 2; + public static final int BAD_RATING = 1; + + public static final List ROLES_OPTIONS = Arrays.asList("admin", "viewer"); + public static final String BASE_URL = "https://locationapi-m13l.onrender.com/"; +} diff --git a/app/src/main/java/com/example/acloc/utility/DialogUtils.java b/app/src/main/java/com/example/acloc/utility/DialogUtils.java new file mode 100644 index 0000000..f105ccf --- /dev/null +++ b/app/src/main/java/com/example/acloc/utility/DialogUtils.java @@ -0,0 +1,85 @@ +package com.example.acloc.utility; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.view.Window; +import android.widget.TextView; + +import com.example.acloc.R; +import com.example.acloc.activities.LoginActivity; + +public class DialogUtils { + private static Dialog dialog; + + public static void showLoadingDialog(Context context, String message) { + if (dialog != null && dialog.isShowing()) { + return; + } + + dialog = new Dialog(context); + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + dialog.setContentView(R.layout.loading); + dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + dialog.setCancelable(false); + + TextView tvLoadingText = dialog.findViewById(R.id.tvLoadingText); + tvLoadingText.setText(message); + + dialog.show(); + } + + public static void dismissDialog() { + if (dialog != null && dialog.isShowing()) { + dialog.dismiss(); + dialog = null; + } + } + + public static AlertDialog logoutDialog(final Context context) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + + builder.setMessage(context.getString(R.string.Are_you_sure_you_want_to_logout)); + + builder.setPositiveButton("Yes", (dialog, id) -> { + dialog.cancel(); + int flags = Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_CLEAR_TASK + | Intent.FLAG_ACTIVITY_NEW_TASK; + SharedPref.setIsLoggedIn(context,false); + SharedPref.deleteAll(context); + Helper.goToWithFlags(context, LoginActivity.class, flags); + }); + + builder.setNegativeButton("No", (dialog, id) -> dialog.cancel()); + return builder.create(); + } + +// public static AlertDialog confirmationDialog(final Context context, String confirmationText +// , DialogInterface.OnClickListener onDeleteClickListener) { +// AlertDialog.Builder builder = new AlertDialog.Builder(context); +// if (confirmationText == null) confirmationText = "perform this operation"; +// builder.setMessage("Are you sure you want to " + confirmationText + "?" + +// "\nWARNING: This action cannot be undone"); +// builder.setPositiveButton("Yes", onDeleteClickListener); +// builder.setNegativeButton("No", (dialog, id) -> dialog.cancel()); +// return builder.create(); +// } + + public static AlertDialog confirmationDialog(final Context context, String confirmationText + , DialogInterface.OnClickListener onDeleteClickListener) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + if (confirmationText == null) confirmationText = "perform this operation"; + String message = context.getString(R.string.confirmation_message, confirmationText); + builder.setMessage(message); + builder.setPositiveButton(context.getString(R.string.yes), onDeleteClickListener); + builder.setNegativeButton(context.getString(R.string.no), (dialog, id) -> dialog.cancel()); + return builder.create(); + } + + +} diff --git a/app/src/main/java/com/example/acloc/utility/Helper.java b/app/src/main/java/com/example/acloc/utility/Helper.java new file mode 100644 index 0000000..8d81066 --- /dev/null +++ b/app/src/main/java/com/example/acloc/utility/Helper.java @@ -0,0 +1,353 @@ +package com.example.acloc.utility; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.util.Log; +import android.view.View; +import android.view.ViewParent; +import android.widget.Toast; + +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.textfield.MaterialAutoCompleteTextView; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import java.io.Serializable; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Objects; + +public class Helper { + + private static final String TAG = Helper.class.getSimpleName(); + + public static void showToast(Context context, String message) { + try { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + } catch (Exception ignored) { + } + } + + public static void makeSnackBar(View view, String message) { + try { + Snackbar.make(view, message, Snackbar.LENGTH_LONG).show(); + } catch (Exception ignored) { + showToast(view.getContext(), message); + } + } + + public static void goTo(Context context, Class activity) { + Intent intent = new Intent(context, activity); + context.startActivity(intent); + } + + public static void goToAndFinish(Context context, Class activity) { + Intent intent = new Intent(context, activity); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + context.startActivity(intent); + + // No need to finish explicitly here, flags handle it + if (context instanceof Activity) { + ((Activity) context).finish(); + } + } + + public static void goTo(Context context, Class activity, String key, Serializable object) { + Intent intent = new Intent(context, activity); + intent.putExtra(key, object); + context.startActivity(intent); + +// if (context instanceof Activity) { +// ((Activity) context).finish(); +// } + } + + public static int getIntValueFromString(String value) { + try { + return Integer.parseInt(value); + } catch (Exception e) { + return 0; + } + } + + public static double getDoubleValueFromString(String value) { + try { + return Double.parseDouble(value); + } catch (Exception e) { + return 0; + } + } + + public static String getStringFromInput(View view) { + try { + if (view instanceof TextInputEditText) { + TextInputEditText editText = (TextInputEditText) view; + return Objects.requireNonNull(editText.getText()).toString().trim(); + } else if (view instanceof MaterialAutoCompleteTextView) { + MaterialAutoCompleteTextView editText = (MaterialAutoCompleteTextView) view; + return Objects.requireNonNull(editText.getText()).toString(); + } + } catch (Exception e) { + return null; + } + return null; + } + + public static boolean isEmptyFieldValidation(TextInputEditText editText) { + boolean isValidate = true; + try { + TextInputLayout textInputLayout = null; + ViewParent parent = editText.getParent().getParent(); + if (parent instanceof TextInputLayout) { + textInputLayout = (TextInputLayout) parent; + } + if (Objects.requireNonNull(editText.getText()).toString().trim().isEmpty()) { + if (textInputLayout != null) { + textInputLayout.isHelperTextEnabled(); + textInputLayout.setError("Please " + textInputLayout.getHint()); + textInputLayout.setErrorEnabled(true); + } else { + editText.setError("Empty"); + } + isValidate = false; + } else { + if (textInputLayout != null) { + textInputLayout.setErrorEnabled(false); + } else { + editText.setError(null); + } + } + } catch (Exception e) { + Log.e(TAG, "Error in Helper Class: ", e); + isValidate = false; + } + return isValidate; + } + + public static boolean isEmptyFieldValidation(View[] inputFields) { + boolean isValidate = true; + try { + for (View view : inputFields) { + TextInputLayout textInputLayout = null; + ViewParent parent = view.getParent().getParent(); + if (parent instanceof TextInputLayout) { + textInputLayout = (TextInputLayout) parent; + } + + String inputText = ""; + if (view instanceof TextInputEditText) { + inputText = Objects.requireNonNull(((TextInputEditText) view).getText()).toString().trim(); + } else if (view instanceof MaterialAutoCompleteTextView) { + inputText = Objects.requireNonNull(((MaterialAutoCompleteTextView) view).getText()).toString().trim(); + } + + if (inputText.isEmpty()) { + if (textInputLayout != null) { + textInputLayout.setError("Please " + textInputLayout.getHint()); + textInputLayout.setErrorEnabled(true); + } else { + if (view instanceof TextInputEditText) { + ((TextInputEditText) view).setError("Empty"); + } else if (view instanceof MaterialAutoCompleteTextView) { + ((MaterialAutoCompleteTextView) view).setError("Empty"); + } + } + isValidate = false; + } else { + if (textInputLayout != null) { + textInputLayout.setErrorEnabled(false); + } else { + if (view instanceof TextInputEditText) { + ((TextInputEditText) view).setError(null); + } else if (view instanceof MaterialAutoCompleteTextView) { + ((MaterialAutoCompleteTextView) view).setError(null); + } + } + } + } + } catch (Exception e) { + Log.e(TAG, "Error in validation: ", e); + isValidate = false; + } + return isValidate; + } + + public static boolean isEmailValid(View emailView) { + boolean isValidate = true; + try { + TextInputLayout textInputLayout = null; + ViewParent parent = emailView.getParent().getParent(); + if (parent instanceof TextInputLayout) { + textInputLayout = (TextInputLayout) parent; + } + + String emailText = ""; + if (emailView instanceof TextInputEditText) { + emailText = Objects.requireNonNull(((TextInputEditText) emailView).getText()).toString().trim(); + } else if (emailView instanceof MaterialAutoCompleteTextView) { + emailText = Objects.requireNonNull(((MaterialAutoCompleteTextView) emailView).getText()).toString().trim(); + } + + // Regex for email validation + if (!emailText.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$")) { + if (textInputLayout != null) { + textInputLayout.setError("Invalid Email Address"); + textInputLayout.setErrorEnabled(true); + } else { + if (emailView instanceof TextInputEditText) { + ((TextInputEditText) emailView).setError("Invalid Email Address"); + } else if (emailView instanceof MaterialAutoCompleteTextView) { + ((MaterialAutoCompleteTextView) emailView).setError("Invalid Email Address"); + } + } + isValidate = false; + } else { + if (textInputLayout != null) { + textInputLayout.setErrorEnabled(false); + } else { + if (emailView instanceof TextInputEditText) { + ((TextInputEditText) emailView).setError(null); + } else if (emailView instanceof MaterialAutoCompleteTextView) { + ((MaterialAutoCompleteTextView) emailView).setError(null); + } + } + } + } catch (Exception e) { + Log.e(TAG, "Error in email validation: ", e); + isValidate = false; + } + return isValidate; + } + + public static boolean isPasswordValid(View passwordView) { + boolean isValidate = true; + try { + TextInputLayout textInputLayout = null; + ViewParent parent = passwordView.getParent().getParent(); + if (parent instanceof TextInputLayout) { + textInputLayout = (TextInputLayout) parent; + } + + String passwordText = ""; + if (passwordView instanceof TextInputEditText) { + passwordText = Objects.requireNonNull(((TextInputEditText) passwordView).getText()).toString().trim(); + } else if (passwordView instanceof MaterialAutoCompleteTextView) { + passwordText = Objects.requireNonNull(((MaterialAutoCompleteTextView) passwordView).getText()).toString().trim(); + } + + // Regex for strong password validation + String passwordPattern = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{6,}$"; + + if (!passwordText.matches(passwordPattern)) { + if (textInputLayout != null) { + textInputLayout.setError("Password must be at least 6 characters, include 1 uppercase, 1 lowercase, 1 digit, and 1 special character."); + textInputLayout.setErrorEnabled(true); + } else { + if (passwordView instanceof TextInputEditText) { + ((TextInputEditText) passwordView).setError("Password must be at least 6 characters, include 1 uppercase, 1 lowercase, 1 digit, and 1 special character."); + } else if (passwordView instanceof MaterialAutoCompleteTextView) { + ((MaterialAutoCompleteTextView) passwordView).setError("Password must be at least 6 characters, include 1 uppercase, 1 lowercase, 1 digit, and 1 special character."); + } + } + isValidate = false; + } else { + if (textInputLayout != null) { + textInputLayout.setErrorEnabled(false); + } else { + if (passwordView instanceof TextInputEditText) { + ((TextInputEditText) passwordView).setError(null); + } else if (passwordView instanceof MaterialAutoCompleteTextView) { + ((MaterialAutoCompleteTextView) passwordView).setError(null); + } + } + } + } catch (Exception e) { + Log.e("Helper", "Error in password validation: ", e); + isValidate = false; + } + return isValidate; + } + + public static boolean isContactValid(View contactView) { + boolean isValidate = true; + try { + TextInputLayout textInputLayout = null; + ViewParent parent = contactView.getParent().getParent(); + if (parent instanceof TextInputLayout) { + textInputLayout = (TextInputLayout) parent; + } + + String contactText = ""; + if (contactView instanceof TextInputEditText) { + contactText = Objects.requireNonNull(((TextInputEditText) contactView).getText()).toString().trim(); + } else if (contactView instanceof MaterialAutoCompleteTextView) { + contactText = Objects.requireNonNull(((MaterialAutoCompleteTextView) contactView).getText()).toString().trim(); + } + + // Regex for validating contact (Assuming 10-digit phone number) + if (!contactText.matches("^[0-9]{10}$")) { + if (textInputLayout != null) { + textInputLayout.setError("Invalid Contact Number"); + textInputLayout.setErrorEnabled(true); + } else { + if (contactView instanceof TextInputEditText) { + ((TextInputEditText) contactView).setError("Invalid Contact Number"); + } else if (contactView instanceof MaterialAutoCompleteTextView) { + ((MaterialAutoCompleteTextView) contactView).setError("Invalid Contact Number"); + } + } + isValidate = false; + } else { + if (textInputLayout != null) { + textInputLayout.setErrorEnabled(false); + } else { + if (contactView instanceof TextInputEditText) { + ((TextInputEditText) contactView).setError(null); + } else if (contactView instanceof MaterialAutoCompleteTextView) { + ((MaterialAutoCompleteTextView) contactView).setError(null); + } + } + } + } catch (Exception e) { + Log.e("ValidationHelper", "Error in contact validation: ", e); + isValidate = false; + } + return isValidate; + } + + public static String getCurrentDate() { + try { + SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); + Date date = new Date(); + return dateFormat.format(date); + } catch (Exception e) { + Log.e(TAG, "Error in getting current date: ", e); + return null; + } + } + + public static String getCurrentTime() { + try { + SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss"); + Date date = new Date(); + return timeFormat.format(date); + } catch (Exception e) { + Log.e(TAG, "Error in getting current time: ", e); + return null; + } + } + + public static void goToWithFlags(Context context, Class activity, int flags) { + Intent intent = new Intent(context, activity); + intent.setFlags(flags); + context.startActivity(intent); + } + + public static LinearLayoutManager getVerticalManager(Context context) { + return new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false); + } +} + diff --git a/app/src/main/java/com/example/acloc/utility/KeyGeneratorUtils.java b/app/src/main/java/com/example/acloc/utility/KeyGeneratorUtils.java new file mode 100644 index 0000000..28b1b6d --- /dev/null +++ b/app/src/main/java/com/example/acloc/utility/KeyGeneratorUtils.java @@ -0,0 +1,32 @@ +package com.example.acloc.utility; + +import java.security.SecureRandom; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class KeyGeneratorUtils { + + private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + private static final int RANDOM_STRING_LENGTH = 8; + private static final SecureRandom random = new SecureRandom(); + + /** + * Generates a unique key using timestamp + random string + * @return Unique string key + */ + public static String uniqueKeyGenerator() { + // Get current timestamp in yyyyMMddHHmmssSSS format + String timeStamp = new SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.getDefault()).format(new Date()); + + // Generate random alphanumeric string + StringBuilder randomStr = new StringBuilder(RANDOM_STRING_LENGTH); + for (int i = 0; i < RANDOM_STRING_LENGTH; i++) { + int index = random.nextInt(CHARACTERS.length()); + randomStr.append(CHARACTERS.charAt(index)); + } + + // Combine timestamp and random string + return timeStamp + "_" + randomStr.toString(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/acloc/utility/SharedPref.java b/app/src/main/java/com/example/acloc/utility/SharedPref.java new file mode 100644 index 0000000..7cf1ecc --- /dev/null +++ b/app/src/main/java/com/example/acloc/utility/SharedPref.java @@ -0,0 +1,173 @@ +package com.example.acloc.utility; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Base64; +import android.util.Log; + +import org.json.JSONObject; + +import java.util.Locale; + +public class SharedPref { + public static final String TAG = "SharedPref"; + + private static final String IS_LOGGED_IN = "isLoggedIn"; + private static final String USER_UUID = "userUuid"; // Key for UID storage + private static final String ADMIN_ROLE_UUID = "adminRoleUuid"; + private static final String VIEWER_ROLE_UUID = "viewerRoleUuid"; + private static final String USER_NAME = "userName"; + private static final String USER_EMAIL = "userEmail"; + private static final String ROLE = "role"; + private static final String ACCESS_TOKEN = "AccessToken"; + private static final String ACCESS_TOKEN_EXPIRY = "AccessTokenExpiry"; + private static final String REFRESH_TOKEN = "RefreshToken"; + private static final String LANGUAGE_KEY = "language_key"; // Key to store selected language + + public static SharedPreferences sharedPreferences(Context con) { + return con.getSharedPreferences(Constants.SHARED_PREF, Context.MODE_PRIVATE); + } + + public static void setIsLoggedIn(Context con, boolean value) { + SharedPreferences.Editor editor = sharedPreferences(con).edit(); + editor.putBoolean(IS_LOGGED_IN, value); + editor.apply(); + } + + public static boolean getIsLoggedIn(Context con) { + return sharedPreferences(con).getBoolean(IS_LOGGED_IN, false); + } + + public static void setUuid(Context con, String uid) { + SharedPreferences.Editor editor = sharedPreferences(con).edit(); + editor.putString(USER_UUID, uid); + editor.apply(); + } + + public static String getUserUid(Context con) { + return sharedPreferences(con).getString(USER_UUID, ""); + } + + public static void setUsername(Context con, String username) { + SharedPreferences.Editor editor = sharedPreferences(con).edit(); + editor.putString(USER_NAME, username); + editor.apply(); + } + + public static String getUserName(Context con) { + return sharedPreferences(con).getString(USER_NAME, ""); + } + + public static void setUserEmail(Context con, String userEmail) { + SharedPreferences.Editor editor = sharedPreferences(con).edit(); + editor.putString(USER_EMAIL, userEmail); + editor.apply(); + } + + public static String getUserEmail(Context con) { + return sharedPreferences(con).getString(USER_EMAIL, ""); + } + + public static void setRole(Context con, String userEmail) { + SharedPreferences.Editor editor = sharedPreferences(con).edit(); + editor.putString(ROLE, userEmail); + editor.apply(); + } + + public static String getRole(Context con) { + return sharedPreferences(con).getString(ROLE, ""); + } + + public static void setAccessToken(Context con, String token) { + SharedPreferences.Editor editor = sharedPreferences(con).edit(); + editor.putString(ACCESS_TOKEN, token); + + // Decode token to get expiry (exp) field + try { + if (token != null && !token.trim().isEmpty()) { + String[] split = token.split("\\."); + if (split.length >= 2) { + String payload = new String(Base64.decode(split[1], Base64.DEFAULT)); + JSONObject jsonObject = new JSONObject(payload); + + if (jsonObject.has("exp")) { + long exp = jsonObject.getLong("exp"); // in seconds + long expiryMillis = exp * 1000; // convert to milliseconds + editor.putLong(ACCESS_TOKEN_EXPIRY, expiryMillis); + } else { + editor.putLong(ACCESS_TOKEN_EXPIRY, 0); + } + } else { + editor.putLong(ACCESS_TOKEN_EXPIRY, 0); + } + } else { + editor.putLong(ACCESS_TOKEN_EXPIRY, 0); + } + } catch (Exception e) { + Log.e(TAG, "Failed to decode token", e); + editor.putLong(ACCESS_TOKEN_EXPIRY, 0); // fallback + } + + editor.apply(); + } + + + public static String getAccessToken(Context con) { + return sharedPreferences(con).getString(ACCESS_TOKEN, ""); + } + + public static boolean isAccessTokenValid(Context con) { + long expiryTime = sharedPreferences(con).getLong(ACCESS_TOKEN_EXPIRY, 0); + long currentTime = System.currentTimeMillis(); + return currentTime < expiryTime; + } + + public static void setRefreshToken(Context con, String refreshToken) { + SharedPreferences.Editor editor = sharedPreferences(con).edit(); + editor.putString(REFRESH_TOKEN, refreshToken); + editor.apply(); + } + + public static String getRefreshToken(Context con) { + return sharedPreferences(con).getString(REFRESH_TOKEN, ""); + } + + public static void deleteAll(Context context) { + SharedPreferences.Editor editor = sharedPreferences(context).edit(); + editor.clear(); + editor.apply(); + } + + // Method to save selected language + public static void setLanguage(Context context, String language) { + SharedPreferences.Editor editor = sharedPreferences(context).edit(); + editor.putString(LANGUAGE_KEY, language); + editor.apply(); + } + + // Method to retrieve saved language, default to system language if not set + public static String getLanguage(Context context) { + return sharedPreferences(context).getString(LANGUAGE_KEY, Locale.getDefault().getLanguage()); + } + + + public static void setViewerRoleUuid(Context con, String uid) { + SharedPreferences.Editor editor = sharedPreferences(con).edit(); + editor.putString(VIEWER_ROLE_UUID, uid); + editor.apply(); + } + + public static String getViewerRoleUuid(Context con) { + return sharedPreferences(con).getString(VIEWER_ROLE_UUID, ""); + } + + public static void setAdminRoleUuid(Context con, String uid) { + SharedPreferences.Editor editor = sharedPreferences(con).edit(); + editor.putString(ADMIN_ROLE_UUID, uid); + editor.apply(); + } + + public static String getAdminRoleUuid(Context con) { + return sharedPreferences(con).getString(ADMIN_ROLE_UUID, ""); + } +} diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 0000000..9f83b8f --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_add_light.xml b/app/src/main/res/drawable/ic_add_light.xml new file mode 100644 index 0000000..2ae27b8 --- /dev/null +++ b/app/src/main/res/drawable/ic_add_light.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_add_location.xml b/app/src/main/res/drawable/ic_add_location.xml new file mode 100644 index 0000000..9e099e9 --- /dev/null +++ b/app/src/main/res/drawable/ic_add_location.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml new file mode 100644 index 0000000..1b10afe --- /dev/null +++ b/app/src/main/res/drawable/ic_delete.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_dropdown.xml b/app/src/main/res/drawable/ic_dropdown.xml new file mode 100644 index 0000000..a77e331 --- /dev/null +++ b/app/src/main/res/drawable/ic_dropdown.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml new file mode 100644 index 0000000..9fc6aac --- /dev/null +++ b/app/src/main/res/drawable/ic_edit.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_edit_location.xml b/app/src/main/res/drawable/ic_edit_location.xml new file mode 100644 index 0000000..623bc14 --- /dev/null +++ b/app/src/main/res/drawable/ic_edit_location.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_email.xml b/app/src/main/res/drawable/ic_email.xml new file mode 100644 index 0000000..a3335d4 --- /dev/null +++ b/app/src/main/res/drawable/ic_email.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_favorite.xml b/app/src/main/res/drawable/ic_favorite.xml new file mode 100644 index 0000000..ce3be0b --- /dev/null +++ b/app/src/main/res/drawable/ic_favorite.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_favorite_border.xml b/app/src/main/res/drawable/ic_favorite_border.xml new file mode 100644 index 0000000..b2d6f0c --- /dev/null +++ b/app/src/main/res/drawable/ic_favorite_border.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_home.xml b/app/src/main/res/drawable/ic_home.xml new file mode 100644 index 0000000..20cb4d6 --- /dev/null +++ b/app/src/main/res/drawable/ic_home.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_language.xml b/app/src/main/res/drawable/ic_language.xml new file mode 100644 index 0000000..643d3fc --- /dev/null +++ b/app/src/main/res/drawable/ic_language.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..ca3826a --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_location.xml b/app/src/main/res/drawable/ic_location.xml new file mode 100644 index 0000000..7eb4ae9 --- /dev/null +++ b/app/src/main/res/drawable/ic_location.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000..4edadb0 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_logout.xml b/app/src/main/res/drawable/ic_logout.xml new file mode 100644 index 0000000..c22a96f --- /dev/null +++ b/app/src/main/res/drawable/ic_logout.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_manage_roles.xml b/app/src/main/res/drawable/ic_manage_roles.xml new file mode 100644 index 0000000..3a2e3ab --- /dev/null +++ b/app/src/main/res/drawable/ic_manage_roles.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_map.xml b/app/src/main/res/drawable/ic_map.xml new file mode 100644 index 0000000..58f2947 --- /dev/null +++ b/app/src/main/res/drawable/ic_map.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_person.xml b/app/src/main/res/drawable/ic_person.xml new file mode 100644 index 0000000..ddc8322 --- /dev/null +++ b/app/src/main/res/drawable/ic_person.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_phone.xml b/app/src/main/res/drawable/ic_phone.xml new file mode 100644 index 0000000..2862a96 --- /dev/null +++ b/app/src/main/res/drawable/ic_phone.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_report.xml b/app/src/main/res/drawable/ic_report.xml new file mode 100644 index 0000000..b6a6b48 --- /dev/null +++ b/app/src/main/res/drawable/ic_report.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 0000000..d29c6ea --- /dev/null +++ b/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_thumb_down.xml b/app/src/main/res/drawable/ic_thumb_down.xml new file mode 100644 index 0000000..0621911 --- /dev/null +++ b/app/src/main/res/drawable/ic_thumb_down.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_thumb_down_border.xml b/app/src/main/res/drawable/ic_thumb_down_border.xml new file mode 100644 index 0000000..6a26286 --- /dev/null +++ b/app/src/main/res/drawable/ic_thumb_down_border.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_thumb_up.xml b/app/src/main/res/drawable/ic_thumb_up.xml new file mode 100644 index 0000000..faa4659 --- /dev/null +++ b/app/src/main/res/drawable/ic_thumb_up.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_thumb_up_average.xml b/app/src/main/res/drawable/ic_thumb_up_average.xml new file mode 100644 index 0000000..ccb79a7 --- /dev/null +++ b/app/src/main/res/drawable/ic_thumb_up_average.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_thumb_up_border.xml b/app/src/main/res/drawable/ic_thumb_up_border.xml new file mode 100644 index 0000000..e57a113 --- /dev/null +++ b/app/src/main/res/drawable/ic_thumb_up_border.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_thumbs_down.xml b/app/src/main/res/drawable/ic_thumbs_down.xml new file mode 100644 index 0000000..cea7fa0 --- /dev/null +++ b/app/src/main/res/drawable/ic_thumbs_down.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_thumbs_up.xml b/app/src/main/res/drawable/ic_thumbs_up.xml new file mode 100644 index 0000000..cd531c6 --- /dev/null +++ b/app/src/main/res/drawable/ic_thumbs_up.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/logo_add_location.png b/app/src/main/res/drawable/logo_add_location.png new file mode 100644 index 0000000..9925dd1 Binary files /dev/null and b/app/src/main/res/drawable/logo_add_location.png differ diff --git a/app/src/main/res/drawable/logo_location.png b/app/src/main/res/drawable/logo_location.png new file mode 100644 index 0000000..f06e037 Binary files /dev/null and b/app/src/main/res/drawable/logo_location.png differ diff --git a/app/src/main/res/drawable/place_header.jpg b/app/src/main/res/drawable/place_header.jpg new file mode 100644 index 0000000..2effa42 Binary files /dev/null and b/app/src/main/res/drawable/place_header.jpg differ diff --git a/app/src/main/res/drawable/rating.xml b/app/src/main/res/drawable/rating.xml new file mode 100644 index 0000000..1482ff0 --- /dev/null +++ b/app/src/main/res/drawable/rating.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rectangle.xml b/app/src/main/res/drawable/rectangle.xml new file mode 100644 index 0000000..36e5733 --- /dev/null +++ b/app/src/main/res/drawable/rectangle.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_new_place.xml b/app/src/main/res/layout/activity_add_new_place.xml new file mode 100644 index 0000000..2d33566 --- /dev/null +++ b/app/src/main/res/layout/activity_add_new_place.xml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_report.xml b/app/src/main/res/layout/activity_add_report.xml new file mode 100644 index 0000000..105944a --- /dev/null +++ b/app/src/main/res/layout/activity_add_report.xml @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..c654d61 --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..5cb9fa6 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_manage_roles.xml b/app/src/main/res/layout/activity_manage_roles.xml new file mode 100644 index 0000000..436f08d --- /dev/null +++ b/app/src/main/res/layout/activity_manage_roles.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_place_detail.xml b/app/src/main/res/layout/activity_place_detail.xml new file mode 100644 index 0000000..809c04d --- /dev/null +++ b/app/src/main/res/layout/activity_place_detail.xml @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_register.xml b/app/src/main/res/layout/activity_register.xml new file mode 100644 index 0000000..dc50f7f --- /dev/null +++ b/app/src/main/res/layout/activity_register.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/alert_dialog_add_new_place.xml b/app/src/main/res/layout/alert_dialog_add_new_place.xml new file mode 100644 index 0000000..c58a688 --- /dev/null +++ b/app/src/main/res/layout/alert_dialog_add_new_place.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/alert_dialog_change_password.xml b/app/src/main/res/layout/alert_dialog_change_password.xml new file mode 100644 index 0000000..98fa1d6 --- /dev/null +++ b/app/src/main/res/layout/alert_dialog_change_password.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/alert_dialog_profile.xml b/app/src/main/res/layout/alert_dialog_profile.xml new file mode 100644 index 0000000..aa8a405 --- /dev/null +++ b/app/src/main/res/layout/alert_dialog_profile.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_favorite.xml b/app/src/main/res/layout/fragment_favorite.xml new file mode 100644 index 0000000..ec2bec1 --- /dev/null +++ b/app/src/main/res/layout/fragment_favorite.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_map.xml b/app/src/main/res/layout/fragment_map.xml new file mode 100644 index 0000000..71f6f57 --- /dev/null +++ b/app/src/main/res/layout/fragment_map.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_my_reports.xml b/app/src/main/res/layout/fragment_my_reports.xml new file mode 100644 index 0000000..80751de --- /dev/null +++ b/app/src/main/res/layout/fragment_my_reports.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_view_favorite.xml b/app/src/main/res/layout/list_view_favorite.xml new file mode 100644 index 0000000..0688a48 --- /dev/null +++ b/app/src/main/res/layout/list_view_favorite.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/list_view_my_report.xml b/app/src/main/res/layout/list_view_my_report.xml new file mode 100644 index 0000000..4a60f70 --- /dev/null +++ b/app/src/main/res/layout/list_view_my_report.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/list_view_place_reports.xml b/app/src/main/res/layout/list_view_place_reports.xml new file mode 100644 index 0000000..589a0e5 --- /dev/null +++ b/app/src/main/res/layout/list_view_place_reports.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/list_view_users.xml b/app/src/main/res/layout/list_view_users.xml new file mode 100644 index 0000000..be81b06 --- /dev/null +++ b/app/src/main/res/layout/list_view_users.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/loading.xml b/app/src/main/res/layout/loading.xml new file mode 100644 index 0000000..89f2af8 --- /dev/null +++ b/app/src/main/res/layout/loading.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/app/src/main/res/layout/toolbar.xml b/app/src/main/res/layout/toolbar.xml new file mode 100644 index 0000000..f2516df --- /dev/null +++ b/app/src/main/res/layout/toolbar.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_bottom_navigation.xml b/app/src/main/res/menu/menu_bottom_navigation.xml new file mode 100644 index 0000000..608d269 --- /dev/null +++ b/app/src/main/res/menu/menu_bottom_navigation.xml @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_dashboard.xml b/app/src/main/res/menu/menu_dashboard.xml new file mode 100644 index 0000000..a33bc4a --- /dev/null +++ b/app/src/main/res/menu/menu_dashboard.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..b580b49 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..f140925 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..f2ba4d8 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..6936ee9 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..a56ba49 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..aa1b049 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..19404d0 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..c7646e1 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..42565f3 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..e2be485 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..5304018 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..757535e Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..d6c9573 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..aebc5cb Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..5ef4b8b Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml new file mode 100644 index 0000000..654a645 --- /dev/null +++ b/app/src/main/res/values-es/strings.xml @@ -0,0 +1,155 @@ + + AcLoc - Aplicación de Ubicaciones Accesibles + + + INICIAR SESIÓN + REGISTRARSE + GUARDAR LUGAR + ACTUALIZAR + CANCELAR + ENTREGAR + AGREGAR INFORME + + + Agregar Informe + Detalles del Lugar + Agregar Lugar + + + Introduzca nombre de usuario + Introducir contraseña + Introducir correo electrónico + Introducir nombre + Introduzca ubicación + Ingrese el contacto + ¿Ya tienes una cuenta? + Por favor, inicie sesión + Por favor, regístrese + ¿No tienes una cuenta? + Mi Informe + Mapa + Favorito + Perfil + Cambiar contraseña + Gestionar roles + Cambiar idioma + Cerrar sesión + Ingrese el nombre del lugar + Ingrese latitud + Ingrese longitud + Ingrese dirección + Ingrese la descripción del lugar + Buscar lugar + Ingrese contraseña anterior + Ingrese nueva contraseña + Ingrese descripción + Nombre del Lugar + Dirección + Descripción del Lugar + Descripción del Informe + Informe + Buscar Usuario + Usuario + Rol + Cambiar Rol + + + BUENO + REGULAR + MALO + + + cambiar el idioma a inglés + ¿Estás seguro de que quieres cerrar sesión? + ¿Estás seguro de que deseas %1$s?\nADVERTENCIA: Esta acción no se puede deshacer + Sí + No + Se requiere permiso de ubicación + Esta aplicación requiere permiso de ubicación para funcionar correctamente. Por favor, habilítalo en Configuración. + Ir a Configuración + Salir de la aplicación + + + ¡Algo salió mal! + Error de inicio de sesión + Credenciales inválidas. Por favor, inténtalo de nuevo. + Inicio de sesión exitoso + Por favor espera... + El usuario ya existe. Por favor, inicia sesión. + ¡Perfil actualizado con éxito! + Registro exitoso + Intenta de nuevo más tarde + Actualización fallida + Algo salió mal. Intenta de nuevo. + Actualizando... + Contraseña anterior inválida + Error de red. Intenta de nuevo. + Cambiando la contraseña... + ¡Contraseña actualizada con éxito! + Falló la actualización de la contraseña. + Verificando la contraseña anterior... + Cambiar Rol a + Actualizando Roles... + ¡Rol de usuario actualizado con éxito! + Actualización fallida. Error del servidor. Intenta de nuevo. + Calificación: MALA + Calificación: REGULAR + Calificación: BUENA + Eliminar informe + Eliminando informe... + ¡Informe eliminado! + No se pudo eliminar el informe + Eliminando de favoritos... + Favorito eliminado + No se pudo eliminar el favorito + Cargando favoritos... + No se encontraron favoritos. + No se pudieron cargar los favoritos. Intenta de nuevo. + Se requiere permiso de ubicación + No se pudieron cargar los lugares + Cargando informes... + No se encontraron informes. + No se pudieron cargar los informes. Intenta de nuevo. + ¡Lugar actualizado con éxito! + Actualizando lugar... + ¡Lugar agregado con éxito! + No se pudo obtener el lugar. Intenta de nuevo + Error al insertar. Error del servidor. + Error al enviar el informe. Intenta de nuevo + ¡Informe enviado con éxito! + ¡Por favor selecciona una calificación! + Actualizando informe... + ¡Informe actualizado con éxito! + No se pudieron cargar los usuarios. Intenta de nuevo. + No se encontraron usuarios + Cargando usuarios... + Agregando a favoritos... + ¡Lugar agregado a favoritos! + No se pudo agregar a favoritos. Error del servidor. + Restaurando favorito... + ¡Lugar restaurado a favoritos! + No se pudo restaurar el favorito. + Eliminado de favoritos + No se pudo eliminar de favoritos + Verificando estado de favorito... + + + + + + + + + + + + + + + + + + AIzaSyCzcuZhHd-GqadewAiCbh4NuaBhO2AcPOY + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..678135f --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..3b639ea --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,59 @@ + + + #FF000000 + #FFFFFFFF + + + #0D47A1 + #4D8EF4 + + + #42A5F5 + #5AB1F6 + + + #2E7D32 + #FFA000 + #C62828 + + + #FAFAFA + #121212 + + + #FFFFFF + #040404 + + + #FFFFFF + #FFFFFF + + #000000 + #FFFFFF + + #212121 + + #E0E0E0 + + #212121 + #F5F5F5 + + + #C62828 + #F44336 + + + #FF4081 + #C51162 + + #e8edf6 + #232324 + + + #919395 + #4CAF50 + #FFC107 + #F44336 + #e8edf6 + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..7766af4 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,51 @@ + + + 16dp + 8dp + 6dp + 5dp + 14dp + 60dp + 4dp + 30dp + 10sp + 10sp + 15sp + 16sp + 18sp + 25sp + 50sp + 20sp + 10dp + 10dp + 20dp + 2dp + 8dp + 85dp + + + + 25dp + 25dp + 50dp + 50dp + + 100dp + 100dp + + 150dp + 150dp + + 70dp + 70dp + + 40dp + 8dp + + 16dp + 16dp + 16dp + 16dp + 8dp + + \ No newline at end of file diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..c5d5899 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..8baf61e --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,158 @@ + + AcLoc - Accessible Locations Application + + + LOGIN + REGISTER + SAVE PLACE + UPDATE + CANCEL + SUBMIT + ADD REPORT + + + Add Report + Place Details + Add Place + + + + Enter Username + Enter Password + Enter Email + Enter Name + Enter Location + Enter Contact + "Already have an Account? + Please login + Please register + Don\'t have an Account? + Favorite + My Report + Map + Profile + Change Password + Manage Roles + Change Language + Logout + Enter Place Name + Enter Latitude + Enter Longitude + Enter Address + Enter Place Description + Search Place + Enter Old Password + Enter New Password + Place Name + Address + Place Description + Enter Description + Report Description + Report + Search User + User + Role + Change Role + + + GOOD + AVERAGE + BAD + + + change language to Spanish + Are you sure you want to logout? + Are you sure you want to %1$s?\nWARNING: This action cannot be undone + Yes + No + Location permission required + This app requires location permission to function properly. Please enable it in Settings. + Go to Settings + Exit App + + + + Something went wrong!!! + Login failed + Invalid Credentials. Please try again. + Login Successful + Please wait... + "User already exists. Please login. + Profile Updated Successfully! + Registration Successful + Try again later + Update Failed + Something went wrong. Try again. + Updating... + Invalid old password + Network error. Try again. + Changing password... + Password updated successfully! + Password update failed. + Verifying old password... + change Role to + Updating Roles... + User Role updated successfully! + Update failed. Server error. Try again + Rating: BAD + Rating: AVERAGE + Rating: GOOD + Delete report + Removing Report... + Report removed! + Failed to remove report + Removing from favorites... + Favorite removed + Failed to remove Favorite + Loading favorites... + No Favorite found. + Failed to load favorite. Try again. + Location permission is required + Failed to load places + Loading reports... + No reports found. + Failed to load reports. Try again. + Place updated successfully! + Updating place... + Place inserted successfully! + Failed to extract place.Try again + Insert failed. Server error. + Report submission failed. Try again + Report submitted successfully! + Please select rating! + Updating report... + Report updated successfully! + Failed to load users. Try again. + No users found + Loading users... + Adding to favorites... + Place added to favorites! + Failed to add favorite. Server error. + Restoring favorite... + Place restored to favorites! + Failed to restore favorite. + Removed from favorites + Failed to remove from favorites + Checking favorite status... + + + + + + + + + + + + + + + + + + + AIzaSyCzcuZhHd-GqadewAiCbh4NuaBhO2AcPOY + + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..ed444ad --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000..fa0f996 --- /dev/null +++ b/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000..9ee9997 --- /dev/null +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file