Før individuelle avtaler

This commit is contained in:
ErolHaagenrud 2025-12-19 09:56:51 +01:00
parent 864820212f
commit 93092f33d9
11 changed files with 399 additions and 168 deletions

View file

@ -7,56 +7,38 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Build; import android.os.Build;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import java.io.IOException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import retrofit2.Response;
public class NotificationWorker extends Worker { public class AlarmScheduler {
private static final String TAG = "NotificationWorker";
private static final String TAG = "AlarmScheduler";
private static final String PREFS_NAME = "kbs_alarm_history"; private static final String PREFS_NAME = "kbs_alarm_history";
public NotificationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { /**
super(context, workerParams); * Denne metoden går gjennom en liste hendelser og setter alarmer for dem.
} */
public static void scheduleAlarmsForEvents(Context context, List<CalendarEvent> events) {
@NonNull
@Override
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 {
if (response.code() >= 400 && response.code() < 500) return Result.failure();
return Result.retry();
}
} catch (IOException e) {
return Result.retry();
}
}
private void scheduleAlarms(List<CalendarEvent> events) {
Context context = getApplicationContext();
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// Sjekk rettigheter for Android 12+ (Exact Alarm)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) {
Log.w(TAG, "Mangler rettighet til å sette nøyaktige alarmer.");
return;
}
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) return;
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
long catchUpWindow = now - (30 * 60 * 1000L); // Vi ser etter hendelser 30 dager frem i tid
long futureWindow = now + (30 * 24 * 60 * 60 * 1000L); // 30 dager frem long futureWindow = now + (30L * 24 * 60 * 60 * 1000L);
for (CalendarEvent event : events) { for (CalendarEvent event : events) {
try { try {
// Hopp over hvis ingen dato eller heldags (uten tidspunkt)
if (event.getRawDate() == null || event.getRawDate().length() == 10) continue; if (event.getRawDate() == null || event.getRawDate().length() == 10) continue;
Date eventDate = null; Date eventDate = null;
@ -65,28 +47,26 @@ public class NotificationWorker extends Worker {
if (raw.length() > 19) raw = raw.substring(0, 19); if (raw.length() > 19) raw = raw.substring(0, 19);
eventDate = isoFormat.parse(raw); eventDate = isoFormat.parse(raw);
} }
if (eventDate == null) continue; if (eventDate == null) continue;
// Loop gjennom alle varsler for denne hendelsen // Loop gjennom alle varsler (f.eks. 15 min før, 60 min før)
for (int minutesBefore : event.getReminders()) { for (int minutesBefore : event.getReminders()) {
if (minutesBefore < 0) continue; // 0 betyr "ved start", negative ignoreres if (minutesBefore < 0) continue;
long triggerTime = eventDate.getTime() - (minutesBefore * 60 * 1000L); long triggerTime = eventDate.getTime() - (minutesBefore * 60 * 1000L);
// Unik nøkkel for denne alarmen: EventID + Tidspunkt
String alarmKey = "alarm_" + event.getId() + "_" + triggerTime; String alarmKey = "alarm_" + event.getId() + "_" + triggerTime;
// Sjekk om vi allerede har fyrt denne alarmen // Hvis tidspunktet er i fremtiden (og innenfor vinduet)
if (triggerTime > now && triggerTime < futureWindow) {
// Sjekk om vi allerede har satt denne alarmen for å unngå dobbeltarbeid
if (prefs.getBoolean(alarmKey, false)) { if (prefs.getBoolean(alarmKey, false)) {
continue; // Allerede håndtert continue;
} }
if (triggerTime > catchUpWindow && triggerTime < futureWindow) { int alarmId = alarmKey.hashCode(); // Unik ID basert hendelse+tid
if (triggerTime < now) {
triggerTime = now + 1000; // Catch-up
}
int alarmId = alarmKey.hashCode();
Intent intent = new Intent(context, AlarmReceiver.class); Intent intent = new Intent(context, AlarmReceiver.class);
intent.putExtra("TITLE", event.getTitle()); intent.putExtra("TITLE", event.getTitle());
String timeStr = new SimpleDateFormat("HH:mm", Locale.getDefault()).format(eventDate); String timeStr = new SimpleDateFormat("HH:mm", Locale.getDefault()).format(eventDate);
@ -94,9 +74,13 @@ public class NotificationWorker extends Worker {
intent.putExtra("ID", alarmId); intent.putExtra("ID", alarmId);
PendingIntent pendingIntent = PendingIntent.getBroadcast( PendingIntent pendingIntent = PendingIntent.getBroadcast(
context, alarmId, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE context,
alarmId,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
); );
// Sett alarmen
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent);
} else { } else {
@ -105,14 +89,12 @@ public class NotificationWorker extends Worker {
// Marker som satt // Marker som satt
prefs.edit().putBoolean(alarmKey, true).apply(); prefs.edit().putBoolean(alarmKey, true).apply();
Log.i(TAG, "Satt alarm: " + event.getTitle() + " (" + minutesBefore + "m før)"); Log.d(TAG, "Alarm satt for " + event.getTitle() + " om " + minutesBefore + " min.");
} }
} }
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Feil", e); Log.e(TAG, "Feil ved setting av alarm", e);
} }
} }
// Rensk opp gamle nøkler (valgfritt, for å spare plass over tid)
} }
} }

