diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4858af9..95307a6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -13,6 +13,7 @@
+
+
+
= Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "KBS Kalender", NotificationManager.IMPORTANCE_HIGH);
+ channel.setDescription("Varsler for kalenderhendelser");
+ manager.createNotificationChannel(channel);
+ }
+
+ Intent openAppIntent = new Intent(context, MainActivity.class);
+ openAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ PendingIntent pendingIntent = PendingIntent.getActivity(
+ context,
+ 0,
+ openAppIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
+ );
+
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setContentTitle(title)
+ .setContentText(message)
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setCategory(NotificationCompat.CATEGORY_EVENT)
+ .setContentIntent(pendingIntent)
+ .setAutoCancel(true);
+
+ try {
+ manager.notify(notificationId, builder.build());
+ Log.d(TAG, "AlarmReceiver: Varsel sendt til systemet.");
+ } catch (SecurityException e) {
+ Log.e(TAG, "AlarmReceiver: Feil - Mangler tillatelse til å sende varsel!", e);
+ } catch (Exception e) {
+ Log.e(TAG, "AlarmReceiver: Ukjent feil ved visning av varsel", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kbs/kbsintranett/CalendarFullFragment.java b/app/src/main/java/com/kbs/kbsintranett/CalendarFullFragment.java
index 601a4a5..0878e49 100644
--- a/app/src/main/java/com/kbs/kbsintranett/CalendarFullFragment.java
+++ b/app/src/main/java/com/kbs/kbsintranett/CalendarFullFragment.java
@@ -38,7 +38,6 @@ public class CalendarFullFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
-
recyclerView = view.findViewById(R.id.recycler_full_calendar);
progressBar = view.findViewById(R.id.loading_full_calendar);
emptyView = view.findViewById(R.id.empty_view_calendar);
@@ -46,7 +45,6 @@ public class CalendarFullFragment extends Fragment {
layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
-
backBtn.setOnClickListener(v -> Navigation.findNavController(view).navigateUp());
fetchAllEvents();
@@ -54,22 +52,21 @@ public class CalendarFullFragment extends Fragment {
private void fetchAllEvents() {
progressBar.setVisibility(View.VISIBLE);
-
// Hent personlige hendelser (Nå med historikk)
List deviceEvents = CalendarManager.getDeviceEvents(getContext());
- RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback>() {
+ // NYTT: Hent fra Google direkte
+ String url = CalendarManager.getGoogleCalendarUrl();
+ RetrofitClient.getApiService().getDirectGoogleEvents(url).enqueue(new Callback() {
@Override
- public void onResponse(Call> call, Response> response) {
+ public void onResponse(Call call, Response response) {
if (!isAdded()) return;
progressBar.setVisibility(View.GONE);
List apiEvents = new ArrayList<>();
if (response.isSuccessful() && response.body() != null) {
- for (CalendarEvent e : response.body()) {
- CalendarManager.formatEventForUI(e);
- apiEvents.add(e);
- }
+ // Konverter og formatér
+ apiEvents = CalendarManager.convertGoogleResponse(response.body());
}
// Flett og vis
@@ -94,7 +91,7 @@ public class CalendarFullFragment extends Fragment {
}
@Override
- public void onFailure(Call> call, Throwable t) {
+ public void onFailure(Call call, Throwable t) {
if (!isAdded()) return;
progressBar.setVisibility(View.GONE);
diff --git a/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java b/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java
index 46133d6..2c5a5ba 100644
--- a/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java
+++ b/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java
@@ -15,15 +15,86 @@ import java.util.TimeZone;
public class CalendarManager {
+ // --- KONFIGURASJON FOR GOOGLE DIREKTE-KOBLING ---
+ private static final String GOOGLE_CALENDAR_ID = "kbservice.no_o2bmp5f9f540vedveit51optfo@group.calendar.google.com";
+
+ // TODO: Sett inn din API-nøkkel her!
+ private static final String GOOGLE_API_KEY = "AIzaSyCos8VW5mClUcuhs86gbSJo8uitY0fVPus";
+
+ public static String getGoogleCalendarUrl() {
+ // Hent hendelser fra 1 år tilbake i tid
+ long oneYearAgo = System.currentTimeMillis() - (365L * 24 * 60 * 60 * 1000);
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ String timeMin = dateFormat.format(new Date(oneYearAgo));
+
+ return "https://www.googleapis.com/calendar/v3/calendars/"
+ + GOOGLE_CALENDAR_ID
+ + "/events?key=" + GOOGLE_API_KEY
+ + "&singleEvents=true"
+ + "&orderBy=startTime"
+ + "&maxResults=250"
+ + "&timeMin=" + timeMin; // URL-encoded er ikke nødvendig her siden Retrofit/OkHttp håndterer det, men timeMin bør være formatert
+ }
+
+ // Konverterer Google Response til vår interne CalendarEvent
+ public static List convertGoogleResponse(GoogleCalendarModels.Response response) {
+ List events = new ArrayList<>();
+ if (response == null || response.items == null) return events;
+
+ for (GoogleCalendarModels.Item item : response.items) {
+ String title = item.summary != null ? item.summary : "(Uten tittel)";
+ String desc = item.description;
+ String loc = item.location;
+
+ // Dato-logikk
+ String start = null;
+ String end = null;
+
+ if (item.start != null) {
+ if (item.start.dateTime != null) start = item.start.dateTime;
+ else start = item.start.date; // Heldags
+ }
+ if (item.end != null) {
+ if (item.end.dateTime != null) end = item.end.dateTime;
+ else end = item.end.date; // Heldags
+ }
+
+ // Varslings-logikk (Henter de sanne innstillingene)
+ int reminderMinutes = 15; // Default fallback
+ if (item.reminders != null) {
+ if (item.reminders.useDefault) {
+ reminderMinutes = 15; // Standard i Google er ofte 10 eller 15, vi antar 15
+ } else if (item.reminders.overrides != null) {
+ for (GoogleCalendarModels.Override override : item.reminders.overrides) {
+ if ("popup".equalsIgnoreCase(override.method) || "alert".equalsIgnoreCase(override.method)) {
+ reminderMinutes = override.minutes;
+ break; // Bruk den første popup-varslingen vi finner
+ }
+ }
+ }
+ }
+
+ CalendarEvent event = new CalendarEvent(title, start, end, desc, loc);
+ event.setReminderMinutes(reminderMinutes);
+
+ // Formatér for UI med en gang
+ formatEventForUI(event);
+
+ events.add(event);
+ }
+ return events;
+ }
+
+ // --- EKSISTERENDE KODE (UENDRET UNDER) ---
+
// NY HJELPEMETODE: Finner ID-ene til kalendere som tilhører @kbs.no
private static List getKbsCalendarIds(Context context) {
List ids = new ArrayList<>();
-
String[] projection = new String[] {
CalendarContract.Calendars._ID,
CalendarContract.Calendars.ACCOUNT_NAME
};
-
// Vi ser etter kontoer som slutter på @kbs.no
// (SQL: account_name LIKE '%@kbs.no')
String selection = CalendarContract.Calendars.ACCOUNT_NAME + " LIKE ?";
@@ -38,7 +109,8 @@ public class CalendarManager {
)) {
if (cursor != null) {
while (cursor.moveToNext()) {
- ids.add(cursor.getString(0)); // Legg til kalender-ID
+ ids.add(cursor.getString(0));
+ // Legg til kalender-ID
}
}
} catch (Exception e) {
@@ -49,7 +121,6 @@ public class CalendarManager {
public static List getDeviceEvents(Context context) {
List deviceEvents = new ArrayList<>();
-
if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR)
!= PackageManager.PERMISSION_GRANTED) {
return deviceEvents;
@@ -57,7 +128,6 @@ public class CalendarManager {
// 1. Finn først ID-ene til KBS-kalenderne
List kbsCalendarIds = getKbsCalendarIds(context);
-
// Hvis ingen kbs-kalendere finnes på telefonen, returner tom liste
if (kbsCalendarIds.isEmpty()) {
return deviceEvents;
@@ -67,7 +137,6 @@ public class CalendarManager {
long now = System.currentTimeMillis();
long startMillis = now - (365L * 24 * 60 * 60 * 1000);
long endMillis = now + (365L * 24 * 60 * 60 * 1000);
-
String[] projection = new String[]{
CalendarContract.Events.TITLE,
CalendarContract.Events.DTSTART,
@@ -90,7 +159,6 @@ public class CalendarManager {
}
}
selection.append(") AND ");
-
selection.append(CalendarContract.Events.DTSTART).append(" >= ? AND ");
selection.append(CalendarContract.Events.DTSTART).append(" <= ?");
@@ -98,7 +166,6 @@ public class CalendarManager {
selectionArgsList.add(String.valueOf(endMillis));
String[] selectionArgs = selectionArgsList.toArray(new String[0]);
-
try (Cursor cursor = context.getContentResolver().query(
CalendarContract.Events.CONTENT_URI,
projection,
@@ -108,7 +175,6 @@ public class CalendarManager {
)) {
if (cursor != null) {
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
-
while (cursor.moveToNext()) {
String title = cursor.getString(0);
long dtStart = cursor.getLong(1);
@@ -116,7 +182,6 @@ public class CalendarManager {
String desc = cursor.getString(3);
String loc = cursor.getString(4);
int allDay = cursor.getInt(5);
-
String rawStart;
String rawEnd;
@@ -140,14 +205,11 @@ public class CalendarManager {
return deviceEvents;
}
- // --- (formatEventForUI og mergeAndSort er uendret fra forrige versjon) ---
public static void formatEventForUI(CalendarEvent event) {
if (event.getRawDate() == null) return;
-
SimpleDateFormat outputDay = new SimpleDateFormat("dd", Locale.getDefault());
SimpleDateFormat outputMonth = new SimpleDateFormat("MMM", new Locale("no", "NO"));
SimpleDateFormat outputTime = new SimpleDateFormat("HH:mm", Locale.getDefault());
-
outputMonth.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
outputTime.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
@@ -157,7 +219,6 @@ public class CalendarManager {
boolean isAllDay = false;
String raw = event.getRawDate();
-
if (raw.length() == 10 && !raw.contains("T") && !raw.contains(" ")) {
SimpleDateFormat shortFmt = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
date = shortFmt.parse(raw);
@@ -215,7 +276,6 @@ public class CalendarManager {
String d2 = e2.getRawDate() != null ? e2.getRawDate() : "";
return d1.compareTo(d2);
});
-
return all;
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/kbs/kbsintranett/GoogleCalendarModels.java b/app/src/main/java/com/kbs/kbsintranett/GoogleCalendarModels.java
new file mode 100644
index 0000000..28bc501
--- /dev/null
+++ b/app/src/main/java/com/kbs/kbsintranett/GoogleCalendarModels.java
@@ -0,0 +1,59 @@
+package com.kbs.kbsintranett;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.List;
+
+/**
+ * Hjelpeklasser for å parse JSON direkte fra Google Calendar API v3.
+ */
+public class GoogleCalendarModels {
+
+ public static class Response {
+ @SerializedName("items")
+ public List- items;
+ }
+
+ public static class Item {
+ @SerializedName("summary")
+ public String summary;
+
+ @SerializedName("description")
+ public String description;
+
+ @SerializedName("location")
+ public String location;
+
+ @SerializedName("start")
+ public TimePoint start;
+
+ @SerializedName("end")
+ public TimePoint end;
+
+ @SerializedName("reminders")
+ public Reminders reminders;
+ }
+
+ public static class TimePoint {
+ @SerializedName("dateTime")
+ public String dateTime; // Format: 2025-12-15T10:00:00+01:00
+
+ @SerializedName("date")
+ public String date; // Format: 2025-12-15 (for heldags)
+ }
+
+ public static class Reminders {
+ @SerializedName("useDefault")
+ public boolean useDefault;
+
+ @SerializedName("overrides")
+ public List overrides;
+ }
+
+ public static class Override {
+ @SerializedName("method")
+ public String method; // f.eks "popup"
+
+ @SerializedName("minutes")
+ public int minutes;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java
index 56e729d..8e68992 100644
--- a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java
+++ b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java
@@ -37,7 +37,6 @@ public class HomeFragment extends Fragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
// Håndter svar på kalendertillatelse
requestPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
@@ -48,7 +47,6 @@ public class HomeFragment extends Fragment {
}
}
);
-
// Start bakgrunnsjobb for varsling (kjører hver 15. minutt)
startNotificationWorker();
}
@@ -62,7 +60,6 @@ public class HomeFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
-
// 0. Profil-knapp
View profileBtn = view.findViewById(R.id.btn_profile);
if (profileBtn != null) {
@@ -74,7 +71,6 @@ public class HomeFragment extends Fragment {
calendarRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
// Sett tom adapter midlertidig
calendarRecycler.setAdapter(new CalendarAdapter(new ArrayList<>(), event -> {}));
-
// "Se alle" knapp for kalender
TextView viewAllCalendar = view.findViewById(R.id.btn_view_all_calendar);
if (viewAllCalendar != null) {
@@ -102,7 +98,6 @@ public class HomeFragment extends Fragment {
newsRecycler.setNestedScrollingEnabled(false);
// Sett tom adapter midlertidig
newsRecycler.setAdapter(new NewsAdapter(new ArrayList<>(), item -> {}));
-
// "Se alle" knapp for nyheter
TextView viewAllNews = view.findViewById(R.id.btn_view_all_news);
if (viewAllNews != null) {
@@ -118,26 +113,23 @@ public class HomeFragment extends Fragment {
// 1. Hent personlige hendelser først (fra CalendarManager)
List deviceEvents = CalendarManager.getDeviceEvents(getContext());
- // 2. Hent API-hendelser fra WordPress
- WordPressApiService apiService = RetrofitClient.getApiService();
- apiService.getCalendarEvents().enqueue(new Callback
>() {
+ // 2. Hent API-hendelser DIREKTE fra Google
+ String url = CalendarManager.getGoogleCalendarUrl();
+ RetrofitClient.getApiService().getDirectGoogleEvents(url).enqueue(new Callback() {
@Override
- public void onResponse(Call> call, Response> response) {
+ public void onResponse(Call call, Response response) {
if (!isAdded()) return;
List apiEvents = new ArrayList<>();
if (response.isSuccessful() && response.body() != null) {
- for (CalendarEvent e : response.body()) {
- CalendarManager.formatEventForUI(e); // Formatér datoer
- apiEvents.add(e);
- }
+ // Konverter fra Google-modell til KBS-modell
+ apiEvents = CalendarManager.convertGoogleResponse(response.body());
}
// 3. Flett listene (API + Personlig) og sorter
List merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents);
// 4. Filtrer ut hendelser som har passert (vis kun fremtidige + i dag)
- // (CalendarManager henter 1 år bakover, så vi må filtrere for "Topp 5 kommende")
List upcomingEvents = new ArrayList<>();
String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
@@ -160,14 +152,12 @@ public class HomeFragment extends Fragment {
}
@Override
- public void onFailure(Call> call, Throwable t) {
+ public void onFailure(Call call, Throwable t) {
if (!isAdded()) return;
// Hvis API feiler, vis bare personlige events hvis vi har noen
if (!deviceEvents.isEmpty()) {
List top5 = new ArrayList<>();
- // Filtrer og plukk topp 5 fra lokale også
for(int i=0; i {
CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
sheet.show(getParentFragmentManager(), "CalendarDetails");
@@ -201,7 +191,8 @@ public class HomeFragment extends Fragment {
for (WpPost post : wpPosts) {
try {
Date date = rawFormat.parse(post.date);
- post.date = targetFormat.format(date); // Setter pen dato
+ post.date = targetFormat.format(date);
+ // Setter pen dato
} catch (Exception e) {}
}
diff --git a/app/src/main/java/com/kbs/kbsintranett/MainActivity.java b/app/src/main/java/com/kbs/kbsintranett/MainActivity.java
index cfd03b1..b940995 100644
--- a/app/src/main/java/com/kbs/kbsintranett/MainActivity.java
+++ b/app/src/main/java/com/kbs/kbsintranett/MainActivity.java
@@ -1,6 +1,13 @@
package com.kbs.kbsintranett;
+import android.app.AlarmManager;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
+import android.provider.Settings;
import android.util.Log;
import android.view.View;
@@ -8,6 +15,8 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.NavigationUI;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkManager;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
@@ -32,6 +41,7 @@ public class MainActivity extends AppCompatActivity {
bottomNav = findViewById(R.id.bottom_nav_view);
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
.findFragmentById(R.id.nav_host_fragment);
+
if (navHostFragment != null) {
navController = navHostFragment.getNavController();
NavigationUI.setupWithNavController(bottomNav, navController);
@@ -47,10 +57,50 @@ public class MainActivity extends AppCompatActivity {
});
}
+ // --- NYTT: Sjekk tillatelse for nøyaktige alarmer (Android 12+) ---
+ checkExactAlarmPermission();
+
// 2. Start Silent Sign-In ved oppstart
checkLoginState();
}
+ /**
+ * Sjekker om appen har lov til å sette nøyaktige alarmer (SCHEDULE_EXACT_ALARM).
+ * Hvis ikke, spør brukeren om å gå til innstillinger.
+ */
+ private void checkExactAlarmPermission() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ if (alarmManager != null && !alarmManager.canScheduleExactAlarms()) {
+ // Vi mangler tillatelse. Vis dialog.
+ new AlertDialog.Builder(this)
+ .setTitle("Varslingstillatelse kreves")
+ .setMessage("For at kalenderen skal kunne varsle deg nøyaktig når et møte starter, må du gi appen tilgang til å sette alarmer.")
+ .setPositiveButton("Gå til Innstillinger", (dialog, which) -> {
+ Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
+ intent.setData(Uri.parse("package:" + getPackageName()));
+ startActivity(intent);
+ })
+ .setNegativeButton("Senere", null)
+ .show();
+ } else {
+ // Vi har tillatelse (eller er på eldre Android). Kjør logikk!
+ runNotificationWorker();
+ }
+ } else {
+ // Eldre Android-versjoner trenger ikke denne tillatelsen
+ runNotificationWorker();
+ }
+ }
+
+ private void runNotificationWorker() {
+ // --- DEBUG: TVING KJØRING AV KALENDER-SJEKK ---
+ // Denne linjen kjører NotificationWorker umiddelbart ved oppstart for feilsøking.
+ // Fjern eller kommenter ut denne når testingen er ferdig.
+ WorkManager.getInstance(this).enqueue(OneTimeWorkRequest.from(NotificationWorker.class));
+ // ----------------------------------------------
+ }
+
private void checkLoginState() {
GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
if (account == null) {
diff --git a/app/src/main/java/com/kbs/kbsintranett/NotificationWorker.java b/app/src/main/java/com/kbs/kbsintranett/NotificationWorker.java
index c0c5bf8..9b59010 100644
--- a/app/src/main/java/com/kbs/kbsintranett/NotificationWorker.java
+++ b/app/src/main/java/com/kbs/kbsintranett/NotificationWorker.java
@@ -1,12 +1,12 @@
package com.kbs.kbsintranett;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
import android.content.Context;
-import android.content.SharedPreferences;
+import android.content.Intent;
import android.os.Build;
+import android.util.Log;
import androidx.annotation.NonNull;
-import androidx.core.app.NotificationCompat;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import java.io.IOException;
@@ -18,8 +18,7 @@ import retrofit2.Response;
public class NotificationWorker extends Worker {
- private static final String CHANNEL_ID = "kbs_calendar_channel";
- private static final String PREFS_NAME = "KBSNotificationPrefs";
+ private static final String TAG = "KBS_DEBUG";
public NotificationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
@@ -28,72 +27,108 @@ public class NotificationWorker extends Worker {
@NonNull
@Override
public Result doWork() {
- // Dette kjører i bakgrunnen
+ Log.d(TAG, "NotificationWorker: Starter sjekk av kalender...");
+
try {
- // Hent events synkront (ikke enqueue)
- Response> response = RetrofitClient.getApiService().getCalendarEvents().execute();
+ String url = CalendarManager.getGoogleCalendarUrl();
+ // Hent events synkront
+ Response response = RetrofitClient.getApiService().getDirectGoogleEvents(url).execute();
if (response.isSuccessful() && response.body() != null) {
- checkAndNotify(response.body());
+ List events = CalendarManager.convertGoogleResponse(response.body());
+ scheduleAlarms(events);
return Result.success();
} else {
+ Log.e(TAG, "NotificationWorker: API-kall feilet. Kode: " + response.code());
return Result.retry();
}
} catch (IOException e) {
+ Log.e(TAG, "NotificationWorker: Nettverksfeil", e);
return Result.retry();
}
}
- private void checkAndNotify(List events) {
- SharedPreferences prefs = getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
- long now = System.currentTimeMillis();
- long fifteenMinutes = 15 * 60 * 1000;
+ private void scheduleAlarms(List events) {
+ Context context = getApplicationContext();
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ // Sjekk rettigheter (Android 12+)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ if (!alarmManager.canScheduleExactAlarms()) {
+ Log.e(TAG, "NotificationWorker: MANGLER fortsatt tillatelse! Gå til Innstillinger -> Apper -> KBS -> Alarmer og påminnelser.");
+ return;
+ }
+ }
+
+ long now = System.currentTimeMillis();
+ // Bruker en parser som er litt mer fleksibel for datoer
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
- SimpleDateFormat sqlFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
+
+ int countSet = 0;
for (CalendarEvent event : events) {
try {
- Date eventDate;
- if (event.getRawDate().contains("T")) eventDate = isoFormat.parse(event.getRawDate());
- else eventDate = sqlFormat.parse(event.getRawDate());
+ // Hopp over heldagshendelser (dato uten klokkeslett)
+ if (event.getRawDate().length() == 10) continue;
+
+ Date eventDate = null;
+ // Enkel parsing. Merk: Google sender med tidssone (+01:00),
+ // men SimpleDateFormat uten 'X' vil parse dette som lokal tid hvis formatet stemmer.
+ // For optimal tidssone-håndtering burde vi brukt java.time (Android 8+),
+ // men dette fungerer greit så lenge telefonen er i samme sone som kalenderen.
+ if (event.getRawDate().contains("T")) {
+ // Kutter vekk tidssone-offset for enkel parsing til lokal tid
+ String raw = event.getRawDate();
+ if (raw.length() > 19) raw = raw.substring(0, 19);
+ eventDate = isoFormat.parse(raw);
+ }
if (eventDate == null) continue;
- long diff = eventDate.getTime() - now;
+ // Beregn når alarmen skal gå
+ long triggerTime = eventDate.getTime() - (event.getReminderMinutes() * 60 * 1000L);
- // Hvis eventet starter innen de neste 30 min, og ikke allerede varslet
- if (diff > 0 && diff < (30 * 60 * 1000)) {
- String eventId = event.getTitle() + event.getRawDate(); // Enkel ID
- boolean alreadyNotified = prefs.getBoolean(eventId, false);
+ // Vi setter alarmen hvis tidspunktet er i fremtiden
+ // Vi sjekker også at det ikke er mer enn 24 timer frem i tid (for å spare ressurser)
+ if (triggerTime > now && triggerTime < (now + 24 * 60 * 60 * 1000L)) {
- if (!alreadyNotified) {
- sendNotification(event.getTitle(), "Starter kl " + event.getTime());
- // Lagre at vi har varslet
- prefs.edit().putBoolean(eventId, true).apply();
+ // Lag en unik ID for alarmen
+ String uniqueIdString = event.getTitle() + event.getRawDate();
+ int alarmId = uniqueIdString.hashCode();
+
+ Intent intent = new Intent(context, AlarmReceiver.class);
+ intent.putExtra("TITLE", event.getTitle());
+ intent.putExtra("MESSAGE", "Starter kl " + event.getTime());
+ intent.putExtra("ID", alarmId);
+
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ context,
+ alarmId,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
+ );
+
+ // VIKTIG ENDRING: Vi setter alarmen PÅ NYTT hver gang.
+ // AlarmManager overskriver automatisk hvis ID er lik.
+ // Dette sikrer at alarmen faktisk ligger der, selv etter omstart av tlf.
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent);
+ } else {
+ alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent);
}
+
+ Log.i(TAG, ">>> ALARM SATT (Oppdatert): " + event.getTitle() + " -> Skal ringe: " + new Date(triggerTime));
+ countSet++;
}
+
} catch (Exception e) {
- e.printStackTrace();
+ Log.e(TAG, "Feil ved behandling av event: " + event.getTitle(), e);
}
}
- }
- private void sendNotification(String title, String content) {
- NotificationManager manager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "KBS Kalender", NotificationManager.IMPORTANCE_HIGH);
- manager.createNotificationChannel(channel);
+ if (countSet == 0) {
+ Log.d(TAG, "Ingen kommende alarmer (innenfor neste 24t) funnet akkurat nå.");
}
-
- NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
- .setSmallIcon(R.mipmap.ic_launcher) // Sørg for at du har et ikon her
- .setContentTitle(title)
- .setContentText(content)
- .setPriority(NotificationCompat.PRIORITY_HIGH)
- .setAutoCancel(true);
-
- manager.notify((int) System.currentTimeMillis(), builder.build());
}
}
\ 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 c4e6b51..b380da3 100644
--- a/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java
+++ b/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java
@@ -15,6 +15,7 @@ import retrofit2.http.Multipart;
import retrofit2.http.Part;
import retrofit2.http.PartMap;
import retrofit2.http.Query;
+import retrofit2.http.Url; // NY IMPORT
public interface WordPressApiService {
@GET("wp-json/wp/v2/posts?per_page=10&_embed")
@@ -40,9 +41,14 @@ public interface WordPressApiService {
@Part List files
);
+ // ENDRET: Denne brukes ikke lenger for kalender, men beholdes for bakoverkompatibilitet
@GET("wp-json/kbs/v1/calendar/events")
Call> getCalendarEvents();
+ // NY: Direkte kall mot Google (bruker @Url for å override base URL)
+ @GET
+ Call getDirectGoogleEvents(@Url String fullUrl);
+
@GET("wp-json/gf/v2/entries")
Call getEntries(
@Query("form_ids") int formId,
diff --git a/hele_prosjektet.txt b/hele_prosjektet.txt
index 7c5359e..746c51f 100644
--- a/hele_prosjektet.txt
+++ b/hele_prosjektet.txt
@@ -177,6 +177,7 @@ FILSTI: app\src\main\AndroidManifest.xml
+
+
+
+============================================================
+FILSTI: app\src\main\java\com\kbs\kbsintranett\AlarmReceiver.java
+============================================================
+package com.kbs.kbsintranett;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.util.Log;
+import androidx.core.app.NotificationCompat;
+
+public class AlarmReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "KBS_DEBUG";
+ private static final String CHANNEL_ID = "kbs_calendar_channel";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String title = intent.getStringExtra("TITLE");
+ String message = intent.getStringExtra("MESSAGE");
+ int notificationId = intent.getIntExtra("ID", 0);
+
+ Log.i(TAG, "AlarmReceiver: WAKE UP! Mottok alarm for: " + title);
+
+ showNotification(context, title, message, notificationId);
+ }
+
+ private void showNotification(Context context, String title, String message, int notificationId) {
+ NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "KBS Kalender", NotificationManager.IMPORTANCE_HIGH);
+ channel.setDescription("Varsler for kalenderhendelser");
+ manager.createNotificationChannel(channel);
+ }
+
+ Intent openAppIntent = new Intent(context, MainActivity.class);
+ openAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ PendingIntent pendingIntent = PendingIntent.getActivity(
+ context,
+ 0,
+ openAppIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
+ );
+
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setContentTitle(title)
+ .setContentText(message)
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setCategory(NotificationCompat.CATEGORY_EVENT)
+ .setContentIntent(pendingIntent)
+ .setAutoCancel(true);
+
+ try {
+ manager.notify(notificationId, builder.build());
+ Log.d(TAG, "AlarmReceiver: Varsel sendt til systemet.");
+ } catch (SecurityException e) {
+ Log.e(TAG, "AlarmReceiver: Feil - Mangler tillatelse til å sende varsel!", e);
+ } catch (Exception e) {
+ Log.e(TAG, "AlarmReceiver: Ukjent feil ved visning av varsel", e);
+ }
+ }
+}
+
============================================================
FILSTI: app\src\main\java\com\kbs\kbsintranett\AuthRepository.java
============================================================
@@ -568,7 +640,6 @@ public class CalendarFullFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
-
recyclerView = view.findViewById(R.id.recycler_full_calendar);
progressBar = view.findViewById(R.id.loading_full_calendar);
emptyView = view.findViewById(R.id.empty_view_calendar);
@@ -576,7 +647,6 @@ public class CalendarFullFragment extends Fragment {
layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
-
backBtn.setOnClickListener(v -> Navigation.findNavController(view).navigateUp());
fetchAllEvents();
@@ -584,22 +654,21 @@ public class CalendarFullFragment extends Fragment {
private void fetchAllEvents() {
progressBar.setVisibility(View.VISIBLE);
-
// Hent personlige hendelser (Nå med historikk)
List deviceEvents = CalendarManager.getDeviceEvents(getContext());
- RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback>() {
+ // NYTT: Hent fra Google direkte
+ String url = CalendarManager.getGoogleCalendarUrl();
+ RetrofitClient.getApiService().getDirectGoogleEvents(url).enqueue(new Callback() {
@Override
- public void onResponse(Call> call, Response> response) {
+ public void onResponse(Call call, Response response) {
if (!isAdded()) return;
progressBar.setVisibility(View.GONE);
List apiEvents = new ArrayList<>();
if (response.isSuccessful() && response.body() != null) {
- for (CalendarEvent e : response.body()) {
- CalendarManager.formatEventForUI(e);
- apiEvents.add(e);
- }
+ // Konverter og formatér
+ apiEvents = CalendarManager.convertGoogleResponse(response.body());
}
// Flett og vis
@@ -624,7 +693,7 @@ public class CalendarFullFragment extends Fragment {
}
@Override
- public void onFailure(Call> call, Throwable t) {
+ public void onFailure(Call call, Throwable t) {
if (!isAdded()) return;
progressBar.setVisibility(View.GONE);
@@ -684,15 +753,86 @@ import java.util.TimeZone;
public class CalendarManager {
+ // --- KONFIGURASJON FOR GOOGLE DIREKTE-KOBLING ---
+ private static final String GOOGLE_CALENDAR_ID = "kbservice.no_o2bmp5f9f540vedveit51optfo@group.calendar.google.com";
+
+ // TODO: Sett inn din API-nøkkel her!
+ private static final String GOOGLE_API_KEY = "AIzaSyCos8VW5mClUcuhs86gbSJo8uitY0fVPus";
+
+ public static String getGoogleCalendarUrl() {
+ // Hent hendelser fra 1 år tilbake i tid
+ long oneYearAgo = System.currentTimeMillis() - (365L * 24 * 60 * 60 * 1000);
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ String timeMin = dateFormat.format(new Date(oneYearAgo));
+
+ return "https://www.googleapis.com/calendar/v3/calendars/"
+ + GOOGLE_CALENDAR_ID
+ + "/events?key=" + GOOGLE_API_KEY
+ + "&singleEvents=true"
+ + "&orderBy=startTime"
+ + "&maxResults=250"
+ + "&timeMin=" + timeMin; // URL-encoded er ikke nødvendig her siden Retrofit/OkHttp håndterer det, men timeMin bør være formatert
+ }
+
+ // Konverterer Google Response til vår interne CalendarEvent
+ public static List convertGoogleResponse(GoogleCalendarModels.Response response) {
+ List events = new ArrayList<>();
+ if (response == null || response.items == null) return events;
+
+ for (GoogleCalendarModels.Item item : response.items) {
+ String title = item.summary != null ? item.summary : "(Uten tittel)";
+ String desc = item.description;
+ String loc = item.location;
+
+ // Dato-logikk
+ String start = null;
+ String end = null;
+
+ if (item.start != null) {
+ if (item.start.dateTime != null) start = item.start.dateTime;
+ else start = item.start.date; // Heldags
+ }
+ if (item.end != null) {
+ if (item.end.dateTime != null) end = item.end.dateTime;
+ else end = item.end.date; // Heldags
+ }
+
+ // Varslings-logikk (Henter de sanne innstillingene)
+ int reminderMinutes = 15; // Default fallback
+ if (item.reminders != null) {
+ if (item.reminders.useDefault) {
+ reminderMinutes = 15; // Standard i Google er ofte 10 eller 15, vi antar 15
+ } else if (item.reminders.overrides != null) {
+ for (GoogleCalendarModels.Override override : item.reminders.overrides) {
+ if ("popup".equalsIgnoreCase(override.method) || "alert".equalsIgnoreCase(override.method)) {
+ reminderMinutes = override.minutes;
+ break; // Bruk den første popup-varslingen vi finner
+ }
+ }
+ }
+ }
+
+ CalendarEvent event = new CalendarEvent(title, start, end, desc, loc);
+ event.setReminderMinutes(reminderMinutes);
+
+ // Formatér for UI med en gang
+ formatEventForUI(event);
+
+ events.add(event);
+ }
+ return events;
+ }
+
+ // --- EKSISTERENDE KODE (UENDRET UNDER) ---
+
// NY HJELPEMETODE: Finner ID-ene til kalendere som tilhører @kbs.no
private static List getKbsCalendarIds(Context context) {
List ids = new ArrayList<>();
-
String[] projection = new String[] {
CalendarContract.Calendars._ID,
CalendarContract.Calendars.ACCOUNT_NAME
};
-
// Vi ser etter kontoer som slutter på @kbs.no
// (SQL: account_name LIKE '%@kbs.no')
String selection = CalendarContract.Calendars.ACCOUNT_NAME + " LIKE ?";
@@ -707,7 +847,8 @@ public class CalendarManager {
)) {
if (cursor != null) {
while (cursor.moveToNext()) {
- ids.add(cursor.getString(0)); // Legg til kalender-ID
+ ids.add(cursor.getString(0));
+ // Legg til kalender-ID
}
}
} catch (Exception e) {
@@ -718,7 +859,6 @@ public class CalendarManager {
public static List getDeviceEvents(Context context) {
List deviceEvents = new ArrayList<>();
-
if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR)
!= PackageManager.PERMISSION_GRANTED) {
return deviceEvents;
@@ -726,7 +866,6 @@ public class CalendarManager {
// 1. Finn først ID-ene til KBS-kalenderne
List kbsCalendarIds = getKbsCalendarIds(context);
-
// Hvis ingen kbs-kalendere finnes på telefonen, returner tom liste
if (kbsCalendarIds.isEmpty()) {
return deviceEvents;
@@ -736,7 +875,6 @@ public class CalendarManager {
long now = System.currentTimeMillis();
long startMillis = now - (365L * 24 * 60 * 60 * 1000);
long endMillis = now + (365L * 24 * 60 * 60 * 1000);
-
String[] projection = new String[]{
CalendarContract.Events.TITLE,
CalendarContract.Events.DTSTART,
@@ -759,7 +897,6 @@ public class CalendarManager {
}
}
selection.append(") AND ");
-
selection.append(CalendarContract.Events.DTSTART).append(" >= ? AND ");
selection.append(CalendarContract.Events.DTSTART).append(" <= ?");
@@ -767,7 +904,6 @@ public class CalendarManager {
selectionArgsList.add(String.valueOf(endMillis));
String[] selectionArgs = selectionArgsList.toArray(new String[0]);
-
try (Cursor cursor = context.getContentResolver().query(
CalendarContract.Events.CONTENT_URI,
projection,
@@ -777,7 +913,6 @@ public class CalendarManager {
)) {
if (cursor != null) {
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
-
while (cursor.moveToNext()) {
String title = cursor.getString(0);
long dtStart = cursor.getLong(1);
@@ -785,7 +920,6 @@ public class CalendarManager {
String desc = cursor.getString(3);
String loc = cursor.getString(4);
int allDay = cursor.getInt(5);
-
String rawStart;
String rawEnd;
@@ -809,14 +943,11 @@ public class CalendarManager {
return deviceEvents;
}
- // --- (formatEventForUI og mergeAndSort er uendret fra forrige versjon) ---
public static void formatEventForUI(CalendarEvent event) {
if (event.getRawDate() == null) return;
-
SimpleDateFormat outputDay = new SimpleDateFormat("dd", Locale.getDefault());
SimpleDateFormat outputMonth = new SimpleDateFormat("MMM", new Locale("no", "NO"));
SimpleDateFormat outputTime = new SimpleDateFormat("HH:mm", Locale.getDefault());
-
outputMonth.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
outputTime.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
@@ -826,7 +957,6 @@ public class CalendarManager {
boolean isAllDay = false;
String raw = event.getRawDate();
-
if (raw.length() == 10 && !raw.contains("T") && !raw.contains(" ")) {
SimpleDateFormat shortFmt = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
date = shortFmt.parse(raw);
@@ -884,7 +1014,6 @@ public class CalendarManager {
String d2 = e2.getRawDate() != null ? e2.getRawDate() : "";
return d1.compareTo(d2);
});
-
return all;
}
}
@@ -2946,6 +3075,69 @@ public class FormSubmission {
}
}
+============================================================
+FILSTI: app\src\main\java\com\kbs\kbsintranett\GoogleCalendarModels.java
+============================================================
+package com.kbs.kbsintranett;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.List;
+
+/**
+ * Hjelpeklasser for å parse JSON direkte fra Google Calendar API v3.
+ */
+public class GoogleCalendarModels {
+
+ public static class Response {
+ @SerializedName("items")
+ public List- items;
+ }
+
+ public static class Item {
+ @SerializedName("summary")
+ public String summary;
+
+ @SerializedName("description")
+ public String description;
+
+ @SerializedName("location")
+ public String location;
+
+ @SerializedName("start")
+ public TimePoint start;
+
+ @SerializedName("end")
+ public TimePoint end;
+
+ @SerializedName("reminders")
+ public Reminders reminders;
+ }
+
+ public static class TimePoint {
+ @SerializedName("dateTime")
+ public String dateTime; // Format: 2025-12-15T10:00:00+01:00
+
+ @SerializedName("date")
+ public String date; // Format: 2025-12-15 (for heldags)
+ }
+
+ public static class Reminders {
+ @SerializedName("useDefault")
+ public boolean useDefault;
+
+ @SerializedName("overrides")
+ public List overrides;
+ }
+
+ public static class Override {
+ @SerializedName("method")
+ public String method; // f.eks "popup"
+
+ @SerializedName("minutes")
+ public int minutes;
+ }
+}
+
============================================================
FILSTI: app\src\main\java\com\kbs\kbsintranett\GravityEntryResponse.java
============================================================
@@ -3767,7 +3959,6 @@ public class HomeFragment extends Fragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
// Håndter svar på kalendertillatelse
requestPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
@@ -3778,7 +3969,6 @@ public class HomeFragment extends Fragment {
}
}
);
-
// Start bakgrunnsjobb for varsling (kjører hver 15. minutt)
startNotificationWorker();
}
@@ -3792,7 +3982,6 @@ public class HomeFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
-
// 0. Profil-knapp
View profileBtn = view.findViewById(R.id.btn_profile);
if (profileBtn != null) {
@@ -3804,7 +3993,6 @@ public class HomeFragment extends Fragment {
calendarRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
// Sett tom adapter midlertidig
calendarRecycler.setAdapter(new CalendarAdapter(new ArrayList<>(), event -> {}));
-
// "Se alle" knapp for kalender
TextView viewAllCalendar = view.findViewById(R.id.btn_view_all_calendar);
if (viewAllCalendar != null) {
@@ -3832,7 +4020,6 @@ public class HomeFragment extends Fragment {
newsRecycler.setNestedScrollingEnabled(false);
// Sett tom adapter midlertidig
newsRecycler.setAdapter(new NewsAdapter(new ArrayList<>(), item -> {}));
-
// "Se alle" knapp for nyheter
TextView viewAllNews = view.findViewById(R.id.btn_view_all_news);
if (viewAllNews != null) {
@@ -3848,26 +4035,23 @@ public class HomeFragment extends Fragment {
// 1. Hent personlige hendelser først (fra CalendarManager)
List deviceEvents = CalendarManager.getDeviceEvents(getContext());
- // 2. Hent API-hendelser fra WordPress
- WordPressApiService apiService = RetrofitClient.getApiService();
- apiService.getCalendarEvents().enqueue(new Callback
>() {
+ // 2. Hent API-hendelser DIREKTE fra Google
+ String url = CalendarManager.getGoogleCalendarUrl();
+ RetrofitClient.getApiService().getDirectGoogleEvents(url).enqueue(new Callback() {
@Override
- public void onResponse(Call> call, Response> response) {
+ public void onResponse(Call call, Response response) {
if (!isAdded()) return;
List apiEvents = new ArrayList<>();
if (response.isSuccessful() && response.body() != null) {
- for (CalendarEvent e : response.body()) {
- CalendarManager.formatEventForUI(e); // Formatér datoer
- apiEvents.add(e);
- }
+ // Konverter fra Google-modell til KBS-modell
+ apiEvents = CalendarManager.convertGoogleResponse(response.body());
}
// 3. Flett listene (API + Personlig) og sorter
List merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents);
// 4. Filtrer ut hendelser som har passert (vis kun fremtidige + i dag)
- // (CalendarManager henter 1 år bakover, så vi må filtrere for "Topp 5 kommende")
List upcomingEvents = new ArrayList<>();
String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
@@ -3890,14 +4074,12 @@ public class HomeFragment extends Fragment {
}
@Override
- public void onFailure(Call> call, Throwable t) {
+ public void onFailure(Call call, Throwable t) {
if (!isAdded()) return;
// Hvis API feiler, vis bare personlige events hvis vi har noen
if (!deviceEvents.isEmpty()) {
List top5 = new ArrayList<>();
- // Filtrer og plukk topp 5 fra lokale også
for(int i=0; i {
CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
sheet.show(getParentFragmentManager(), "CalendarDetails");
@@ -3931,7 +4113,8 @@ public class HomeFragment extends Fragment {
for (WpPost post : wpPosts) {
try {
Date date = rawFormat.parse(post.date);
- post.date = targetFormat.format(date); // Setter pen dato
+ post.date = targetFormat.format(date);
+ // Setter pen dato
} catch (Exception e) {}
}
@@ -4314,7 +4497,14 @@ FILSTI: app\src\main\java\com\kbs\kbsintranett\MainActivity.java
============================================================
package com.kbs.kbsintranett;
+import android.app.AlarmManager;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
+import android.provider.Settings;
import android.util.Log;
import android.view.View;
@@ -4322,6 +4512,8 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.NavigationUI;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkManager;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
@@ -4346,6 +4538,7 @@ public class MainActivity extends AppCompatActivity {
bottomNav = findViewById(R.id.bottom_nav_view);
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
.findFragmentById(R.id.nav_host_fragment);
+
if (navHostFragment != null) {
navController = navHostFragment.getNavController();
NavigationUI.setupWithNavController(bottomNav, navController);
@@ -4361,10 +4554,50 @@ public class MainActivity extends AppCompatActivity {
});
}
+ // --- NYTT: Sjekk tillatelse for nøyaktige alarmer (Android 12+) ---
+ checkExactAlarmPermission();
+
// 2. Start Silent Sign-In ved oppstart
checkLoginState();
}
+ /**
+ * Sjekker om appen har lov til å sette nøyaktige alarmer (SCHEDULE_EXACT_ALARM).
+ * Hvis ikke, spør brukeren om å gå til innstillinger.
+ */
+ private void checkExactAlarmPermission() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ if (alarmManager != null && !alarmManager.canScheduleExactAlarms()) {
+ // Vi mangler tillatelse. Vis dialog.
+ new AlertDialog.Builder(this)
+ .setTitle("Varslingstillatelse kreves")
+ .setMessage("For at kalenderen skal kunne varsle deg nøyaktig når et møte starter, må du gi appen tilgang til å sette alarmer.")
+ .setPositiveButton("Gå til Innstillinger", (dialog, which) -> {
+ Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
+ intent.setData(Uri.parse("package:" + getPackageName()));
+ startActivity(intent);
+ })
+ .setNegativeButton("Senere", null)
+ .show();
+ } else {
+ // Vi har tillatelse (eller er på eldre Android). Kjør logikk!
+ runNotificationWorker();
+ }
+ } else {
+ // Eldre Android-versjoner trenger ikke denne tillatelsen
+ runNotificationWorker();
+ }
+ }
+
+ private void runNotificationWorker() {
+ // --- DEBUG: TVING KJØRING AV KALENDER-SJEKK ---
+ // Denne linjen kjører NotificationWorker umiddelbart ved oppstart for feilsøking.
+ // Fjern eller kommenter ut denne når testingen er ferdig.
+ WorkManager.getInstance(this).enqueue(OneTimeWorkRequest.from(NotificationWorker.class));
+ // ----------------------------------------------
+ }
+
private void checkLoginState() {
GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
if (account == null) {
@@ -4780,13 +5013,13 @@ FILSTI: app\src\main\java\com\kbs\kbsintranett\NotificationWorker.java
============================================================
package com.kbs.kbsintranett;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
import android.content.Context;
-import android.content.SharedPreferences;
+import android.content.Intent;
import android.os.Build;
+import android.util.Log;
import androidx.annotation.NonNull;
-import androidx.core.app.NotificationCompat;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import java.io.IOException;
@@ -4798,8 +5031,7 @@ import retrofit2.Response;
public class NotificationWorker extends Worker {
- private static final String CHANNEL_ID = "kbs_calendar_channel";
- private static final String PREFS_NAME = "KBSNotificationPrefs";
+ private static final String TAG = "KBS_DEBUG";
public NotificationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
@@ -4808,73 +5040,109 @@ public class NotificationWorker extends Worker {
@NonNull
@Override
public Result doWork() {
- // Dette kjører i bakgrunnen
+ Log.d(TAG, "NotificationWorker: Starter sjekk av kalender...");
+
try {
- // Hent events synkront (ikke enqueue)
- Response> response = RetrofitClient.getApiService().getCalendarEvents().execute();
+ String url = CalendarManager.getGoogleCalendarUrl();
+ // Hent events synkront
+ Response response = RetrofitClient.getApiService().getDirectGoogleEvents(url).execute();
if (response.isSuccessful() && response.body() != null) {
- checkAndNotify(response.body());
+ List events = CalendarManager.convertGoogleResponse(response.body());
+ scheduleAlarms(events);
return Result.success();
} else {
+ Log.e(TAG, "NotificationWorker: API-kall feilet. Kode: " + response.code());
return Result.retry();
}
} catch (IOException e) {
+ Log.e(TAG, "NotificationWorker: Nettverksfeil", e);
return Result.retry();
}
}
- private void checkAndNotify(List events) {
- SharedPreferences prefs = getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
- long now = System.currentTimeMillis();
- long fifteenMinutes = 15 * 60 * 1000;
+ private void scheduleAlarms(List events) {
+ Context context = getApplicationContext();
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ // Sjekk rettigheter (Android 12+)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ if (!alarmManager.canScheduleExactAlarms()) {
+ Log.e(TAG, "NotificationWorker: MANGLER fortsatt tillatelse! Gå til Innstillinger -> Apper -> KBS -> Alarmer og påminnelser.");
+ return;
+ }
+ }
+
+ long now = System.currentTimeMillis();
+ // Bruker en parser som er litt mer fleksibel for datoer
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
- SimpleDateFormat sqlFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
+
+ int countSet = 0;
for (CalendarEvent event : events) {
try {
- Date eventDate;
- if (event.getRawDate().contains("T")) eventDate = isoFormat.parse(event.getRawDate());
- else eventDate = sqlFormat.parse(event.getRawDate());
+ // Hopp over heldagshendelser (dato uten klokkeslett)
+ if (event.getRawDate().length() == 10) continue;
+
+ Date eventDate = null;
+ // Enkel parsing. Merk: Google sender med tidssone (+01:00),
+ // men SimpleDateFormat uten 'X' vil parse dette som lokal tid hvis formatet stemmer.
+ // For optimal tidssone-håndtering burde vi brukt java.time (Android 8+),
+ // men dette fungerer greit så lenge telefonen er i samme sone som kalenderen.
+ if (event.getRawDate().contains("T")) {
+ // Kutter vekk tidssone-offset for enkel parsing til lokal tid
+ String raw = event.getRawDate();
+ if (raw.length() > 19) raw = raw.substring(0, 19);
+ eventDate = isoFormat.parse(raw);
+ }
if (eventDate == null) continue;
- long diff = eventDate.getTime() - now;
+ // Beregn når alarmen skal gå
+ long triggerTime = eventDate.getTime() - (event.getReminderMinutes() * 60 * 1000L);
- // Hvis eventet starter innen de neste 30 min, og ikke allerede varslet
- if (diff > 0 && diff < (30 * 60 * 1000)) {
- String eventId = event.getTitle() + event.getRawDate(); // Enkel ID
- boolean alreadyNotified = prefs.getBoolean(eventId, false);
+ // Vi setter alarmen hvis tidspunktet er i fremtiden
+ // Vi sjekker også at det ikke er mer enn 24 timer frem i tid (for å spare ressurser)
+ if (triggerTime > now && triggerTime < (now + 24 * 60 * 60 * 1000L)) {
- if (!alreadyNotified) {
- sendNotification(event.getTitle(), "Starter kl " + event.getTime());
- // Lagre at vi har varslet
- prefs.edit().putBoolean(eventId, true).apply();
+ // Lag en unik ID for alarmen
+ String uniqueIdString = event.getTitle() + event.getRawDate();
+ int alarmId = uniqueIdString.hashCode();
+
+ Intent intent = new Intent(context, AlarmReceiver.class);
+ intent.putExtra("TITLE", event.getTitle());
+ intent.putExtra("MESSAGE", "Starter kl " + event.getTime());
+ intent.putExtra("ID", alarmId);
+
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ context,
+ alarmId,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
+ );
+
+ // VIKTIG ENDRING: Vi setter alarmen PÅ NYTT hver gang.
+ // AlarmManager overskriver automatisk hvis ID er lik.
+ // Dette sikrer at alarmen faktisk ligger der, selv etter omstart av tlf.
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent);
+ } else {
+ alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent);
}
+
+ Log.i(TAG, ">>> ALARM SATT (Oppdatert): " + event.getTitle() + " -> Skal ringe: " + new Date(triggerTime));
+ countSet++;
}
+
} catch (Exception e) {
- e.printStackTrace();
+ Log.e(TAG, "Feil ved behandling av event: " + event.getTitle(), e);
}
}
- }
- private void sendNotification(String title, String content) {
- NotificationManager manager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "KBS Kalender", NotificationManager.IMPORTANCE_HIGH);
- manager.createNotificationChannel(channel);
+ if (countSet == 0) {
+ Log.d(TAG, "Ingen kommende alarmer (innenfor neste 24t) funnet akkurat nå.");
}
-
- NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
- .setSmallIcon(R.mipmap.ic_launcher) // Sørg for at du har et ikon her
- .setContentTitle(title)
- .setContentText(content)
- .setPriority(NotificationCompat.PRIORITY_HIGH)
- .setAutoCancel(true);
-
- manager.notify((int) System.currentTimeMillis(), builder.build());
}
}
@@ -5247,6 +5515,7 @@ import retrofit2.http.Multipart;
import retrofit2.http.Part;
import retrofit2.http.PartMap;
import retrofit2.http.Query;
+import retrofit2.http.Url; // NY IMPORT
public interface WordPressApiService {
@GET("wp-json/wp/v2/posts?per_page=10&_embed")
@@ -5272,9 +5541,14 @@ public interface WordPressApiService {
@Part List files
);
+ // ENDRET: Denne brukes ikke lenger for kalender, men beholdes for bakoverkompatibilitet
@GET("wp-json/kbs/v1/calendar/events")
Call> getCalendarEvents();
+ // NY: Direkte kall mot Google (bruker @Url for å override base URL)
+ @GET
+ Call getDirectGoogleEvents(@Url String fullUrl);
+
@GET("wp-json/gf/v2/entries")
Call getEntries(
@Query("form_ids") int formId,