From b513e691bc6c5acf8a22bf6e31db76a3c7a69259 Mon Sep 17 00:00:00 2001 From: Georgy Lebedik Date: Sun, 22 Jun 2025 03:52:38 +0300 Subject: [PATCH 01/14] Delete event functionality. --- .../context/event/activity/EventActivity.java | 140 ++++----------- .../context/event/service/EventService.java | 4 + .../event/viewmodel/EventViewModel.java | 15 +- .../meeter/context/image/ImageDownloader.java | 44 ++--- .../infrastructure/common/DateHelper.java | 161 +++++++++++++++++- .../res/layout/activity_event_editable.xml | 8 + .../src/main/res/values-en/strings.xml | 8 + .../src/main/res/values-ru-rRU/strings.xml | 8 + AndroidClient/src/main/res/values/strings.xml | 8 + 9 files changed, 264 insertions(+), 132 deletions(-) diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java index 0d97e26..88e24ec 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java @@ -1,6 +1,7 @@ package com.tom.meeter.context.event.activity; import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; import static com.tom.meeter.context.event.activity.EventLocationMapActivity.createEventLocationMapActivityIntent; import static com.tom.meeter.context.event.activity.EventOnMapActivity.dispatchToEventOnMapActivity; import static com.tom.meeter.context.event.utils.Utils.createUpdateEventRequest; @@ -8,38 +9,36 @@ import static com.tom.meeter.infrastructure.common.CommonHelper.UI_DATE_TIME_FORMAT; import static com.tom.meeter.infrastructure.common.CommonHelper.dateOrNull; import static com.tom.meeter.infrastructure.common.CommonHelper.textOrNull; +import static com.tom.meeter.infrastructure.common.DateHelper.showDateTimePicker; import static com.tom.meeter.infrastructure.common.ImagesHelper.circleImage; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.showMessage; import android.accounts.AccountManager; -import android.app.DatePickerDialog; -import android.app.TimePickerDialog; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; import android.view.View; -import android.widget.EditText; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.ViewModelProviders; import androidx.viewbinding.ViewBinding; -import com.google.android.material.datepicker.CalendarConstraints; -import com.google.android.material.datepicker.DateValidatorPointForward; -import com.google.android.material.datepicker.MaterialDatePicker; import com.tom.meeter.App; +import com.tom.meeter.R; import com.tom.meeter.context.auth.infrastructure.AuthHelper; import com.tom.meeter.context.event.message.UpdateEventRequest; import com.tom.meeter.context.event.service.EventService; import com.tom.meeter.context.event.viewmodel.EventViewModel; import com.tom.meeter.context.network.dto.EventDTO; +import com.tom.meeter.context.profile.activity.ProfileActivity; import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.databinding.ActivityEventEditableBinding; import com.tom.meeter.databinding.ActivityEventReadableBinding; @@ -47,10 +46,6 @@ import com.tom.meeter.infrastructure.http.HttpErrorLogger; import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelFactory; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Locale; - import javax.inject.Inject; import okhttp3.ResponseBody; @@ -161,21 +156,23 @@ private void initEditableLayout(String token) { eBinding.saveEventButton.setOnClickListener(v -> { UpdateEventRequest req = createUpdateEventRequest(eventCache, eBinding); if (req.isEmpty()) { - showMessage(this, "Empty update request is not sent."); + showMessage(this, getString(R.string.empty_update_request_is_not_sent)); return; } eventService.updateEvent(Globals.getAuthHeader(token), eventCache.getId(), req).enqueue( new HttpErrorLogger<>(getApplicationContext()) { @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - eventCache = response.body(); + public void onResponse(Call call, Response res) { + super.onResponse(call, res); + if (res.isSuccessful()) { + eventCache = res.body(); updateEditableLayout(); - showMessage(EventActivity.this, "Saved."); + showMessage(EventActivity.this, getString(R.string.saved)); } } }); }); + eBinding.deleteEventButton.setOnClickListener(v -> showAlertDialog()); /* @@ -185,9 +182,9 @@ public void onResponse(Call call, Response response) { updateEditableLayout(); eBinding.selectStartingDateButton.setOnClickListener( - v -> showDateTimePicker(eBinding.eventStarting)); + v -> showDateTimePicker(this, eBinding.eventStarting)); eBinding.selectEndingDateButton.setOnClickListener( - v -> showDateTimePicker(eBinding.eventEnding)); + v -> showDateTimePicker(this, eBinding.eventEnding)); eBinding.btnEventLocationMap.setOnClickListener( v -> mapResult.launch( createEventLocationMapActivityIntent(this, eventCache.getId()))); @@ -202,6 +199,29 @@ public void onResponse(Call call, Response response) { }); } + private void showAlertDialog() { + new AlertDialog.Builder(this) + .setTitle(R.string.delete_event) + .setMessage(R.string.are_you_sure_delete_event) + .setPositiveButton(R.string.delete, (dialog, which) -> { + eventService.deleteEvent(getAuthHeader(accountManager), eventCache.getId()) + .enqueue(new HttpErrorLogger<>(getApplicationContext()) { + @Override + public void onResponse(Call call, Response resp) { + super.onResponse(call, resp); + if (resp.isSuccessful()) { + showMessage(EventActivity.this, getString(R.string.deleted)); + startActivity(new Intent(getApplicationContext(), ProfileActivity.class)); + finish(); + } + } + }); + dialog.dismiss(); + }) + .setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()) + .show(); + } + private void updateEditablePhoto() { if (binding instanceof ActivityEventEditableBinding eBinding) { eBinding.eventPhoto.setImageBitmap(circleImage(photoCache, 600, 600)); @@ -243,92 +263,6 @@ public View onCreateView( return super.onCreateView(parent, name, ctx, attrs); } - // Метод для отображения DatePickerDialog - private void showDatePickerDialog(final EditText targetEditText) { - // Получаем текущую дату - Calendar calendar = Calendar.getInstance(); - int year = calendar.get(Calendar.YEAR); - int month = calendar.get(Calendar.MONTH); - int day = calendar.get(Calendar.DAY_OF_MONTH); - - // Создаем и показываем DatePickerDialog - DatePickerDialog datePickerDialog = new DatePickerDialog(this, - (view, selectedYear, selectedMonth, selectedDay) -> { - // Устанавливаем выбранную дату в EditText - String selectedDate = selectedDay + "/" + (selectedMonth + 1) + "/" + selectedYear; - targetEditText.setText(selectedDate); - }, year, month, day); - - // Показываем диалог - datePickerDialog.show(); - } - - // Метод для отображения Material DatePicker - private void showMaterialDatePicker(final EditText targetEditText) { - // Создаём constraints (ограничения для выбора даты) - CalendarConstraints.Builder constraintsBuilder = new CalendarConstraints.Builder(); - Calendar calendar = Calendar.getInstance(); - constraintsBuilder.setValidator(DateValidatorPointForward.from(calendar.getTimeInMillis())); - - // Создаем Material DatePicker - MaterialDatePicker.Builder builder = MaterialDatePicker.Builder.datePicker(); - builder.setCalendarConstraints(constraintsBuilder.build()); - builder.setTitleText("Select Date"); - - MaterialDatePicker datePicker = builder.build(); - - // Устанавливаем слушатель на выбор даты - datePicker.addOnPositiveButtonClickListener(selection -> { - // Форматируем выбранную дату - Calendar selectedDate = Calendar.getInstance(); - selectedDate.setTimeInMillis(selection); - String selectedDateString = selectedDate.get(Calendar.DAY_OF_MONTH) + "/" + - (selectedDate.get(Calendar.MONTH) + 1) + "/" + - selectedDate.get(Calendar.YEAR); - - // Устанавливаем выбранную дату в поле - targetEditText.setText(selectedDateString); - }); - - // Показываем диалог - datePicker.show(getSupportFragmentManager(), datePicker.toString()); - } - - private void showDateTimePicker(EditText target) { - final Calendar calendar = Calendar.getInstance(); - - DatePickerDialog datePickerDialog = new DatePickerDialog( - this, - (view, year, month, dayOfMonth) -> { - calendar.set(Calendar.YEAR, year); - calendar.set(Calendar.MONTH, month); - calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth); - - TimePickerDialog timePickerDialog = new TimePickerDialog( - this, - (timeView, hourOfDay, minute) -> { - calendar.set(Calendar.HOUR_OF_DAY, hourOfDay); - calendar.set(Calendar.MINUTE, minute); - - SimpleDateFormat sdf = new SimpleDateFormat( - "yyyy-MM-dd HH:mm", Locale.getDefault()); - String formatted = sdf.format(calendar.getTime()); - target.setText(formatted); - }, - calendar.get(Calendar.HOUR_OF_DAY), - calendar.get(Calendar.MINUTE), - true - ); - - timePickerDialog.show(); - }, - calendar.get(Calendar.YEAR), - calendar.get(Calendar.MONTH), - calendar.get(Calendar.DAY_OF_MONTH) - ); - - datePickerDialog.show(); - } public static void dispatchToEventActivity(Context ctx, String eventId) { ctx.startActivity(createEventActivityIntent(ctx, eventId)); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/service/EventService.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/service/EventService.java index e8b107e..cc3308c 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/service/EventService.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/service/EventService.java @@ -7,6 +7,7 @@ import retrofit2.Call; import retrofit2.http.Body; +import retrofit2.http.DELETE; import retrofit2.http.GET; import retrofit2.http.Header; import retrofit2.http.PATCH; @@ -21,4 +22,7 @@ public interface EventService { Call updateEvent( @Header(AUTH_HEADER) String authHeader, @Path("id") String eventId, @Body UpdateEventRequest req); + + @DELETE("/event/{id}") + Call deleteEvent(@Header(AUTH_HEADER) String authHeader, @Path("id") String eventId); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/viewmodel/EventViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/viewmodel/EventViewModel.java index c403dc6..ff37c3d 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/viewmodel/EventViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/viewmodel/EventViewModel.java @@ -3,7 +3,6 @@ import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.app.Activity; -import android.util.Log; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; @@ -14,8 +13,8 @@ import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.context.user.viewmodel.UserViewModel; import com.tom.meeter.infrastructure.common.Globals; -import com.tom.meeter.infrastructure.http.ErrorLogger; import com.tom.meeter.infrastructure.http.HttpCodes; +import com.tom.meeter.infrastructure.http.HttpErrorLogger; import javax.inject.Inject; @@ -42,11 +41,12 @@ public EventViewModel(EventService eventService, ImageDownloader imageDownloader public void fetchEventInformation(String token, String eventId, Activity activity) { eventService.getEvent(Globals.getAuthHeader(token), eventId).enqueue( - new ErrorLogger<>(activity) { + new HttpErrorLogger<>(activity) { @Override - public void onResponse(Call call, Response response) { - EventDTO body = response.body(); - if (response.code() == HttpCodes.OK && body != null) { + public void onResponse(Call call, Response resp) { + super.onResponse(call, resp); + EventDTO body = resp.body(); + if (resp.code() == HttpCodes.OK && body != null) { eventLiveData.setValue(body); String photoPath = body.getPhotoPath(); if (photoPath != null) { @@ -58,10 +58,9 @@ public void onResponse(Call call, Response response) { } return; } - if (response.code() == HttpCodes.NOT_AUTHENTICATED) { + if (resp.code() == HttpCodes.NOT_AUTHENTICATED) { activity.recreate(); } - Log.i(TAG, "/event/{id}: " + response.code() + " : " + body); } } ); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageDownloader.java b/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageDownloader.java index d143032..8d5355f 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageDownloader.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageDownloader.java @@ -5,11 +5,10 @@ import android.accounts.AccountManager; import android.content.Context; -import android.util.Log; import com.tom.meeter.context.image.service.ImageService; -import com.tom.meeter.infrastructure.http.ErrorLogger; import com.tom.meeter.infrastructure.http.HttpCodes; +import com.tom.meeter.infrastructure.http.HttpErrorLogger; import java.util.function.Consumer; @@ -34,19 +33,21 @@ public void downloadEventImage( String photoPath, Context ctx, Consumer onDownloaded, Runnable onNotAuthenticated) { imageService.downloadEventImage(getAuthHeader(AccountManager.get(ctx)), photoPath) - .enqueue(new ErrorLogger<>(ctx) { + .enqueue(new HttpErrorLogger<>(ctx) { @Override public void onResponse(Call call, Response response) { + super.onResponse(call, response); //Log.d(TAG, "/images/event" + photoPath + " downloaded..."); - try (ResponseBody body = response.body()) { - if (response.code() == HttpCodes.OK && body != null) { - onDownloaded.accept(response.body()); - return; + if (response.code() == HttpCodes.OK) { + try (ResponseBody body = response.body()) { + if (body != null) { + onDownloaded.accept(response.body()); + return; + } } - if (response.code() == HttpCodes.NOT_AUTHENTICATED) { - onNotAuthenticated.run(); - } - Log.i(TAG, "/images/event/: " + response.code() + " : " + body); + } + if (response.code() == HttpCodes.NOT_AUTHENTICATED) { + onNotAuthenticated.run(); } } }); @@ -56,18 +57,21 @@ public void downloadUserImage( String photoPath, Context ctx, Consumer onDownloaded, Runnable onNotAuthenticated) { imageService.downloadUserImage(getAuthHeader(AccountManager.get(ctx)), photoPath) - .enqueue(new ErrorLogger<>(ctx) { + .enqueue(new HttpErrorLogger<>(ctx) { @Override public void onResponse(Call call, Response response) { - try (ResponseBody body = response.body()) { - if (response.code() == HttpCodes.OK && body != null) { - onDownloaded.accept(response.body()); - return; + super.onResponse(call, response); + if (response.code() == HttpCodes.OK) { + try (ResponseBody body = response.body()) { + if (body != null) { + onDownloaded.accept(response.body()); + return; + } } - if (response.code() == HttpCodes.NOT_AUTHENTICATED) { - onNotAuthenticated.run(); - } - Log.i(TAG, "/images/user/: " + response.code() + " : " + body); + return; + } + if (response.code() == HttpCodes.NOT_AUTHENTICATED) { + onNotAuthenticated.run(); } } }); diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/DateHelper.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/DateHelper.java index cbe066b..ddb71ee 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/DateHelper.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/DateHelper.java @@ -1,19 +1,40 @@ package com.tom.meeter.infrastructure.common; +import static com.tom.meeter.infrastructure.common.CommonHelper.EMPTY_STR; +import static com.tom.meeter.infrastructure.common.CommonHelper.UI_DATE_FORMAT; +import static com.tom.meeter.infrastructure.common.CommonHelper.UI_DATE_TIME_FORMAT; + +import android.app.DatePickerDialog; +import android.app.TimePickerDialog; +import android.content.Context; import android.util.Log; +import android.widget.EditText; + +import androidx.appcompat.app.AppCompatActivity; + +import com.google.android.material.datepicker.CalendarConstraints; +import com.google.android.material.datepicker.DateValidatorPointForward; +import com.google.android.material.datepicker.MaterialDatePicker; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.time.format.DateTimeParseException; import java.util.Calendar; +import java.util.Locale; public final class DateHelper { + private static final String TAG = DateHelper.class.getCanonicalName(); private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd"); private DateHelper() { } - public static String getAgeFromDate(String date) { + public static String getAgeFromDateOld(String date) { if (date == null) { return ""; } @@ -36,4 +57,142 @@ public static String getAgeFromDate(String date) { return String.valueOf(age); } + + + public static String getAgeFromDate(String date) { + if (date == null || date.isEmpty()) { + return EMPTY_STR; + } + try { + LocalDate birthDate = LocalDate.parse(date, UI_DATE_FORMAT); + LocalDate today = LocalDate.now(); + int age = Period.between(birthDate, today).getYears(); + return String.valueOf(age); + } catch (DateTimeParseException e) { + Log.e(TAG, "Ошибка парсинга даты: " + e.getMessage(), e); + return null; + } + } + + + // Метод для отображения DatePickerDialog + public static void showDatePickerDialog(Context ctx, final EditText targetEditText) { + // Получаем текущую дату + Calendar calendar = Calendar.getInstance(); + int year = calendar.get(Calendar.YEAR); + int month = calendar.get(Calendar.MONTH); + int day = calendar.get(Calendar.DAY_OF_MONTH); + + // Создаем и показываем DatePickerDialog + DatePickerDialog datePickerDialog = new DatePickerDialog(ctx, + (view, selectedYear, selectedMonth, selectedDay) -> { + // Устанавливаем выбранную дату в EditText + String selectedDate = selectedDay + "/" + (selectedMonth + 1) + "/" + selectedYear; + targetEditText.setText(selectedDate); + }, year, month, day); + + // Показываем диалог + datePickerDialog.show(); + } + + // Метод для отображения Material DatePicker + public static void showMaterialDatePicker( + AppCompatActivity activity, final EditText targetEditText) { + // Создаём constraints (ограничения для выбора даты) + CalendarConstraints.Builder constraintsBuilder = new CalendarConstraints.Builder(); + Calendar calendar = Calendar.getInstance(); + constraintsBuilder.setValidator(DateValidatorPointForward.from(calendar.getTimeInMillis())); + + // Создаем Material DatePicker + MaterialDatePicker.Builder builder = MaterialDatePicker.Builder.datePicker(); + builder.setCalendarConstraints(constraintsBuilder.build()); + builder.setTitleText("Select Date"); + + MaterialDatePicker datePicker = builder.build(); + + // Устанавливаем слушатель на выбор даты + datePicker.addOnPositiveButtonClickListener(selection -> { + // Форматируем выбранную дату + Calendar selectedDate = Calendar.getInstance(); + selectedDate.setTimeInMillis(selection); + String selectedDateString = selectedDate.get(Calendar.DAY_OF_MONTH) + "/" + + (selectedDate.get(Calendar.MONTH) + 1) + "/" + + selectedDate.get(Calendar.YEAR); + + // Устанавливаем выбранную дату в поле + targetEditText.setText(selectedDateString); + }); + + // Показываем диалог + datePicker.show(activity.getSupportFragmentManager(), datePicker.toString()); + } + + public static void showDateTimePickerOld(Context ctx, EditText target) { + final Calendar calendar = Calendar.getInstance(); + + DatePickerDialog datePickerDialog = new DatePickerDialog( + ctx, + (view, year, month, dayOfMonth) -> { + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, month); + calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth); + + TimePickerDialog timePickerDialog = new TimePickerDialog( + ctx, + (timeView, hourOfDay, minute) -> { + calendar.set(Calendar.HOUR_OF_DAY, hourOfDay); + calendar.set(Calendar.MINUTE, minute); + + SimpleDateFormat sdf = new SimpleDateFormat( + "yyyy-MM-dd HH:mm", Locale.getDefault()); + String formatted = sdf.format(calendar.getTime()); + target.setText(formatted); + }, + calendar.get(Calendar.HOUR_OF_DAY), + calendar.get(Calendar.MINUTE), + true + ); + + timePickerDialog.show(); + }, + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH), + calendar.get(Calendar.DAY_OF_MONTH) + ); + + datePickerDialog.show(); + } + + public static void showDateTimePicker(Context ctx, EditText target) { + LocalDate nowDate = LocalDate.now(); + LocalTime nowTime = LocalTime.now(); + + DatePickerDialog datePickerDialog = new DatePickerDialog( + ctx, + (view, year, month, dayOfMonth) -> { + LocalDate selectedDate = LocalDate.of(year, month + 1, dayOfMonth); + + TimePickerDialog timePickerDialog = new TimePickerDialog( + ctx, + (timeView, hourOfDay, minute) -> { + LocalTime selectedTime = LocalTime.of(hourOfDay, minute); + LocalDateTime dateTime = LocalDateTime.of(selectedDate, selectedTime); + + String formatted = dateTime.format(UI_DATE_TIME_FORMAT); + target.setText(formatted); + }, + nowTime.getHour(), + nowTime.getMinute(), + true + ); + + timePickerDialog.show(); + }, + nowDate.getYear(), + nowDate.getMonthValue() - 1, + nowDate.getDayOfMonth() + ); + + datePickerDialog.show(); + } } diff --git a/AndroidClient/src/main/res/layout/activity_event_editable.xml b/AndroidClient/src/main/res/layout/activity_event_editable.xml index 102f0f0..a4ef31a 100644 --- a/AndroidClient/src/main/res/layout/activity_event_editable.xml +++ b/AndroidClient/src/main/res/layout/activity_event_editable.xml @@ -268,5 +268,13 @@ android:layout_height="wrap_content" android:layout_marginTop="15dp" android:text="Save Event" /> + + +