View file

@ -2,6 +2,7 @@
package com.kbs.kbsintranett; package com.kbs.kbsintranett;
import android.util.Log; import android.util.Log;
import com.google.gson.JsonElement;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
import retrofit2.Response; import retrofit2.Response;
@ -57,6 +58,12 @@ public class AuthRepository {
// Lagre listen over skrivbare kalendere // Lagre listen over skrivbare kalendere
UserManager.getInstance().setWriteableCalendars(response.body().writeableCalendars); UserManager.getInstance().setWriteableCalendars(response.body().writeableCalendars);
// NYTT: Hvis vi har en ventende FCM-token, send den som vi er logget inn
String pendingToken = UserManager.getInstance().getFcmToken();
if (pendingToken != null && !pendingToken.isEmpty()) {
updateDeviceToken(pendingToken);
}
callback.onSuccess(role); callback.onSuccess(role);
} else { } else {
@ -72,4 +79,33 @@ public class AuthRepository {
} }
}); });
} }
/**
* Sender FCM-token til WordPress for å registrere enheten for push-varsler.
*/
public static void updateDeviceToken(String token) {
if (!UserManager.getInstance().isLoggedIn()) {
// Hvis ikke logget inn, bare lagre den til senere
UserManager.getInstance().setFcmToken(token);
return;
}
// Send til server
RegisterDeviceRequest request = new RegisterDeviceRequest(token);
RetrofitClient.getApiService().registerDevice(request).enqueue(new Callback<JsonElement>() {
@Override
public void onResponse(Call<JsonElement> call, Response<JsonElement> response) {
if (response.isSuccessful()) {
Log.d(TAG, "FCM Token registrert på server OK.");
} else {
Log.e(TAG, "Feil ved registrering av FCM Token. Kode: " + response.code());
}
}
@Override
public void onFailure(Call<JsonElement> call, Throwable t) {
Log.e(TAG, "Nettverksfeil ved sending av FCM token", t);
}
});
}
} }

View file

