From 3b2d19bd0bf8382299058137667756a782013cb0 Mon Sep 17 00:00:00 2001 From: ErolHaagenrud Date: Wed, 17 Dec 2025 08:04:08 +0100 Subject: [PATCH] =?UTF-8?q?F=C3=B8r=20tilgangskontroll=20til=20kalendre?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CalendarDetailsBottomSheet.java | 110 ++++++---- .../com/kbs/kbsintranett/CalendarEvent.java | 71 +++--- .../com/kbs/kbsintranett/CalendarManager.java | 3 +- .../kbs/kbsintranett/CreateEventFragment.java | 206 ++++++++++++++---- .../kbs/kbsintranett/CreateEventRequest.java | 18 +- .../com/kbs/kbsintranett/HomeFragment.java | 3 +- .../kbs/kbsintranett/NotificationWorker.java | 85 ++++---- .../kbs/kbsintranett/WordPressApiService.java | 7 + .../layout/bottom_sheet_calendar_details.xml | 39 +++- .../main/res/layout/fragment_create_event.xml | 64 ++++-- 10 files changed, 419 insertions(+), 187 deletions(-) diff --git a/app/src/main/java/com/kbs/kbsintranett/CalendarDetailsBottomSheet.java b/app/src/main/java/com/kbs/kbsintranett/CalendarDetailsBottomSheet.java index 2d3db3b..a3136a8 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CalendarDetailsBottomSheet.java +++ b/app/src/main/java/com/kbs/kbsintranett/CalendarDetailsBottomSheet.java @@ -1,23 +1,29 @@ package com.kbs.kbsintranett; -import android.content.Intent; +import android.app.AlertDialog; import android.os.Bundle; -import android.provider.CalendarContract; +import android.text.Html; +import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.navigation.Navigation; +import androidx.navigation.fragment.NavHostFragment; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; +import com.google.gson.JsonElement; +import java.util.ArrayList; +import java.util.Collections; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; public class CalendarDetailsBottomSheet extends BottomSheetDialogFragment { - private CalendarEvent event; public CalendarDetailsBottomSheet(CalendarEvent event) { @@ -33,20 +39,19 @@ public class CalendarDetailsBottomSheet extends BottomSheetDialogFragment { TextView time = view.findViewById(R.id.sheet_time); TextView desc = view.findViewById(R.id.sheet_desc); TextView loc = view.findViewById(R.id.sheet_location); - Button btnAdd = view.findViewById(R.id.btn_add_to_calendar); - - // Skjul knapp siden appen nå varsler automatisk (iht krav) - btnAdd.setVisibility(View.GONE); + LinearLayout adminLayout = view.findViewById(R.id.layout_admin_buttons); + Button btnDelete = view.findViewById(R.id.btn_delete); + Button btnEdit = view.findViewById(R.id.btn_edit); title.setText(event.getTitle()); time.setText(event.getTime() + " (" + event.getDay() + ". " + event.getMonth() + ")"); if (!event.getDescription().isEmpty()) { - // HER ER FIKSEN FOR HTML: - desc.setText(android.text.Html.fromHtml(event.getDescription(), android.text.Html.FROM_HTML_MODE_COMPACT)); + // Skjul #varsel-taggen for visning + String cleanDesc = event.getDescription().replaceAll("#varsel:[\\d,]+", "").trim(); + desc.setText(Html.fromHtml(cleanDesc, Html.FROM_HTML_MODE_COMPACT)); desc.setVisibility(View.VISIBLE); - // Gjør linker klikkbare - desc.setMovementMethod(android.text.method.LinkMovementMethod.getInstance()); + desc.setMovementMethod(LinkMovementMethod.getInstance()); } else { desc.setVisibility(View.GONE); } @@ -58,36 +63,63 @@ public class CalendarDetailsBottomSheet extends BottomSheetDialogFragment { loc.setVisibility(View.GONE); } + // Sjekk admin-rettigheter + if (UserManager.getInstance().isEditorOrAbove()) { + adminLayout.setVisibility(View.VISIBLE); + + btnDelete.setOnClickListener(v -> confirmDelete()); + + btnEdit.setOnClickListener(v -> { + // Send eventet videre til redigering + Bundle bundle = new Bundle(); + bundle.putSerializable("edit_event", event); + // Vi må navigere via parent fragmentets navController + NavHostFragment.findNavController(this).navigate(R.id.navigation_create_event, bundle); + dismiss(); + }); + } + return view; } - private void addToSystemCalendar() { - try { - SimpleDateFormat apiFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); - apiFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); + private void confirmDelete() { + new AlertDialog.Builder(getContext()) + .setTitle("Slett hendelse") + .setMessage("Er du sikker på at du vil slette '" + event.getTitle() + "'?") + .setPositiveButton("Slett", (dialog, which) -> deleteEvent()) + .setNegativeButton("Avbryt", null) + .show(); + } - Date startDate = apiFormat.parse(event.getRawDate()); - long startMillis = startDate.getTime(); - long endMillis = startMillis + (60 * 60 * 1000); // Default 1 time hvis slutt mangler + private void deleteEvent() { + // Vi må sende en CreateEventRequest med ID for å slette + // Vi trenger ikke fylle ut alt, bare ID og kalendertype + // Siden vi ikke vet nøyaktig hvilken kalender den kom fra (APIet gir ikke det), + // prøver vi "Felles" som default, eller prøver å slette fra ID. + // PHP-koden vi lagde støtter sletting basert på ID hvis vi sender riktig kalendertype. + // For nå antar vi "Felles" eller looper i backend. I V11.3 PHP scriptet sletter den basert på ID i valgt kalender. - if (event.getRawEndDate() != null && !event.getRawEndDate().isEmpty()) { - Date endDate = apiFormat.parse(event.getRawEndDate()); - endMillis = endDate.getTime(); + CreateEventRequest req = new CreateEventRequest( + "", "", "", "", "", "Felles", new ArrayList<>(), false, "" + ); + req.id = event.getId(); + + RetrofitClient.getApiService().deleteCalendarEvent(req).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + Toast.makeText(getContext(), "Slettet!", Toast.LENGTH_SHORT).show(); + dismiss(); + // Her burde vi ideelt sett oppdatert listen bak, men brukeren kan dra for å oppdatere + } else { + Toast.makeText(getContext(), "Kunne ikke slette (Er det en felles-hendelse?)", Toast.LENGTH_LONG).show(); + } } - Intent intent = new Intent(Intent.ACTION_INSERT) - .setData(CalendarContract.Events.CONTENT_URI) - .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startMillis) - .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endMillis) - .putExtra(CalendarContract.Events.TITLE, event.getTitle()) - .putExtra(CalendarContract.Events.DESCRIPTION, event.getDescription()) - .putExtra(CalendarContract.Events.EVENT_LOCATION, event.getLocation()) - .putExtra(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_BUSY); - - startActivity(intent); - - } catch (Exception e) { - e.printStackTrace(); - } + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(getContext(), "Nettverksfeil", Toast.LENGTH_SHORT).show(); + } + }); } } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/CalendarEvent.java b/app/src/main/java/com/kbs/kbsintranett/CalendarEvent.java index 4638d60..40fe62a 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CalendarEvent.java +++ b/app/src/main/java/com/kbs/kbsintranett/CalendarEvent.java @@ -1,31 +1,38 @@ package com.kbs.kbsintranett; import com.google.gson.annotations.SerializedName; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; -public class CalendarEvent { +public class CalendarEvent implements Serializable { + @SerializedName("id") + private String id; @SerializedName("title") private String title; - @SerializedName("start_date") // Juster denne nøkkelen til hva APIet faktisk returnerer (f.eks "start") + + @SerializedName("start_date") private String rawDate; - @SerializedName("end_date") // Juster nøkkel (f.eks "end") + + @SerializedName("end_date") private String rawEndDate; @SerializedName("description") private String description; + @SerializedName("location") private String location; - // --- NYTT FELT: Varsling (minutter før start) --- - // Nå henter denne verdien direkte fra "reminder_minutes" i JSON-responsen fra PHP - @SerializedName("reminder_minutes") - private int reminderMinutes = 15; // Default 15 min + // V11.0: Liste av minutter (f.eks [15, 60]) + @SerializedName("reminders") + private List reminders = new ArrayList<>(); - // --- UI-hjelpefelter (settes manuelt i appen etter parsing) --- - private String day; // F.eks "12" - private String month; // F.eks "DES" - private String time; // F.eks "10:00 - 11:30" + // UI-hjelpefelter + private String day; + private String month; + private String time; - // Konstruktør for Retrofit (Gson) + // Konstruktør public CalendarEvent(String title, String rawDate, String rawEndDate, String description, String location) { this.title = title; this.rawDate = rawDate; @@ -34,31 +41,41 @@ public class CalendarEvent { this.location = location; } - // Konstruktør for manuell opprettelse (f.eks ved feil) - public CalendarEvent(String title, String time, String day, String month) { - this.title = title; - this.time = time; - this.day = day; - this.month = month; - } - + public String getId() { return id; } public String getTitle() { return title; } public String getRawDate() { return rawDate; } public String getRawEndDate() { return rawEndDate; } public String getDescription() { return description != null ? description : ""; } public String getLocation() { return location != null ? location : ""; } - // Getters og Setters for UI-felter + // Henter listen. Hvis den er null (gamle data), returner tom liste. + public List getReminders() { + return reminders != null ? reminders : new ArrayList<>(); + } + +// --- KOMPATIBILITETS-METODER (For å fikse build-feil i CalendarManager og Worker) --- + + // Brukes av CalendarManager.java for lokale events + public void setReminderMinutes(int minutes) { + this.reminders = new ArrayList<>(); + if (minutes > 0) { + this.reminders.add(minutes); + } + } + + // Brukes hvis gammel kode prøver å hente ett tall. Returnerer det første i listen. + public int getReminderMinutes() { + if (reminders != null && !reminders.isEmpty()) { + return reminders.get(0); + } + return 0; + } + + // --- UI SETTERS/GETTERS --- public String getDay() { return day; } public void setDay(String day) { this.day = day; } - public String getMonth() { return month; } public void setMonth(String month) { this.month = month; } - public String getTime() { return time; } public void setTime(String time) { this.time = time; } - - // --- NYE METODER FOR VARSLING --- - public int getReminderMinutes() { return reminderMinutes; } - public void setReminderMinutes(int minutes) { this.reminderMinutes = minutes; } } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java b/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java index 5dbc441..ea7c107 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java +++ b/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java @@ -118,7 +118,8 @@ public class CalendarManager { } CalendarEvent event = new CalendarEvent(title, rawStart, rawEnd, desc, loc); - event.setReminderMinutes(0); // Systemet håndterer lokale varsler + // Denne metoden eksisterer nå i CalendarEvent (se fil 1) + event.setReminderMinutes(0); formatEventForUI(event); deviceEvents.add(event); diff --git a/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java b/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java index 5373479..5fe3036 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java @@ -22,19 +22,25 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.navigation.Navigation; +import com.google.android.material.chip.Chip; +import com.google.android.material.chip.ChipGroup; import com.google.gson.JsonElement; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; +import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class CreateEventFragment extends Fragment { private EditText etTitle, etDesc, etLocation; - private Spinner spinnerCalendar, spinnerReminder, spinnerRecurrence; + private Spinner spinnerCalendar, spinnerRecurrence; + private ChipGroup chipGroupReminders; private Switch switchAllDay; private TextView txtPreview; private Button btnStartDate, btnStartTime, btnEndDate, btnEndTime, btnSave; @@ -45,6 +51,9 @@ public class CreateEventFragment extends Fragment { private String selectedRRule = null; private boolean isCustomRecurrence = false; + // EDIT MODE + private CalendarEvent eventToEdit = null; + @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { @@ -61,8 +70,8 @@ public class CreateEventFragment extends Fragment { switchAllDay = view.findViewById(R.id.switch_all_day); spinnerCalendar = view.findViewById(R.id.spinner_calendar); - spinnerReminder = view.findViewById(R.id.spinner_reminder); spinnerRecurrence = view.findViewById(R.id.spinner_recurrence); + chipGroupReminders = view.findViewById(R.id.chip_group_reminders); txtPreview = view.findViewById(R.id.txt_time_preview); @@ -73,13 +82,22 @@ public class CreateEventFragment extends Fragment { btnSave = view.findViewById(R.id.btn_save_event); - // Initialiser tid (neste hele time) - startCal.add(Calendar.HOUR_OF_DAY, 1); - startCal.set(Calendar.MINUTE, 0); - endCal.setTime(startCal.getTime()); - endCal.add(Calendar.HOUR_OF_DAY, 1); + setupCalendarSpinner(); + setupReminderChips(); + + // SJEKK OM VI ER I REDIGERINGS-MODUS + if (getArguments() != null && getArguments().containsKey("edit_event")) { + eventToEdit = (CalendarEvent) getArguments().getSerializable("edit_event"); + prefillForm(eventToEdit); + btnSave.setText("Oppdater Hendelse"); + } else { + // Ny event: Standard tid (neste time) + startCal.add(Calendar.HOUR_OF_DAY, 1); + startCal.set(Calendar.MINUTE, 0); + endCal.setTime(startCal.getTime()); + endCal.add(Calendar.HOUR_OF_DAY, 1); + } - setupStandardSpinners(); updateRecurrenceSpinner(); updateUI(); @@ -97,8 +115,10 @@ public class CreateEventFragment extends Fragment { spinnerRecurrence.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { - String selected = parent.getItemAtPosition(position).toString(); + // Unngå å overskrive ved innlasting + if (eventToEdit != null && position == 0) return; + String selected = parent.getItemAtPosition(position).toString(); if (selected.equals("Egendefinert...")) { showCustomRecurrenceDialog(); } else if (selected.startsWith("Ikke gjenta")) { @@ -111,8 +131,89 @@ public class CreateEventFragment extends Fragment { }); } - private void setupStandardSpinners() { - // Må matche PHP switch-case nøyaktig + private void prefillForm(CalendarEvent event) { + etTitle.setText(event.getTitle()); + + // Rens beskrivelsen for #varsel tag + String cleanDesc = event.getDescription().replaceAll("#varsel:[\\d,]+", "").trim(); + etDesc.setText(cleanDesc); + etLocation.setText(event.getLocation()); + + // Dato-parsing + try { + SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); + SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); + + String start = event.getRawDate(); + if (start != null) { + if (start.length() == 10) { + // Heldags + switchAllDay.setChecked(true); + Date d = simpleFormat.parse(start); + startCal.setTime(d); + + // Sluttdato + if (event.getRawEndDate() != null) { + Date e = simpleFormat.parse(event.getRawEndDate()); + // Google sender sluttdato eksklusiv (dagen etter). Vi trekker fra 1 dag for visning. + Calendar c = Calendar.getInstance(); + c.setTime(e); + c.add(Calendar.DAY_OF_MONTH, -1); + endCal.setTime(c.getTime()); + } else { + endCal.setTime(d); + } + } else if (start.contains("T")) { + // Vanlig tid (kutt tidssone) + if (start.length() > 19) start = start.substring(0, 19); + startCal.setTime(isoFormat.parse(start)); + + String end = event.getRawEndDate(); + if (end != null && end.contains("T")) { + if (end.length() > 19) end = end.substring(0, 19); + endCal.setTime(isoFormat.parse(end)); + } else { + endCal.setTime(startCal.getTime()); + endCal.add(Calendar.HOUR_OF_DAY, 1); + } + } + } + + // Varsler - Finn tag i den rå beskrivelsen + if (event.getDescription() != null) { + Pattern p = Pattern.compile("#varsel:([\\d,]+)"); + Matcher m = p.matcher(event.getDescription()); + if (m.find()) { + String[] parts = m.group(1).split(","); + // Fjern alle sjekkmerker først + for (int i = 0; i < chipGroupReminders.getChildCount(); i++) { + ((Chip) chipGroupReminders.getChildAt(i)).setChecked(false); + } + // Sett nye + for (String part : parts) { + try { + int min = Integer.parseInt(part); + checkChipByMinutes(min); + } catch (NumberFormatException e) {} + } + } + } + + } catch (Exception e) { + Log.e("KBS_EDIT", "Feil ved prefill", e); + } + } + + private void checkChipByMinutes(int minutes) { + for (int i = 0; i < chipGroupReminders.getChildCount(); i++) { + Chip chip = (Chip) chipGroupReminders.getChildAt(i); + if ((int)chip.getTag() == minutes) { + chip.setChecked(true); + } + } + } + + private void setupCalendarSpinner() { String[] calendars = { "Felles", "Administrasjonen", @@ -121,11 +222,42 @@ public class CreateEventFragment extends Fragment { "Prosjektavdelingen" }; spinnerCalendar.setAdapter(new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, calendars)); + } - String[] reminders = {"Ingen varsling", "10 min før", "15 min før", "30 min før", "1 time før", "2 timer før", "24 timer før"}; - ArrayAdapter remAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, reminders); - spinnerReminder.setAdapter(remAdapter); - spinnerReminder.setSelection(2); // 15 min default + private void setupReminderChips() { + addChip("Ved start", 0); + addChip("5 min", 5); + addChip("10 min", 10); + addChip("15 min", 15); + addChip("30 min", 30); + addChip("1 time", 60); + addChip("2 timer", 120); + addChip("1 dag", 1440); + addChip("2 dager", 2880); + addChip("1 uke", 10080); + + // Marker 15 min som standard KUN for ny event + if (eventToEdit == null) checkChipByMinutes(15); + } + + private void addChip(String text, int minutes) { + Chip chip = new Chip(getContext()); + chip.setText(text); + chip.setTag(minutes); + chip.setCheckable(true); + chip.setClickable(true); + chipGroupReminders.addView(chip); + } + + private List getSelectedReminders() { + List selected = new ArrayList<>(); + for (int i = 0; i < chipGroupReminders.getChildCount(); i++) { + Chip chip = (Chip) chipGroupReminders.getChildAt(i); + if (chip.isChecked()) { + selected.add((Integer) chip.getTag()); + } + } + return selected; } private void updateRecurrenceSpinner() { @@ -353,19 +485,6 @@ public class CreateEventFragment extends Fragment { } } - private int getReminderMinutes() { - int pos = spinnerReminder.getSelectedItemPosition(); - switch (pos) { - case 1: return 10; - case 2: return 15; - case 3: return 30; - case 4: return 60; - case 5: return 120; - case 6: return 1440; - default: return 0; - } - } - private void submitEvent() { String title = etTitle.getText().toString().trim(); if (title.isEmpty()) { @@ -384,33 +503,41 @@ public class CreateEventFragment extends Fragment { sdf.format(startCal.getTime()), sdf.format(endCal.getTime()), getCalendarSlug(), - getReminderMinutes(), + getSelectedReminders(), isAllDay, selectedRRule ); - Toast.makeText(getContext(), "Oppretter...", Toast.LENGTH_SHORT).show(); + // HVIS VI REDIGERER, SETT ID + if (eventToEdit != null) { + req.id = eventToEdit.getId(); + } - // HER ER DEN NYE LOGIKKEN FOR Å LESE FEILMELDING FRA PHP - RetrofitClient.getApiService().createCalendarEvent(req).enqueue(new Callback() { + Toast.makeText(getContext(), eventToEdit != null ? "Oppdaterer..." : "Oppretter...", Toast.LENGTH_SHORT).show(); + + // Velg riktig API-kall (Create vs Update) + Call call; + if (eventToEdit != null) { + call = RetrofitClient.getApiService().updateCalendarEvent(req); + } else { + call = RetrofitClient.getApiService().createCalendarEvent(req); + } + + call.enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { - Toast.makeText(getContext(), "Hendelse opprettet!", Toast.LENGTH_LONG).show(); + Toast.makeText(getContext(), eventToEdit != null ? "Hendelse oppdatert!" : "Hendelse opprettet!", Toast.LENGTH_LONG).show(); Navigation.findNavController(getView()).navigateUp(); } else { - // Les feilmelding fra serveren (body) String errorMsg = "Ukjent feil"; try { if (response.errorBody() != null) { errorMsg = response.errorBody().string(); } - } catch (Exception e) { - errorMsg = "Kunne ikke lese feil: " + e.getMessage(); - } + } catch (Exception e) {} Log.e("KBS_ERROR", "Server svarte med feil: " + errorMsg); - // Vis en kortversjon til brukeren, eller hele hvis du debugger Toast.makeText(getContext(), "Feil (" + response.code() + "): Sjekk Logcat", Toast.LENGTH_LONG).show(); } } @@ -422,5 +549,4 @@ public class CreateEventFragment extends Fragment { } }); } - -} +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/CreateEventRequest.java b/app/src/main/java/com/kbs/kbsintranett/CreateEventRequest.java index 4698223..41d8ec2 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CreateEventRequest.java +++ b/app/src/main/java/com/kbs/kbsintranett/CreateEventRequest.java @@ -1,7 +1,10 @@ package com.kbs.kbsintranett; import com.google.gson.annotations.SerializedName; +import java.util.List; public class CreateEventRequest { + @SerializedName("id") + public String id; @SerializedName("title") public String title; @@ -9,7 +12,7 @@ public class CreateEventRequest { public String description; @SerializedName("location") - public String location; // NY + public String location; @SerializedName("start_time") public String startTime; @@ -20,23 +23,24 @@ public class CreateEventRequest { @SerializedName("calendar_type") public String calendarType; - @SerializedName("reminder_minutes") - public int reminderMinutes; + @SerializedName("reminders") + public List reminders; // Liste, ikke int @SerializedName("is_all_day") - public boolean isAllDay; // NY + public boolean isAllDay; @SerializedName("recurrence") - public String recurrence; // NY (RRULE) + public String recurrence; - public CreateEventRequest(String title, String description, String location, String startTime, String endTime, String calendarType, int reminderMinutes, boolean isAllDay, String recurrence) { + // Oppdatert konstruktør som tar imot List + public CreateEventRequest(String title, String description, String location, String startTime, String endTime, String calendarType, List reminders, boolean isAllDay, String recurrence) { this.title = title; this.description = description; this.location = location; this.startTime = startTime; this.endTime = endTime; this.calendarType = calendarType; - this.reminderMinutes = reminderMinutes; + this.reminders = reminders; this.isAllDay = isAllDay; this.recurrence = recurrence; } diff --git a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java index 241b2e0..3d9be59 100644 --- a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java @@ -160,7 +160,8 @@ public class HomeFragment extends Fragment { })); } else { List errorList = new ArrayList<>(); - errorList.add(new CalendarEvent("Kunne ikke laste kalender", "Sjekk nettverk", "!", "OBS")); + // FIKSET HER: La til "" som 5. argument (location) + errorList.add(new CalendarEvent("Kunne ikke laste kalender", "Sjekk nettverk", "!", "OBS", "")); recyclerView.setAdapter(new CalendarAdapter(errorList, null)); } } diff --git a/app/src/main/java/com/kbs/kbsintranett/NotificationWorker.java b/app/src/main/java/com/kbs/kbsintranett/NotificationWorker.java index 08b46f3..72fc83b 100644 --- a/app/src/main/java/com/kbs/kbsintranett/NotificationWorker.java +++ b/app/src/main/java/com/kbs/kbsintranett/NotificationWorker.java @@ -4,6 +4,7 @@ import android.app.AlarmManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Build; import android.util.Log; import androidx.annotation.NonNull; @@ -18,6 +19,7 @@ import retrofit2.Response; public class NotificationWorker extends Worker { private static final String TAG = "NotificationWorker"; + private static final String PREFS_NAME = "kbs_alarm_history"; public NotificationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); @@ -28,20 +30,14 @@ public class NotificationWorker extends Worker { public Result doWork() { try { Response> response = RetrofitClient.getApiService().getCalendarEvents().execute(); - if (response.isSuccessful() && response.body() != null) { scheduleAlarms(response.body()); return Result.success(); } else { - // Ved klientfeil (4xx), stopp retry-loop - if (response.code() >= 400 && response.code() < 500) { - Log.w(TAG, "Kunne ikke hente kalender. Kode: " + response.code()); - return Result.failure(); - } + if (response.code() >= 400 && response.code() < 500) return Result.failure(); return Result.retry(); } } catch (IOException e) { - Log.e(TAG, "Nettverksfeil under kalendersjekk", e); return Result.retry(); } } @@ -49,18 +45,15 @@ public class NotificationWorker extends Worker { private void scheduleAlarms(List events) { Context context = getApplicationContext(); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if (!alarmManager.canScheduleExactAlarms()) { - return; // Mangler rettigheter, kan ikke sette alarm - } - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) return; long now = System.currentTimeMillis(); SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); - long catchUpWindow = now - (30 * 60 * 1000L); // 30 min bakover - long futureWindow = now + (24 * 60 * 60 * 1000L); // 24 timer fremover + long catchUpWindow = now - (30 * 60 * 1000L); + long futureWindow = now + (30 * 24 * 60 * 60 * 1000L); // 30 dager frem for (CalendarEvent event : events) { try { @@ -72,50 +65,54 @@ public class NotificationWorker extends Worker { if (raw.length() > 19) raw = raw.substring(0, 19); eventDate = isoFormat.parse(raw); } - if (eventDate == null) continue; - int minutesBefore = event.getReminderMinutes(); - if (minutesBefore <= 0) continue; + // Loop gjennom alle varsler for denne hendelsen + for (int minutesBefore : event.getReminders()) { + if (minutesBefore < 0) continue; // 0 betyr nå "ved start", negative ignoreres - long triggerTime = eventDate.getTime() - (minutesBefore * 60 * 1000L); + long triggerTime = eventDate.getTime() - (minutesBefore * 60 * 1000L); - // Sjekk om alarmen er innenfor tidsvinduet - if (triggerTime > catchUpWindow && triggerTime < futureWindow) { + // Unik nøkkel for denne alarmen: EventID + Tidspunkt + String alarmKey = "alarm_" + event.getId() + "_" + triggerTime; - // Catch-up: Hvis tiden har passert, fyr av umiddelbart - if (triggerTime < now) { - triggerTime = now + 1000; + // Sjekk om vi allerede har fyrt denne alarmen + if (prefs.getBoolean(alarmKey, false)) { + continue; // Allerede håndtert } - int alarmId = (event.getTitle() + event.getRawDate()).hashCode(); + if (triggerTime > catchUpWindow && triggerTime < futureWindow) { + if (triggerTime < now) { + triggerTime = now + 1000; // Catch-up + } - Intent intent = new Intent(context, AlarmReceiver.class); - intent.putExtra("TITLE", event.getTitle()); - String timeStr = new SimpleDateFormat("HH:mm", Locale.getDefault()).format(eventDate); - intent.putExtra("MESSAGE", "Starter kl " + timeStr); - intent.putExtra("ID", alarmId); + int alarmId = alarmKey.hashCode(); + Intent intent = new Intent(context, AlarmReceiver.class); + intent.putExtra("TITLE", event.getTitle()); + String timeStr = new SimpleDateFormat("HH:mm", Locale.getDefault()).format(eventDate); + intent.putExtra("MESSAGE", "Starter kl " + timeStr); + intent.putExtra("ID", alarmId); - PendingIntent pendingIntent = PendingIntent.getBroadcast( - context, - alarmId, - intent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE - ); + PendingIntent pendingIntent = PendingIntent.getBroadcast( + context, alarmId, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE + ); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); - } else { - alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); + } else { + alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); + } + + // Marker som satt + prefs.edit().putBoolean(alarmKey, true).apply(); + Log.i(TAG, "Satt alarm: " + event.getTitle() + " (" + minutesBefore + "m før)"); } - - // Logger kun når en alarm faktisk blir satt/oppdatert - Log.i(TAG, "Alarm satt for: " + event.getTitle() + " (" + new Date(triggerTime) + ")"); } - } catch (Exception e) { - Log.e(TAG, "Feil ved behandling av event", e); + Log.e(TAG, "Feil", e); } } + + // Rensk opp gamle nøkler (valgfritt, for å spare plass over tid) } } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java b/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java index 8ea0fb4..983eedd 100644 --- a/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java +++ b/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java @@ -68,4 +68,11 @@ public interface WordPressApiService { @GET("wp-json/kbs/v1/lookup-id") Call lookupPageId(@Query("url") String url); + @POST("wp-json/kbs/v1/calendar/update") + Call updateCalendarEvent(@Body CreateEventRequest request); + + @POST("wp-json/kbs/v1/calendar/delete") + Call deleteCalendarEvent(@Body CreateEventRequest request); // Sender kun ID og cal_type + + } \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_calendar_details.xml b/app/src/main/res/layout/bottom_sheet_calendar_details.xml index 154d71c..096346b 100644 --- a/app/src/main/res/layout/bottom_sheet_calendar_details.xml +++ b/app/src/main/res/layout/bottom_sheet_calendar_details.xml @@ -6,7 +6,6 @@ android:orientation="vertical" android:padding="24dp" android:background="@android:color/white"> - + android:layout_marginBottom="24dp"/> + + + +