Før tilgangskontroll til kalendre

This commit is contained in:
ErolHaagenrud 2025-12-17 08:04:08 +01:00
parent fb9749ba3f
commit 3b2d19bd0b
10 changed files with 419 additions and 187 deletions

View file

@ -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 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 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"));
Date startDate = apiFormat.parse(event.getRawDate());
long startMillis = startDate.getTime();
long endMillis = startMillis + (60 * 60 * 1000); // Default 1 time hvis slutt mangler
if (event.getRawEndDate() != null && !event.getRawEndDate().isEmpty()) {
Date endDate = apiFormat.parse(event.getRawEndDate());
endMillis = endDate.getTime();
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();
}
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);
private void deleteEvent() {
// Vi 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 ID hvis vi sender riktig kalendertype.
// For antar vi "Felles" eller looper i backend. I V11.3 PHP scriptet sletter den basert ID i valgt kalender.
startActivity(intent);
CreateEventRequest req = new CreateEventRequest(
"", "", "", "", "", "Felles", new ArrayList<>(), false, ""
);
req.id = event.getId();
} catch (Exception e) {
e.printStackTrace();
RetrofitClient.getApiService().deleteCalendarEvent(req).enqueue(new Callback<JsonElement>() {
@Override
public void onResponse(Call<JsonElement> call, Response<JsonElement> 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();
}
}
@Override
public void onFailure(Call<JsonElement> call, Throwable t) {
Toast.makeText(getContext(), "Nettverksfeil", Toast.LENGTH_SHORT).show();
}
});
}
}

View file

@ -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) ---
// 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<Integer> 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<Integer> 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; }
}

View file

@ -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 i CalendarEvent (se fil 1)
event.setReminderMinutes(0);
formatEventForUI(event);
deviceEvents.add(event);

View file

@ -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)
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() {
// 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 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<String> 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<Integer> getSelectedReminders() {
List<Integer> 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<JsonElement>() {
Toast.makeText(getContext(), eventToEdit != null ? "Oppdaterer..." : "Oppretter...", Toast.LENGTH_SHORT).show();
// Velg riktig API-kall (Create vs Update)
Call<JsonElement> call;
if (eventToEdit != null) {
call = RetrofitClient.getApiService().updateCalendarEvent(req);
} else {
call = RetrofitClient.getApiService().createCalendarEvent(req);
}
call.enqueue(new Callback<JsonElement>() {
@Override
public void onResponse(Call<JsonElement> call, Response<JsonElement> 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 {
}
});
}
}

View file

@ -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<Integer> 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<Integer>
public CreateEventRequest(String title, String description, String location, String startTime, String endTime, String calendarType, List<Integer> 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;
}

View file

@ -160,7 +160,8 @@ public class HomeFragment extends Fragment {
}));
} else {
List<CalendarEvent> 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));
}
}

View file

@ -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<List<CalendarEvent>> 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<CalendarEvent> 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,24 +65,28 @@ 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 "ved start", negative ignoreres
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
}
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);
@ -97,10 +94,7 @@ public class NotificationWorker extends Worker {
intent.putExtra("ID", alarmId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
context,
alarmId,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
context, alarmId, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -109,13 +103,16 @@ public class NotificationWorker extends Worker {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent);
}
// Logger kun når en alarm faktisk blir satt/oppdatert
Log.i(TAG, "Alarm satt for: " + event.getTitle() + " (" + new Date(triggerTime) + ")");
// Marker som satt
prefs.edit().putBoolean(alarmKey, true).apply();
Log.i(TAG, "Satt alarm: " + event.getTitle() + " (" + minutesBefore + "m før)");
}
}
} catch (Exception e) {
Log.e(TAG, "Feil", e);
}
}
} catch (Exception e) {
Log.e(TAG, "Feil ved behandling av event", e);
}
}
// Rensk opp gamle nøkler (valgfritt, for å spare plass over tid)
}
}

View file

@ -68,4 +68,11 @@ public interface WordPressApiService {
@GET("wp-json/kbs/v1/lookup-id")
Call<JsonObject> lookupPageId(@Query("url") String url);
@POST("wp-json/kbs/v1/calendar/update")
Call<JsonElement> updateCalendarEvent(@Body CreateEventRequest request);
@POST("wp-json/kbs/v1/calendar/delete")
Call<JsonElement> deleteCalendarEvent(@Body CreateEventRequest request); // Sender kun ID og cal_type
}