@ -8,6 +8,7 @@ import java.util.List;
public class CalendarEvent implements Serializable { public class CalendarEvent implements Serializable {
@SerializedName("id") @SerializedName("id")
private String id; private String id;
@SerializedName("title") @SerializedName("title")
private String title; private String title;
@ -66,6 +67,8 @@ public class CalendarEvent implements Serializable {
public void setCalendarColor(String color) { this.calendarColor = color; } public void setCalendarColor(String color) { this.calendarColor = color; }
// --- KOMPATIBILITETS-METODER --- // --- KOMPATIBILITETS-METODER ---
// Denne brukes for enkle varsler
public void setReminderMinutes(int minutes) { public void setReminderMinutes(int minutes) {
this.reminders = new ArrayList<>(); this.reminders = new ArrayList<>();
if (minutes > 0) { if (minutes > 0) {
@ -73,6 +76,12 @@ public class CalendarEvent implements Serializable {
} }
} }
// NY METODE (Den som manglet og forårsaket krasj)
// Lar oss sette hele listen med varsler en gang
public void setRemindersList(List<Integer> reminders) {
this.reminders = reminders != null ? new ArrayList<>(reminders) : new ArrayList<>();
}
public int getReminderMinutes() { public int getReminderMinutes() {
if (reminders != null && !reminders.isEmpty()) { if (reminders != null && !reminders.isEmpty()) {
return reminders.get(0); return reminders.get(0);

View file

@ -3,6 +3,7 @@ package com.kbs.kbsintranett;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.DatePickerDialog; import android.app.DatePickerDialog;
import android.app.TimePickerDialog; import android.app.TimePickerDialog;
import android.content.Context;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.os.Bundle; import android.os.Bundle;
@ -33,8 +34,6 @@ import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
import retrofit2.Response; import retrofit2.Response;
@ -117,7 +116,6 @@ public class CreateEventFragment extends Fragment {
spinnerRecurrence.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { spinnerRecurrence.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override @Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
// Unngå å overskrive ved innlasting hvis vi allerede har satt en verdi
if (eventToEdit != null && position == 0 && selectedRRule != null) return; if (eventToEdit != null && position == 0 && selectedRRule != null) return;
String selected = parent.getItemAtPosition(position).toString(); String selected = parent.getItemAtPosition(position).toString();
@ -140,7 +138,6 @@ public class CreateEventFragment extends Fragment {
etDesc.setText(cleanDesc); etDesc.setText(cleanDesc);
etLocation.setText(event.getLocation()); etLocation.setText(event.getLocation());
// --- FIKS 404 FEIL VED OPPDATERING ---
ArrayAdapter<String> adapter = (ArrayAdapter<String>) spinnerCalendar.getAdapter(); ArrayAdapter<String> adapter = (ArrayAdapter<String>) spinnerCalendar.getAdapter();
if (adapter != null) { if (adapter != null) {
int position = adapter.getPosition(event.getCalendarName()); int position = adapter.getPosition(event.getCalendarName());
@ -149,7 +146,6 @@ public class CreateEventFragment extends Fragment {
} }
} }
spinnerCalendar.setEnabled(false); spinnerCalendar.setEnabled(false);
// -------------------------------------
try { try {
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
@ -210,17 +206,14 @@ public class CreateEventFragment extends Fragment {
} }
} }
// --- NY LOGIKK FOR FARGER I SPINNER ---
private String getCalendarColor(String name) { private String getCalendarColor(String name) {
// Matcher fargene i PHP-config (V12.6)
switch (name) { switch (name) {
case "Felles": return "#0069B3"; // KBS Blå case "Felles": return "#0069B3";
case "Administrasjonen": return "#607D8B"; // Blue Grey case "Administrasjonen": return "#607D8B";
case "Serviceavdelingen": return "#E65100"; // Orange case "Serviceavdelingen": return "#E65100";
case "Automasjonsavdelingen": return "#2E7D32"; // Green case "Automasjonsavdelingen": return "#2E7D32";
case "Prosjektavdelingen": return "#7B1FA2"; // Purple case "Prosjektavdelingen": return "#7B1FA2";
default: return "#888888"; // Grå fallback default: return "#888888";
} }
} }
@ -228,41 +221,27 @@ public class CreateEventFragment extends Fragment {
List<String> calendars = UserManager.getInstance().getWriteableCalendars(); List<String> calendars = UserManager.getInstance().getWriteableCalendars();
if (calendars.isEmpty()) calendars.add("Felles"); if (calendars.isEmpty()) calendars.add("Felles");
// Vi bruker en Custom Adapter for å styre farger
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(), android.R.layout.simple_spinner_item, calendars) { ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(), android.R.layout.simple_spinner_item, calendars) {
// getView: Dette er det som vises i selve boksen når noe er valgt
@NonNull @NonNull
@Override @Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
TextView view = (TextView) super.getView(position, convertView, parent); TextView view = (TextView) super.getView(position, convertView, parent);
String calName = getItem(position); String calName = getItem(position);
String colorHex = getCalendarColor(calName); String colorHex = getCalendarColor(calName);
// Sett bakgrunnsfarge lik kalenderfarge
view.setBackgroundColor(Color.parseColor(colorHex)); view.setBackgroundColor(Color.parseColor(colorHex));
// Hvit tekst for kontrast
view.setTextColor(Color.WHITE); view.setTextColor(Color.WHITE);
view.setTypeface(null, Typeface.BOLD); view.setTypeface(null, Typeface.BOLD);
return view; return view;
} }
// getDropDownView: Dette er listen som popper opp
@Override @Override
public View getDropDownView(int position, View convertView, ViewGroup parent) { public View getDropDownView(int position, View convertView, ViewGroup parent) {
TextView view = (TextView) super.getDropDownView(position, convertView, parent); TextView view = (TextView) super.getDropDownView(position, convertView, parent);
String calName = getItem(position); String calName = getItem(position);
String colorHex = getCalendarColor(calName); String colorHex = getCalendarColor(calName);
// Her holder vi bakgrunnen hvit, men farger teksten
view.setBackgroundColor(Color.WHITE); view.setBackgroundColor(Color.WHITE);
view.setTextColor(Color.parseColor(colorHex)); view.setTextColor(Color.parseColor(colorHex));
view.setTypeface(null, Typeface.BOLD); view.setTypeface(null, Typeface.BOLD);
return view; return view;
} }
}; };
@ -270,7 +249,6 @@ public class CreateEventFragment extends Fragment {
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerCalendar.setAdapter(adapter); spinnerCalendar.setAdapter(adapter);
} }
// --------------------------------------
private void setupReminderChips() { private void setupReminderChips() {
addChip("Ved start", 0); addChip("Ved start", 0);
@ -539,16 +517,15 @@ public class CreateEventFragment extends Fragment {
String format = isAllDay ? "yyyy-MM-dd" : "yyyy-MM-dd'T'HH:mm"; String format = isAllDay ? "yyyy-MM-dd" : "yyyy-MM-dd'T'HH:mm";
SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault()); SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault());
String startTimeStr = sdf.format(startCal.getTime());
String endTimeStr = sdf.format(endCal.getTime());
String location = etLocation.getText().toString();
String description = etDesc.getText().toString();
List<Integer> reminders = getSelectedReminders();
CreateEventRequest req = new CreateEventRequest( CreateEventRequest req = new CreateEventRequest(
title, title, description, location, startTimeStr, endTimeStr,
etDesc.getText().toString(), getCalendarSlug(), reminders, isAllDay, selectedRRule
etLocation.getText().toString(),
sdf.format(startCal.getTime()),
sdf.format(endCal.getTime()),
getCalendarSlug(),
getSelectedReminders(),
isAllDay,
selectedRRule
); );
if (eventToEdit != null) { if (eventToEdit != null) {
@ -557,6 +534,10 @@ public class CreateEventFragment extends Fragment {
Toast.makeText(getContext(), eventToEdit != null ? "Oppdaterer..." : "Oppretter...", Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), eventToEdit != null ? "Oppdaterer..." : "Oppretter...", Toast.LENGTH_SHORT).show();
// **VIKTIG ENDRING:**
// Vi henter context her (mens Fragmentet lever) for å bruke den i bakgrunnstråden
final Context appContext = requireContext().getApplicationContext();
Call<JsonElement> call; Call<JsonElement> call;
if (eventToEdit != null) { if (eventToEdit != null) {
call = RetrofitClient.getApiService().updateCalendarEvent(req); call = RetrofitClient.getApiService().updateCalendarEvent(req);
@ -569,25 +550,38 @@ public class CreateEventFragment extends Fragment {
public void onResponse(Call<JsonElement> call, Response<JsonElement> response) { public void onResponse(Call<JsonElement> call, Response<JsonElement> response) {
if (response.isSuccessful()) { if (response.isSuccessful()) {
Toast.makeText(getContext(), eventToEdit != null ? "Hendelse oppdatert!" : "Hendelse opprettet!", Toast.LENGTH_LONG).show(); Toast.makeText(getContext(), eventToEdit != null ? "Hendelse oppdatert!" : "Hendelse opprettet!", Toast.LENGTH_LONG).show();
// Start oppdatering av alarmer i bakgrunnen
fetchCalendarAndSchedule(appContext);
Navigation.findNavController(getView()).navigateUp(); Navigation.findNavController(getView()).navigateUp();
} else { } else {
String errorMsg = "Ukjent feil"; Toast.makeText(getContext(), "Feil (" + response.code() + ")", Toast.LENGTH_LONG).show();
try {
if (response.errorBody() != null) {
errorMsg = response.errorBody().string();
}
} catch (Exception e) {}
Log.e("KBS_ERROR", "Server svarte med feil: " + errorMsg);
Toast.makeText(getContext(), "Feil (" + response.code() + "): Sjekk Logcat", Toast.LENGTH_LONG).show();
} }
} }
@Override @Override
public void onFailure(Call<JsonElement> call, Throwable t) { public void onFailure(Call<JsonElement> call, Throwable t) {
Log.e("KBS_ERROR", "Nettverksfeil", t); Toast.makeText(getContext(), "Nettverksfeil", Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "Nettverksfeil: " + t.getMessage(), Toast.LENGTH_SHORT).show();
} }
}); });
} }
// Oppdatert metode som tar imot context som parameter
private void fetchCalendarAndSchedule(Context context) {
new Thread(() -> {
try {
// Sjekk en ekstra gang for å være sikker
if (context == null) return;
Response<List<CalendarEvent>> response = RetrofitClient.getApiService().getCalendarEvents().execute();
if (response.isSuccessful() && response.body() != null) {
// Bruk contexten vi fikk tilsendt, ikke getContext() som kan være null
AlarmScheduler.scheduleAlarmsForEvents(context, response.body());
}
} catch (Exception e) {
Log.e("CreateEvent", "Kunne ikke oppdatere alarmer", e);
}
}).start();
}
} }

View file

@ -22,15 +22,12 @@ import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
import retrofit2.Response; import retrofit2.Response;
@ -55,7 +52,7 @@ public class HomeFragment extends Fragment {
} }
} }
); );
startNotificationWorker(); // GAMMEL METODE FJERNET HERFRA (startNotificationWorker)
} }
@Nullable @Nullable
@ -282,11 +279,4 @@ public class HomeFragment extends Fragment {
}); });
recyclerView.setAdapter(adapter); recyclerView.setAdapter(adapter);
} }
private void startNotificationWorker() {
PeriodicWorkRequest notifRequest =
new PeriodicWorkRequest.Builder(NotificationWorker.class, 15, TimeUnit.MINUTES)
.build();
WorkManager.getInstance(requireContext()).enqueue(notifRequest);
}
} }

View file

@ -1,6 +1,5 @@
package com.kbs.kbsintranett; package com.kbs.kbsintranett;
import android.Manifest;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.NotificationChannel; import android.app.NotificationChannel;
@ -22,9 +21,6 @@ import androidx.core.content.ContextCompat;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.NavigationUI; import androidx.navigation.ui.NavigationUI;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import com.google.android.gms.auth.api.signin.GoogleSignIn; import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount; import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
@ -32,8 +28,6 @@ import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions; import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
public static final String GOOGLE_WEB_CLIENT_ID = "738325360287-cidl3plnqv9ei74vm9vm5muustj6eenb.apps.googleusercontent.com"; // Bytt med din egen hvis denne er feil public static final String GOOGLE_WEB_CLIENT_ID = "738325360287-cidl3plnqv9ei74vm9vm5muustj6eenb.apps.googleusercontent.com"; // Bytt med din egen hvis denne er feil
@ -60,10 +54,8 @@ public class MainActivity extends AppCompatActivity {
if (bottomNav != null) { if (bottomNav != null) {
NavigationUI.setupWithNavController(bottomNav, navController); NavigationUI.setupWithNavController(bottomNav, navController);
// --- NYTT: Håndter "Reselection" (Klikk fanen man allerede er i) --- // Håndter "Reselection" (Klikk fanen man allerede er i)
bottomNav.setOnItemReselectedListener(item -> { bottomNav.setOnItemReselectedListener(item -> {
// Dette fjerner alt som ligger "oppå" hovedsiden i stabelen.
// F.eks: Hjem -> Kalender Full -> (Klikk Hjem) -> Hjem
navController.popBackStack(item.getItemId(), false); navController.popBackStack(item.getItemId(), false);
}); });
} }
@ -94,7 +86,7 @@ public class MainActivity extends AppCompatActivity {
checkNotificationPermission(); checkNotificationPermission();
checkExactAlarmPermission(); checkExactAlarmPermission();
scheduleCalendarWork(); // GAMMEL METODE FJERNET HERFRA (NotificationWorker)
// --- 3. AUTENTISERING --- // --- 3. AUTENTISERING ---
checkLoginState(); checkLoginState();
@ -174,8 +166,8 @@ public class MainActivity extends AppCompatActivity {
private void checkNotificationPermission() { private void checkNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); requestPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS);
} }
} }
} }
@ -197,15 +189,4 @@ public class MainActivity extends AppCompatActivity {
} }
} }
} }
private void scheduleCalendarWork() {
PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(NotificationWorker.class, 15, TimeUnit.MINUTES)
.build();
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"KbsCalendarWork",
ExistingPeriodicWorkPolicy.UPDATE,
workRequest
);
}
} }