View file

@ -6,7 +6,6 @@
android:orientation="vertical"
android:padding="24dp"
android:background="@android:color/white">
<TextView
android:id="@+id/sheet_title"
android:layout_width="match_parent"
@ -45,15 +44,43 @@
android:text="Beskrivelse..."
android:textSize="14sp"
android:textColor="#555"
android:layout_marginBottom="32dp"/>
android:layout_marginBottom="24dp"/>
<!-- ADMIN KNAPPER (Vises kun for admin) -->
<LinearLayout
android:id="@+id/layout_admin_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone"
android:layout_marginTop="16dp">
<Button
android:id="@+id/btn_delete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Slett"
android:backgroundTint="#D32F2F"
android:textColor="#FFF"
android:layout_marginEnd="8dp"/>
<Button
android:id="@+id/btn_edit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Endre"
android:backgroundTint="@color/kbs_logo_blue"
android:textColor="#FFF"
android:layout_marginStart="8dp"/>
</LinearLayout>
<!-- Gammel knapp (skjult) -->
<Button
android:id="@+id/btn_add_to_calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Lagre i min kalender / Varsle meg"
android:backgroundTint="@color/kbs_logo_blue"
android:textColor="@color/white"
android:padding="12dp"/>
android:visibility="gone"/>
</LinearLayout>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF">
@ -20,6 +21,7 @@
android:layout_marginBottom="24dp"
android:textColor="#333"/>
<!-- TITTEL -->
<EditText
android:id="@+id/et_title"
android:layout_width="match_parent"
@ -30,6 +32,7 @@
android:background="@android:drawable/edit_text"
android:layout_marginBottom="16dp"/>
<!-- BESKRIVELSE -->
<EditText
android:id="@+id/et_desc"
android:layout_width="match_parent"
@ -42,6 +45,7 @@
android:background="@android:drawable/edit_text"
android:layout_marginBottom="16dp"/>
<!-- STED -->
<EditText
android:id="@+id/et_location"
android:layout_width="match_parent"
@ -52,6 +56,22 @@
android:background="@android:drawable/edit_text"
android:layout_marginBottom="16dp"/>
<!-- KALENDER VALG -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Velg Kalender:"
android:textSize="14sp"
android:textColor="#666"/>
<Spinner
android:id="@+id/spinner_calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:padding="12dp"/>
<!-- HELE DAGEN -->
<Switch
android:id="@+id/switch_all_day"
android:layout_width="match_parent"
@ -59,7 +79,7 @@
android:text="Hele dagen"
android:layout_marginBottom="16dp"/>
<!-- Start Dato/Tid -->
<!-- START DATO/TID -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -83,7 +103,7 @@
android:layout_marginStart="4dp"/>
</LinearLayout>
<!-- Slutt Dato/Tid -->
<!-- SLUTT DATO/TID -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -116,20 +136,7 @@
android:textStyle="bold"
android:layout_marginBottom="24dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Velg Kalender:"
android:textSize="14sp"
android:textColor="#666"/>
<Spinner
android:id="@+id/spinner_calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:padding="12dp"/>
<!-- GJENTAKELSE -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -144,19 +151,31 @@
android:layout_marginBottom="16dp"
android:padding="12dp"/>
<!-- NYTT: VARSLING MED CHIPS (MULTIVALG) -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Varsling:"
android:text="Varsling (Velg en eller flere):"
android:textSize="14sp"
android:textColor="#666"/>
android:textColor="#666"
android:layout_marginBottom="8dp"/>
<Spinner
android:id="@+id/spinner_reminder"
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:padding="12dp"/>
android:scrollbars="none"
android:layout_marginBottom="32dp">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chip_group_reminders"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:singleLine="true"
app:selectionRequired="false">
<!-- Chips legges til programmatisk i Java -->
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<Button
android:id="@+id/btn_save_event"
@ -167,4 +186,5 @@
android:textColor="#FFFFFF"/>
</LinearLayout>
</ScrollView>