View file

@ -15,51 +15,47 @@ import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage; import com.google.firebase.messaging.RemoteMessage;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class MyFirebaseMessagingService extends FirebaseMessagingService { public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "FCMService"; private static final String TAG = "FCMService";
private static final String CHANNEL_ID = "kbs_calendar_channel"; // Samme kanal som før private static final String CHANNEL_ID = "kbs_calendar_channel";
/**
* Kalles når en ny token genereres (f.eks. ved ny installasjon).
* Denne tokenen sendes til WordPress-backend slik at serveren vet hvem den skal sende til.
*/
@Override @Override
public void onNewToken(@NonNull String token) { public void onNewToken(@NonNull String token) {
super.onNewToken(token); super.onNewToken(token);
Log.d(TAG, "Ny FCM Token: " + token); Log.d(TAG, "Ny FCM Token: " + token);
AuthRepository.updateDeviceToken(token);
// TODO: Send denne tokenen til din WordPress-backend via AuthRepository eller RetrofitClient.
// F.eks: AuthRepository.updateDeviceToken(token);
// Vi lagrer den midlertidig i UserManager eller SharedPreferences hvis brukeren ikke er logget inn enda.
} }
/**
* Kalles når en melding mottas mens appen er i forgrunnen,
* ELLER hvis det er en "Data Message" (som er det vi bør bruke for bakgrunnsoppdateringer).
*/
@Override @Override
public void onMessageReceived(@NonNull RemoteMessage remoteMessage) { public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage); super.onMessageReceived(remoteMessage);
Log.d(TAG, "Melding mottatt fra: " + remoteMessage.getFrom()); Log.d(TAG, "Melding mottatt fra: " + remoteMessage.getFrom());
// Sjekk om meldingen inneholder data (payload) // 1. Sjekk data payload (Bakgrunnsoppdatering)
if (remoteMessage.getData().size() > 0) { if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Melding data payload: " + remoteMessage.getData()); String forceRefresh = remoteMessage.getData().get("force_refresh");
// Her kan du trigge en oppdatering av kalenderen i bakgrunnen uten å vise varsel,
// eller vise et varsel basert dataene.
if ("true".equalsIgnoreCase(forceRefresh)) {
Log.d(TAG, "Mottok 'force_refresh' - oppdaterer kalender og alarmer...");
updateCalendarAndAlarms();
}
// Hvis meldingen også har egne titler i data-feltet (valgfritt)
String title = remoteMessage.getData().get("title"); String title = remoteMessage.getData().get("title");
String body = remoteMessage.getData().get("body"); String body = remoteMessage.getData().get("body");
if (title != null && body != null) { if (title != null && body != null) {
showNotification(title, body); showNotification(title, body);
} }
} }
// Sjekk om meldingen er en ren varslingsmelding (Notification payload) // 2. Sjekk notification payload (Vises automatisk når app er i bakgrunn, men vi håndterer den her for forgrunn)
if (remoteMessage.getNotification() != null) { if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Melding varsel body: " + remoteMessage.getNotification().getBody()); Log.d(TAG, "Melding varsel body: " + remoteMessage.getNotification().getBody());
showNotification( showNotification(
@ -69,11 +65,31 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
} }
} }
private void updateCalendarAndAlarms() {
// Vi bruker Retrofit for å hente kalenderen nytt
RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback<List<CalendarEvent>>() {
@Override
public void onResponse(Call<List<CalendarEvent>> call, Response<List<CalendarEvent>> response) {
if (response.isSuccessful() && response.body() != null) {
// Lagre til cache først (god praksis)
CacheManager.saveCalendarEvents(getApplicationContext(), response.body());
// Oppdater alarmer lokalt
AlarmScheduler.scheduleAlarmsForEvents(getApplicationContext(), response.body());
Log.d(TAG, "Kalender og alarmer oppdatert via Push.");
}
}
@Override
public void onFailure(Call<List<CalendarEvent>> call, Throwable t) {
Log.e(TAG, "Feil ved push-oppdatering av kalender", t);
}
});
}
private void showNotification(String title, String message) { private void showNotification(String title, String message) {
// Gjenbruk logikk for kanalopprettelse (sikkerhetsnett)
createNotificationChannel(); createNotificationChannel();
// Sjekk rettigheter for Android 13+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
return; return;
@ -99,7 +115,6 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
.setAutoCancel(true); .setAutoCancel(true);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
// Bruk systemtid som ID for å unngå at varsler overskriver hverandre, eller en fast ID hvis ønskelig
notificationManager.notify((int) System.currentTimeMillis(), builder.build()); notificationManager.notify((int) System.currentTimeMillis(), builder.build());
} }

View file

@ -0,0 +1,16 @@
package com.kbs.kbsintranett;
import com.google.gson.annotations.SerializedName;
public class RegisterDeviceRequest {
@SerializedName("fcm_token")
public String fcmToken;
@SerializedName("platform")
public String platform;
public RegisterDeviceRequest(String fcmToken) {
this.fcmToken = fcmToken;
this.platform = "android";
}
}

View file

@ -29,6 +29,9 @@ public class UserManager {
private String stilling; private String stilling;
private String mobiltelefon; private String mobiltelefon;
// FCM Token (Push)
private String fcmToken;
// NYTT: // NYTT:
private List<String> writeableCalendars = new ArrayList<>(); private List<String> writeableCalendars = new ArrayList<>();
@ -79,6 +82,9 @@ public class UserManager {
public String getStilling() { return stilling != null ? stilling : ""; } public String getStilling() { return stilling != null ? stilling : ""; }
public String getMobiltelefon() { return mobiltelefon != null ? mobiltelefon : ""; } public String getMobiltelefon() { return mobiltelefon != null ? mobiltelefon : ""; }
public void setFcmToken(String token) { this.fcmToken = token; }
public String getFcmToken() { return fcmToken; }
public boolean isLoggedIn() { return userEmail != null && !userEmail.isEmpty(); } public boolean isLoggedIn() { return userEmail != null && !userEmail.isEmpty(); }
public boolean isAdmin() { return "administrator".equalsIgnoreCase(userRole); } public boolean isAdmin() { return "administrator".equalsIgnoreCase(userRole); }
public boolean isEditorOrAbove() { public boolean isEditorOrAbove() {
@ -98,5 +104,6 @@ public class UserManager {
stilling = null; stilling = null;
mobiltelefon = null; mobiltelefon = null;
writeableCalendars.clear(); writeableCalendars.clear();
// Vi sletter ikke fcmToken ved logout, da enheten fortsatt er den samme
} }
} }

View file

@ -42,7 +42,6 @@ public interface WordPressApiService {
@GET("wp-json/kbs/v1/calendar/events") @GET("wp-json/kbs/v1/calendar/events")
Call<List<CalendarEvent>> getCalendarEvents(); Call<List<CalendarEvent>> getCalendarEvents();
// DETTE ER METODEN SOM MANGLER:
@POST("wp-json/kbs/v1/calendar/create") @POST("wp-json/kbs/v1/calendar/create")
Call<JsonElement> createCalendarEvent(@Body CreateEventRequest request); Call<JsonElement> createCalendarEvent(@Body CreateEventRequest request);
@ -72,7 +71,9 @@ public interface WordPressApiService {
Call<JsonElement> updateCalendarEvent(@Body CreateEventRequest request); Call<JsonElement> updateCalendarEvent(@Body CreateEventRequest request);
@POST("wp-json/kbs/v1/calendar/delete") @POST("wp-json/kbs/v1/calendar/delete")
Call<JsonElement> deleteCalendarEvent(@Body CreateEventRequest request); // Sender kun ID og cal_type Call<JsonElement> deleteCalendarEvent(@Body CreateEventRequest request);
// NYTT: Registrer enhet for push-varsler
@POST("wp-json/kbs/v1/device/register")
Call<JsonElement> registerDevice(@Body RegisterDeviceRequest request);
} }

View file

@ -58,8 +58,8 @@ android {
applicationId = "com.kbs.kbsintranett" applicationId = "com.kbs.kbsintranett"
minSdk = 28 minSdk = 28
targetSdk = 34 targetSdk = 34
versionCode = 3 versionCode = 4
versionName = "1.4" versionName = "1.5.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
@ -117,8 +117,10 @@ dependencies {
// NY LINJE: (Valgfritt, men lurt for statistikk) // NY LINJE: (Valgfritt, men lurt for statistikk)
implementation("com.google.firebase:firebase-analytics") implementation("com.google.firebase:firebase-analytics")
}
// NYTT: Firebase Cloud Messaging lagt til her
implementation("com.google.firebase:firebase-messaging")
}
============================================================ ============================================================
FILSTI: app\proguard-rules.pro FILSTI: app\proguard-rules.pro
@ -229,6 +231,15 @@ FILSTI: app\src\main\AndroidManifest.xml
android:enabled="true" android:enabled="true"
android:exported="false" /> android:exported="false" />
<!-- NYTT: Registrering av Firebase Messaging Service -->
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="com.kbs.kbsintranett.fileprovider" android:authorities="com.kbs.kbsintranett.fileprovider"
@ -340,6 +351,7 @@ FILSTI: app\src\main\java\com\kbs\kbsintranett\AuthRepository.java
package com.kbs.kbsintranett; package com.kbs.kbsintranett;
import android.util.Log; import android.util.Log;
import com.google.gson.JsonElement;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
import retrofit2.Response; import retrofit2.Response;
@ -395,6 +407,12 @@ public class AuthRepository {
// Lagre listen over skrivbare kalendere // Lagre listen over skrivbare kalendere
UserManager.getInstance().setWriteableCalendars(response.body().writeableCalendars); UserManager.getInstance().setWriteableCalendars(response.body().writeableCalendars);
// NYTT: Hvis vi har en ventende FCM-token, send den nå som vi er logget inn
String pendingToken = UserManager.getInstance().getFcmToken();
if (pendingToken != null && !pendingToken.isEmpty()) {
updateDeviceToken(pendingToken);
}
callback.onSuccess(role); callback.onSuccess(role);
} else { } else {
@ -410,6 +428,35 @@ public class AuthRepository {
} }
}); });
} }
/**
* Sender FCM-token til WordPress for å registrere enheten for push-varsler.
*/
public static void updateDeviceToken(String token) {
if (!UserManager.getInstance().isLoggedIn()) {
// Hvis ikke logget inn, bare lagre den til senere
UserManager.getInstance().setFcmToken(token);
return;
}
// Send til server
RegisterDeviceRequest request = new RegisterDeviceRequest(token);
RetrofitClient.getApiService().registerDevice(request).enqueue(new Callback<JsonElement>() {
@Override
public void onResponse(Call<JsonElement> call, Response<JsonElement> response) {
if (response.isSuccessful()) {
Log.d(TAG, "FCM Token registrert på server OK.");
} else {
Log.e(TAG, "Feil ved registrering av FCM Token. Kode: " + response.code());
}
}
@Override
public void onFailure(Call<JsonElement> call, Throwable t) {
Log.e(TAG, "Nettverksfeil ved sending av FCM token", t);
}
});
}
} }
============================================================ ============================================================
@ -5761,6 +5808,131 @@ public class MainActivity extends AppCompatActivity {
} }
} }
============================================================
FILSTI: app\src\main\java\com\kbs\kbsintranett\MyFirebaseMessagingService.java
============================================================
package com.kbs.kbsintranett;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "FCMService";
private static final String CHANNEL_ID = "kbs_calendar_channel"; // Samme kanal som før
/**
* Kalles når en ny token genereres (f.eks. ved ny installasjon).
* Denne tokenen MÅ sendes til WordPress-backend slik at serveren vet hvem den skal sende til.
*/
@Override
public void onNewToken(@NonNull String token) {
super.onNewToken(token);
Log.d(TAG, "Ny FCM Token: " + token);
// Oppdater token via AuthRepository.
// Hvis brukeren er logget inn, sendes den direkte.
// Hvis ikke, lagres den i UserManager og sendes ved neste login.
AuthRepository.updateDeviceToken(token);
}
/**
* Kalles når en melding mottas mens appen er i forgrunnen,
* ELLER hvis det er en "Data Message" (som er det vi bør bruke for bakgrunnsoppdateringer).
*/
@Override
public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
Log.d(TAG, "Melding mottatt fra: " + remoteMessage.getFrom());
// Sjekk om meldingen inneholder data (payload)
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Melding data payload: " + remoteMessage.getData());
// Her kan du trigge en oppdatering av kalenderen i bakgrunnen uten å vise varsel,
// eller vise et varsel basert på dataene.
String title = remoteMessage.getData().get("title");
String body = remoteMessage.getData().get("body");
if (title != null && body != null) {
showNotification(title, body);
}
}
// Sjekk om meldingen er en ren varslingsmelding (Notification payload)
if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Melding varsel body: " + remoteMessage.getNotification().getBody());
showNotification(
remoteMessage.getNotification().getTitle(),
remoteMessage.getNotification().getBody()
);
}
}
private void showNotification(String title, String message) {
// Gjenbruk logikk for kanalopprettelse (sikkerhetsnett)
createNotificationChannel();
// Sjekk rettigheter for Android 13+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
return;
}
}
Intent tapIntent = new Intent(this, MainActivity.class);
tapIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(
this,
0,
tapIntent,
PendingIntent.FLAG_IMMUTABLE
);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_stat_kbs)
.setColor(ContextCompat.getColor(this, R.color.kbs_logo_blue))
.setContentTitle(title)
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
.setAutoCancel(true);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
// Bruk systemtid som ID for å unngå at varsler overskriver hverandre, eller en fast ID hvis ønskelig
notificationManager.notify((int) System.currentTimeMillis(), builder.build());
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"KBS Kalendervarsler",
NotificationManager.IMPORTANCE_HIGH
);
channel.setDescription("Varsler fra KBS Intranett");
NotificationManager manager = getSystemService(NotificationManager.class);
if (manager != null) {
manager.createNotificationChannel(channel);
}
}
}
}
============================================================ ============================================================
FILSTI: app\src\main\java\com\kbs\kbsintranett\NewsAdapter.java FILSTI: app\src\main\java\com\kbs\kbsintranett\NewsAdapter.java
============================================================ ============================================================
@ -6405,6 +6577,26 @@ public class ProfileFragment extends Fragment {
} }
} }
============================================================
FILSTI: app\src\main\java\com\kbs\kbsintranett\RegisterDeviceRequest.java
============================================================
package com.kbs.kbsintranett;
import com.google.gson.annotations.SerializedName;
public class RegisterDeviceRequest {
@SerializedName("fcm_token")
public String fcmToken;
@SerializedName("platform")
public String platform;
public RegisterDeviceRequest(String fcmToken) {
this.fcmToken = fcmToken;
this.platform = "android";
}
}
============================================================ ============================================================
FILSTI: app\src\main\java\com\kbs\kbsintranett\RetrofitClient.java FILSTI: app\src\main\java\com\kbs\kbsintranett\RetrofitClient.java
============================================================ ============================================================
@ -6511,6 +6703,9 @@ public class UserManager {
private String stilling; private String stilling;
private String mobiltelefon; private String mobiltelefon;
// FCM Token (Push)
private String fcmToken;
// NYTT: // NYTT:
private List<String> writeableCalendars = new ArrayList<>(); private List<String> writeableCalendars = new ArrayList<>();
@ -6561,6 +6756,9 @@ public class UserManager {
public String getStilling() { return stilling != null ? stilling : ""; } public String getStilling() { return stilling != null ? stilling : ""; }
public String getMobiltelefon() { return mobiltelefon != null ? mobiltelefon : ""; } public String getMobiltelefon() { return mobiltelefon != null ? mobiltelefon : ""; }
public void setFcmToken(String token) { this.fcmToken = token; }
public String getFcmToken() { return fcmToken; }
public boolean isLoggedIn() { return userEmail != null && !userEmail.isEmpty(); } public boolean isLoggedIn() { return userEmail != null && !userEmail.isEmpty(); }
public boolean isAdmin() { return "administrator".equalsIgnoreCase(userRole); } public boolean isAdmin() { return "administrator".equalsIgnoreCase(userRole); }
public boolean isEditorOrAbove() { public boolean isEditorOrAbove() {
@ -6580,6 +6778,7 @@ public class UserManager {
stilling = null; stilling = null;
mobiltelefon = null; mobiltelefon = null;
writeableCalendars.clear(); writeableCalendars.clear();
// Vi sletter ikke fcmToken ved logout, da enheten fortsatt er den samme
} }
} }
@ -6688,7 +6887,6 @@ public interface WordPressApiService {
@GET("wp-json/kbs/v1/calendar/events") @GET("wp-json/kbs/v1/calendar/events")
Call<List<CalendarEvent>> getCalendarEvents(); Call<List<CalendarEvent>> getCalendarEvents();
// DETTE ER METODEN SOM MANGLER:
@POST("wp-json/kbs/v1/calendar/create") @POST("wp-json/kbs/v1/calendar/create")
Call<JsonElement> createCalendarEvent(@Body CreateEventRequest request); Call<JsonElement> createCalendarEvent(@Body CreateEventRequest request);
@ -6718,9 +6916,11 @@ public interface WordPressApiService {
Call<JsonElement> updateCalendarEvent(@Body CreateEventRequest request); Call<JsonElement> updateCalendarEvent(@Body CreateEventRequest request);
@POST("wp-json/kbs/v1/calendar/delete") @POST("wp-json/kbs/v1/calendar/delete")
Call<JsonElement> deleteCalendarEvent(@Body CreateEventRequest request); // Sender kun ID og cal_type Call<JsonElement> deleteCalendarEvent(@Body CreateEventRequest request);
// NYTT: Registrer enhet for push-varsler
@POST("wp-json/kbs/v1/device/register")
Call<JsonElement> registerDevice(@Body RegisterDeviceRequest request);
} }
============================================================ ============================================================