diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index b6bc734..5e26931 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -16,6 +16,11 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
+ // NYTT: Dette må til for å kunne bruke BuildConfig.DEBUG i koden
+ buildFeatures {
+ buildConfig = true
+ }
+
buildTypes {
release {
isMinifyEnabled = false
@@ -26,7 +31,6 @@ android {
}
}
compileOptions {
- // ENDRET: Oppgradert til Java 11 for å fikse build warnings
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 95307a6..93a976a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -11,11 +11,15 @@
+
+
+
+ android:exported="true"
+ android:theme="@style/Theme.KBSIntranett">
@@ -39,7 +44,10 @@
-
+
= Build.VERSION_CODES.O) {
- NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "KBS Kalender", NotificationManager.IMPORTANCE_HIGH);
- channel.setDescription("Varsler for kalenderhendelser");
- manager.createNotificationChannel(channel);
+ // Sjekk rettigheter for Android 13+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
+ // Vi kan ikke vise varsel uten rettighet.
+ return;
+ }
}
- Intent openAppIntent = new Intent(context, MainActivity.class);
- openAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ // Intent for hva som skjer når man trykker på varselet (åpne appen)
+ Intent tapIntent = new Intent(context, MainActivity.class);
+ tapIntent.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
+ tapIntent,
+ PendingIntent.FLAG_IMMUTABLE
);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
- .setSmallIcon(R.mipmap.ic_launcher)
+ .setSmallIcon(R.drawable.ic_launcher_foreground) // Pass på at du har et ikon her, ellers bruk 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);
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
+ notificationManager.notify(notificationId, builder.build());
+ }
+
+ private void createNotificationChannel(Context context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ int importance = NotificationManager.IMPORTANCE_HIGH;
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, importance);
+ channel.setDescription("Varsler for kalenderhendelser i KBS Intranett");
+
+ NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
+ if (notificationManager != null) {
+ notificationManager.createNotificationChannel(channel);
+ }
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java b/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java
index 2c5a5ba..1398c51 100644
--- a/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java
+++ b/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java
@@ -4,6 +4,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.provider.CalendarContract;
+import android.util.Log;
import androidx.core.content.ContextCompat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -17,12 +18,10 @@ 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"));
@@ -34,10 +33,9 @@ public class CalendarManager {
+ "&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
+ + "&timeMin=" + timeMin;
}
- // 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;
@@ -53,50 +51,69 @@ public class CalendarManager {
if (item.start != null) {
if (item.start.dateTime != null) start = item.start.dateTime;
- else start = item.start.date; // Heldags
+ else start = item.start.date;
}
if (item.end != null) {
if (item.end.dateTime != null) end = item.end.dateTime;
- else end = item.end.date; // Heldags
+ else end = item.end.date;
}
- // Varslings-logikk (Henter de sanne innstillingene)
+ // --- SPY LOGGING (Se i Logcat etter KBS_DEBUG) ---
+ if (title.toLowerCase().contains("test")) {
+ Log.d("KBS_DEBUG", "--------------------------------------------------");
+ Log.d("KBS_DEBUG", "ANALYSERER EVENT: " + title);
+ Log.d("KBS_DEBUG", " Starttid: " + start);
+
+ if (item.reminders != null) {
+ Log.d("KBS_DEBUG", " useDefault: " + item.reminders.useDefault);
+
+ if (item.reminders.overrides != null && !item.reminders.overrides.isEmpty()) {
+ Log.d("KBS_DEBUG", " Fant " + item.reminders.overrides.size() + " overstyringer:");
+ for (GoogleCalendarModels.Override override : item.reminders.overrides) {
+ Log.d("KBS_DEBUG", " -> Metode: '" + override.method + "', Minutter: " + override.minutes);
+ }
+ } else {
+ Log.d("KBS_DEBUG", " Ingen 'overrides' (spesifikke varsler) funnet i listen fra Google.");
+ }
+ } else {
+ Log.d("KBS_DEBUG", " Ingen 'reminders'-seksjon funnet i JSON-responsen.");
+ }
+ Log.d("KBS_DEBUG", "--------------------------------------------------");
+ }
+ // -------------------------------------------------
+
+ // Varslings-logikk
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
- }
- }
+ reminderMinutes = 15;
+ } else if (item.reminders.overrides != null && !item.reminders.overrides.isEmpty()) {
+ // Vi tar den første vi finner for å se om det fanger opp dine 10/26 minutter
+ reminderMinutes = item.reminders.overrides.get(0).minutes;
}
}
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) ---
+ // --- RESTERENDE KODE (UENDRET) ---
- // NY HJELPEMETODE: Finner ID-ene til kalendere som tilhører @kbs.no
private static List getKbsCalendarIds(Context context) {
List ids = new ArrayList<>();
+ if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) {
+ return ids;
+ }
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 ?";
String[] selectionArgs = new String[] {"%@kbs.no"};
@@ -110,7 +127,6 @@ public class CalendarManager {
if (cursor != null) {
while (cursor.moveToNext()) {
ids.add(cursor.getString(0));
- // Legg til kalender-ID
}
}
} catch (Exception e) {
@@ -126,17 +142,15 @@ public class CalendarManager {
return deviceEvents;
}
- // 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;
}
- // Hent events fra 1 år tilbake og 1 år frem
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,
@@ -146,8 +160,6 @@ public class CalendarManager {
CalendarContract.Events.ALL_DAY
};
- // 2. Bygg opp spørringen for å filtrere på disse ID-ene
- // Resultatet blir noe sånt som: "((calendar_id = ?) OR (calendar_id = ?)) AND dtstart >= ? AND dtstart <= ?"
StringBuilder selection = new StringBuilder("(");
List selectionArgsList = new ArrayList<>();
@@ -166,6 +178,7 @@ 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,
@@ -182,6 +195,7 @@ public class CalendarManager {
String desc = cursor.getString(3);
String loc = cursor.getString(4);
int allDay = cursor.getInt(5);
+
String rawStart;
String rawEnd;
@@ -207,9 +221,11 @@ public class CalendarManager {
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"));
@@ -219,6 +235,7 @@ 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);
@@ -276,6 +293,7 @@ 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/KbsApplication.java b/app/src/main/java/com/kbs/kbsintranett/KbsApplication.java
new file mode 100644
index 0000000..1879bdb
--- /dev/null
+++ b/app/src/main/java/com/kbs/kbsintranett/KbsApplication.java
@@ -0,0 +1,35 @@
+package com.kbs.kbsintranett;
+
+import android.app.Application;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.os.Build;
+
+public class KbsApplication extends Application {
+
+ public static final String CHANNEL_ID = "kbs_calendar_channel";
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ createNotificationChannel();
+ }
+
+ private void createNotificationChannel() {
+ // Vi oppretter kanalen her ved oppstart, så den er klar uansett om
+ // det er MainActivity eller en bakgrunnsjobb som trenger den.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(
+ CHANNEL_ID,
+ "KBS Kalendervarsler",
+ NotificationManager.IMPORTANCE_HIGH
+ );
+ channel.setDescription("Varsler for kalenderhendelser");
+
+ NotificationManager manager = getSystemService(NotificationManager.class);
+ if (manager != null) {
+ manager.createNotificationChannel(channel);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kbs/kbsintranett/MainActivity.java b/app/src/main/java/com/kbs/kbsintranett/MainActivity.java
index b940995..159d2a0 100644
--- a/app/src/main/java/com/kbs/kbsintranett/MainActivity.java
+++ b/app/src/main/java/com/kbs/kbsintranett/MainActivity.java
@@ -1,9 +1,13 @@
package com.kbs.kbsintranett;
+import android.Manifest;
import android.app.AlarmManager;
import android.app.AlertDialog;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -11,11 +15,15 @@ import android.provider.Settings;
import android.util.Log;
import android.view.View;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.content.ContextCompat;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.NavigationUI;
-import androidx.work.OneTimeWorkRequest;
+import androidx.work.ExistingPeriodicWorkPolicy;
+import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
@@ -24,31 +32,42 @@ import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.material.bottomnavigation.BottomNavigationView;
+import java.util.concurrent.TimeUnit;
+
public class MainActivity extends AppCompatActivity {
- // VIKTIG: Erstatt denne med din Web Client ID
+ // VIKTIG: Sørg for at denne matcher den du har i Google Cloud Console
public static final String GOOGLE_WEB_CLIENT_ID = "738325360287-cidl3plnqv9ei74vm9vm5muustj6eenb.apps.googleusercontent.com";
+
private static final String TAG = "MainActivity";
private NavController navController;
private BottomNavigationView bottomNav;
+ // Launcher for å spørre om varslingstillatelse (Android 13+)
+ private ActivityResultLauncher requestPermissionLauncher;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
- // 1. Setup UI
+ // --- 1. SETUP UI & NAVIGASJON ---
+ // Sjekket activity_main.xml: ID er "bottom_nav_view"
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);
+ if (bottomNav != null) {
+ NavigationUI.setupWithNavController(bottomNav, navController);
+ }
// Skjul meny på login-skjerm
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
- // Sjekker mot R.id.navigation_login som er ID'en til fragmentet
+ if (bottomNav == null) return;
+
if (destination.getId() == R.id.navigation_login) {
bottomNav.setVisibility(View.GONE);
} else {
@@ -57,50 +76,32 @@ public class MainActivity extends AppCompatActivity {
});
}
- // --- NYTT: Sjekk tillatelse for nøyaktige alarmer (Android 12+) ---
+ // --- 2. VARSLINGSOPPSETT (NYTT) ---
+ createNotificationChannel();
+
+ // Initialiser permission launcher for varsler
+ requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
+ if (isGranted) {
+ Log.d(TAG, "Varslingstillatelse gitt!");
+ } else {
+ Log.e(TAG, "Varslingstillatelse avslått. Bruker får ikke kalendervarsler.");
+ }
+ });
+
+ // Sjekk tillatelser (både Varsler og Alarmer)
+ checkNotificationPermission();
checkExactAlarmPermission();
- // 2. Start Silent Sign-In ved oppstart
+ // Start bakgrunnsjobben for kalenderen
+ scheduleCalendarWork();
+
+ // --- 3. AUTENTISERING (GAMMELT) ---
checkLoginState();
}
/**
- * Sjekker om appen har lov til å sette nøyaktige alarmer (SCHEDULE_EXACT_ALARM).
- * Hvis ikke, spør brukeren om å gå til innstillinger.
+ * Sjekker om brukeren er logget inn med Google, og gjør en silent refresh mot WP.
*/
- 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) {
@@ -130,10 +131,8 @@ public class MainActivity extends AppCompatActivity {
@Override
public void onSuccess(String role) {
Log.d(TAG, "Silent login fullført. Rolle: " + role);
- // Gå videre til Home hvis vi står på Login
if (navController != null && navController.getCurrentDestination() != null &&
navController.getCurrentDestination().getId() == R.id.navigation_login) {
- // Denne aksjonen finnes i mobile_navigation.xml
navController.navigate(R.id.action_login_to_home);
}
}
@@ -155,11 +154,69 @@ public class MainActivity extends AppCompatActivity {
private void navigateToLogin() {
if (navController != null) {
if (navController.getCurrentDestination() != null &&
- // Sjekker mot R.id.navigation_login som er ID'en til fragmentet
navController.getCurrentDestination().getId() != R.id.navigation_login) {
- // Denne ID'en finnes i mobile_navigation.xml
navController.navigate(R.id.navigation_login);
}
}
}
+
+ // --- NYE HJELPEMETODER FOR VARSLING ---
+
+ private void createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ CharSequence name = "KBS Kalendervarsler";
+ String description = "Varsler for kalenderhendelser";
+ int importance = NotificationManager.IMPORTANCE_HIGH;
+ NotificationChannel channel = new NotificationChannel("kbs_calendar_channel", name, importance);
+ channel.setDescription(description);
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ if (notificationManager != null) {
+ notificationManager.createNotificationChannel(channel);
+ }
+ }
+ }
+
+ private void checkNotificationPermission() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
+ requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);
+ }
+ }
+ }
+
+ /**
+ * 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()) {
+ 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();
+ }
+ }
+ }
+
+ /**
+ * Starter WorkManager som sjekker kalenderen hvert 15. minutt.
+ */
+ private void scheduleCalendarWork() {
+ PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(NotificationWorker.class, 15, TimeUnit.MINUTES)
+ .build();
+
+ WorkManager.getInstance(this).enqueueUniquePeriodicWork(
+ "KbsCalendarWork",
+ ExistingPeriodicWorkPolicy.UPDATE,
+ workRequest
+ );
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/kbs/kbsintranett/NotificationWorker.java b/app/src/main/java/com/kbs/kbsintranett/NotificationWorker.java
index 9b59010..df600de 100644
--- a/app/src/main/java/com/kbs/kbsintranett/NotificationWorker.java
+++ b/app/src/main/java/com/kbs/kbsintranett/NotificationWorker.java
@@ -31,7 +31,6 @@ public class NotificationWorker extends Worker {
try {
String url = CalendarManager.getGoogleCalendarUrl();
- // Hent events synkront
Response response = RetrofitClient.getApiService().getDirectGoogleEvents(url).execute();
if (response.isSuccessful() && response.body() != null) {
@@ -52,32 +51,23 @@ public class NotificationWorker extends Worker {
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.");
+ Log.e(TAG, "NotificationWorker: MANGLER fortsatt tillatelse!");
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());
-
int countSet = 0;
for (CalendarEvent event : events) {
try {
- // 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);
@@ -85,14 +75,26 @@ public class NotificationWorker extends Worker {
if (eventDate == null) continue;
- // Beregn når alarmen skal gå
long triggerTime = eventDate.getTime() - (event.getReminderMinutes() * 60 * 1000L);
- // 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)
+ // --- DETALJERT LOGGING FOR Å FEILSØKE ---
+ if (event.getTitle().toLowerCase().contains("test")) {
+ Log.d(TAG, "SJEKKER TEST-EVENT:");
+ Log.d(TAG, " Start: " + eventDate);
+ Log.d(TAG, " Varsling valgt: " + event.getReminderMinutes() + " min før");
+ Log.d(TAG, " Alarmtid: " + new Date(triggerTime));
+ Log.d(TAG, " Nå: " + new Date(now));
+
+ if (triggerTime > now) {
+ Log.d(TAG, " Status: I FREMTIDEN (Alarm skal settes)");
+ } else {
+ Log.d(TAG, " Status: I FORTIDEN (Hoppes over)");
+ }
+ }
+ // ----------------------------------------
+
if (triggerTime > now && triggerTime < (now + 24 * 60 * 60 * 1000L)) {
- // Lag en unik ID for alarmen
String uniqueIdString = event.getTitle() + event.getRawDate();
int alarmId = uniqueIdString.hashCode();
@@ -108,17 +110,13 @@ public class NotificationWorker extends Worker {
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));
+ Log.i(TAG, ">>> ALARM SATT: " + event.getTitle());
countSet++;
}
@@ -128,7 +126,7 @@ public class NotificationWorker extends Worker {
}
if (countSet == 0) {
- Log.d(TAG, "Ingen kommende alarmer (innenfor neste 24t) funnet akkurat nå.");
+ Log.d(TAG, "Ingen nye alarmer satt (ingen hendelser innenfor neste 24t).");
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/kbs/kbsintranett/RetrofitClient.java b/app/src/main/java/com/kbs/kbsintranett/RetrofitClient.java
index de6008e..c6676c1 100644
--- a/app/src/main/java/com/kbs/kbsintranett/RetrofitClient.java
+++ b/app/src/main/java/com/kbs/kbsintranett/RetrofitClient.java
@@ -11,7 +11,7 @@ import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
-import okhttp3.logging.HttpLoggingInterceptor; // NY IMPORT
+import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
@@ -22,12 +22,16 @@ public class RetrofitClient {
public static WordPressApiService getApiService() {
if (retrofit == null) {
- // NYTT: Logging Interceptor
+ // ENDRET: Redusert loggnivå fra BODY til BASIC for å unngå spam i Logcat
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
- logging.setLevel(HttpLoggingInterceptor.Level.BODY); // Logger ALT (Body, Headers)
+ if (BuildConfig.DEBUG) {
+ logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
+ } else {
+ logging.setLevel(HttpLoggingInterceptor.Level.NONE);
+ }
OkHttpClient client = new OkHttpClient.Builder()
- .addInterceptor(logging) // Legg til loggingen her
+ .addInterceptor(logging)
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
@@ -46,7 +50,7 @@ public class RetrofitClient {
Gson gson = new GsonBuilder()
.registerTypeAdapter(new TypeToken>(){}.getType(), new ChoicesAdapter())
- .setLenient() // NYTT: Gjør parsing litt mer tilgivende
+ .setLenient()
.create();
retrofit = new Retrofit.Builder()
diff --git a/hele_prosjektet.txt b/hele_prosjektet.txt
index 746c51f..b4c730f 100644
--- a/hele_prosjektet.txt
+++ b/hele_prosjektet.txt
@@ -59,6 +59,11 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
+ // NYTT: Dette må til for å kunne bruke BuildConfig.DEBUG i koden
+ buildFeatures {
+ buildConfig = true
+ }
+
buildTypes {
release {
isMinifyEnabled = false
@@ -69,7 +74,6 @@ android {
}
}
compileOptions {
- // ENDRET: Oppgradert til Java 11 for å fikse build warnings
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
@@ -175,11 +179,15 @@ FILSTI: app\src\main\AndroidManifest.xml
+
+
+
+ android:exported="true"
+ android:theme="@style/Theme.KBSIntranett">
@@ -203,7 +212,10 @@ FILSTI: app\src\main\AndroidManifest.xml
-
+
= Build.VERSION_CODES.O) {
- NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "KBS Kalender", NotificationManager.IMPORTANCE_HIGH);
- channel.setDescription("Varsler for kalenderhendelser");
- manager.createNotificationChannel(channel);
+ // Sjekk rettigheter for Android 13+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
+ // Vi kan ikke vise varsel uten rettighet.
+ return;
+ }
}
- Intent openAppIntent = new Intent(context, MainActivity.class);
- openAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ // Intent for hva som skjer når man trykker på varselet (åpne appen)
+ Intent tapIntent = new Intent(context, MainActivity.class);
+ tapIntent.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
+ tapIntent,
+ PendingIntent.FLAG_IMMUTABLE
);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
- .setSmallIcon(R.mipmap.ic_launcher)
+ .setSmallIcon(R.drawable.ic_launcher_foreground) // Pass på at du har et ikon her, ellers bruk 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);
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
+ notificationManager.notify(notificationId, builder.build());
+ }
+
+ private void createNotificationChannel(Context context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ int importance = NotificationManager.IMPORTANCE_HIGH;
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, importance);
+ channel.setDescription("Varsler for kalenderhendelser i KBS Intranett");
+
+ NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
+ if (notificationManager != null) {
+ notificationManager.createNotificationChannel(channel);
+ }
}
}
}
@@ -742,6 +763,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.provider.CalendarContract;
+import android.util.Log;
import androidx.core.content.ContextCompat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -755,12 +777,10 @@ 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"));
@@ -772,10 +792,9 @@ public class CalendarManager {
+ "&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
+ + "&timeMin=" + timeMin;
}
- // 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;
@@ -791,50 +810,69 @@ public class CalendarManager {
if (item.start != null) {
if (item.start.dateTime != null) start = item.start.dateTime;
- else start = item.start.date; // Heldags
+ else start = item.start.date;
}
if (item.end != null) {
if (item.end.dateTime != null) end = item.end.dateTime;
- else end = item.end.date; // Heldags
+ else end = item.end.date;
}
- // Varslings-logikk (Henter de sanne innstillingene)
+ // --- SPY LOGGING (Se i Logcat etter KBS_DEBUG) ---
+ if (title.toLowerCase().contains("test")) {
+ Log.d("KBS_DEBUG", "--------------------------------------------------");
+ Log.d("KBS_DEBUG", "ANALYSERER EVENT: " + title);
+ Log.d("KBS_DEBUG", " Starttid: " + start);
+
+ if (item.reminders != null) {
+ Log.d("KBS_DEBUG", " useDefault: " + item.reminders.useDefault);
+
+ if (item.reminders.overrides != null && !item.reminders.overrides.isEmpty()) {
+ Log.d("KBS_DEBUG", " Fant " + item.reminders.overrides.size() + " overstyringer:");
+ for (GoogleCalendarModels.Override override : item.reminders.overrides) {
+ Log.d("KBS_DEBUG", " -> Metode: '" + override.method + "', Minutter: " + override.minutes);
+ }
+ } else {
+ Log.d("KBS_DEBUG", " Ingen 'overrides' (spesifikke varsler) funnet i listen fra Google.");
+ }
+ } else {
+ Log.d("KBS_DEBUG", " Ingen 'reminders'-seksjon funnet i JSON-responsen.");
+ }
+ Log.d("KBS_DEBUG", "--------------------------------------------------");
+ }
+ // -------------------------------------------------
+
+ // Varslings-logikk
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
- }
- }
+ reminderMinutes = 15;
+ } else if (item.reminders.overrides != null && !item.reminders.overrides.isEmpty()) {
+ // Vi tar den første vi finner for å se om det fanger opp dine 10/26 minutter
+ reminderMinutes = item.reminders.overrides.get(0).minutes;
}
}
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) ---
+ // --- RESTERENDE KODE (UENDRET) ---
- // NY HJELPEMETODE: Finner ID-ene til kalendere som tilhører @kbs.no
private static List getKbsCalendarIds(Context context) {
List ids = new ArrayList<>();
+ if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) {
+ return ids;
+ }
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 ?";
String[] selectionArgs = new String[] {"%@kbs.no"};
@@ -848,7 +886,6 @@ public class CalendarManager {
if (cursor != null) {
while (cursor.moveToNext()) {
ids.add(cursor.getString(0));
- // Legg til kalender-ID
}
}
} catch (Exception e) {
@@ -864,17 +901,15 @@ public class CalendarManager {
return deviceEvents;
}
- // 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;
}
- // Hent events fra 1 år tilbake og 1 år frem
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,
@@ -884,8 +919,6 @@ public class CalendarManager {
CalendarContract.Events.ALL_DAY
};
- // 2. Bygg opp spørringen for å filtrere på disse ID-ene
- // Resultatet blir noe sånt som: "((calendar_id = ?) OR (calendar_id = ?)) AND dtstart >= ? AND dtstart <= ?"
StringBuilder selection = new StringBuilder("(");
List selectionArgsList = new ArrayList<>();
@@ -904,6 +937,7 @@ 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,
@@ -920,6 +954,7 @@ public class CalendarManager {
String desc = cursor.getString(3);
String loc = cursor.getString(4);
int allDay = cursor.getInt(5);
+
String rawStart;
String rawEnd;
@@ -945,9 +980,11 @@ public class CalendarManager {
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"));
@@ -957,6 +994,7 @@ 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);
@@ -1014,6 +1052,7 @@ public class CalendarManager {
String d2 = e2.getRawDate() != null ? e2.getRawDate() : "";
return d1.compareTo(d2);
});
+
return all;
}
}
@@ -4325,6 +4364,45 @@ public class InternalLinkMovementMethod extends LinkMovementMethod {
}
}
+============================================================
+FILSTI: app\src\main\java\com\kbs\kbsintranett\KbsApplication.java
+============================================================
+package com.kbs.kbsintranett;
+
+import android.app.Application;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.os.Build;
+
+public class KbsApplication extends Application {
+
+ public static final String CHANNEL_ID = "kbs_calendar_channel";
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ createNotificationChannel();
+ }
+
+ private void createNotificationChannel() {
+ // Vi oppretter kanalen her ved oppstart, så den er klar uansett om
+ // det er MainActivity eller en bakgrunnsjobb som trenger den.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(
+ CHANNEL_ID,
+ "KBS Kalendervarsler",
+ NotificationManager.IMPORTANCE_HIGH
+ );
+ channel.setDescription("Varsler for kalenderhendelser");
+
+ NotificationManager manager = getSystemService(NotificationManager.class);
+ if (manager != null) {
+ manager.createNotificationChannel(channel);
+ }
+ }
+ }
+}
+
============================================================
FILSTI: app\src\main\java\com\kbs\kbsintranett\LoginFragment.java
============================================================
@@ -4497,10 +4575,14 @@ FILSTI: app\src\main\java\com\kbs\kbsintranett\MainActivity.java
============================================================
package com.kbs.kbsintranett;
+import android.Manifest;
import android.app.AlarmManager;
import android.app.AlertDialog;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -4508,11 +4590,15 @@ import android.provider.Settings;
import android.util.Log;
import android.view.View;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.content.ContextCompat;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.NavigationUI;
-import androidx.work.OneTimeWorkRequest;
+import androidx.work.ExistingPeriodicWorkPolicy;
+import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
@@ -4521,31 +4607,42 @@ import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.material.bottomnavigation.BottomNavigationView;
+import java.util.concurrent.TimeUnit;
+
public class MainActivity extends AppCompatActivity {
- // VIKTIG: Erstatt denne med din Web Client ID
+ // VIKTIG: Sørg for at denne matcher den du har i Google Cloud Console
public static final String GOOGLE_WEB_CLIENT_ID = "SECRET.apps.googleusercontent.com";
+
private static final String TAG = "MainActivity";
private NavController navController;
private BottomNavigationView bottomNav;
+ // Launcher for å spørre om varslingstillatelse (Android 13+)
+ private ActivityResultLauncher requestPermissionLauncher;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
- // 1. Setup UI
+ // --- 1. SETUP UI & NAVIGASJON ---
+ // Sjekket activity_main.xml: ID er "bottom_nav_view"
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);
+ if (bottomNav != null) {
+ NavigationUI.setupWithNavController(bottomNav, navController);
+ }
// Skjul meny på login-skjerm
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
- // Sjekker mot R.id.navigation_login som er ID'en til fragmentet
+ if (bottomNav == null) return;
+
if (destination.getId() == R.id.navigation_login) {
bottomNav.setVisibility(View.GONE);
} else {
@@ -4554,50 +4651,32 @@ public class MainActivity extends AppCompatActivity {
});
}
- // --- NYTT: Sjekk tillatelse for nøyaktige alarmer (Android 12+) ---
+ // --- 2. VARSLINGSOPPSETT (NYTT) ---
+ createNotificationChannel();
+
+ // Initialiser permission launcher for varsler
+ requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
+ if (isGranted) {
+ Log.d(TAG, "Varslingstillatelse gitt!");
+ } else {
+ Log.e(TAG, "Varslingstillatelse avslått. Bruker får ikke kalendervarsler.");
+ }
+ });
+
+ // Sjekk tillatelser (både Varsler og Alarmer)
+ checkNotificationPermission();
checkExactAlarmPermission();
- // 2. Start Silent Sign-In ved oppstart
+ // Start bakgrunnsjobben for kalenderen
+ scheduleCalendarWork();
+
+ // --- 3. AUTENTISERING (GAMMELT) ---
checkLoginState();
}
/**
- * Sjekker om appen har lov til å sette nøyaktige alarmer (SCHEDULE_EXACT_ALARM).
- * Hvis ikke, spør brukeren om å gå til innstillinger.
+ * Sjekker om brukeren er logget inn med Google, og gjør en silent refresh mot WP.
*/
- 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) {
@@ -4627,10 +4706,8 @@ public class MainActivity extends AppCompatActivity {
@Override
public void onSuccess(String role) {
Log.d(TAG, "Silent login fullført. Rolle: " + role);
- // Gå videre til Home hvis vi står på Login
if (navController != null && navController.getCurrentDestination() != null &&
navController.getCurrentDestination().getId() == R.id.navigation_login) {
- // Denne aksjonen finnes i mobile_navigation.xml
navController.navigate(R.id.action_login_to_home);
}
}
@@ -4652,13 +4729,71 @@ public class MainActivity extends AppCompatActivity {
private void navigateToLogin() {
if (navController != null) {
if (navController.getCurrentDestination() != null &&
- // Sjekker mot R.id.navigation_login som er ID'en til fragmentet
navController.getCurrentDestination().getId() != R.id.navigation_login) {
- // Denne ID'en finnes i mobile_navigation.xml
navController.navigate(R.id.navigation_login);
}
}
}
+
+ // --- NYE HJELPEMETODER FOR VARSLING ---
+
+ private void createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ CharSequence name = "KBS Kalendervarsler";
+ String description = "Varsler for kalenderhendelser";
+ int importance = NotificationManager.IMPORTANCE_HIGH;
+ NotificationChannel channel = new NotificationChannel("kbs_calendar_channel", name, importance);
+ channel.setDescription(description);
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ if (notificationManager != null) {
+ notificationManager.createNotificationChannel(channel);
+ }
+ }
+ }
+
+ private void checkNotificationPermission() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
+ requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);
+ }
+ }
+ }
+
+ /**
+ * 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()) {
+ 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();
+ }
+ }
+ }
+
+ /**
+ * Starter WorkManager som sjekker kalenderen hvert 15. minutt.
+ */
+ private void scheduleCalendarWork() {
+ PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(NotificationWorker.class, 15, TimeUnit.MINUTES)
+ .build();
+
+ WorkManager.getInstance(this).enqueueUniquePeriodicWork(
+ "KbsCalendarWork",
+ ExistingPeriodicWorkPolicy.UPDATE,
+ workRequest
+ );
+ }
}
============================================================
@@ -5044,7 +5179,6 @@ public class NotificationWorker extends Worker {
try {
String url = CalendarManager.getGoogleCalendarUrl();
- // Hent events synkront
Response response = RetrofitClient.getApiService().getDirectGoogleEvents(url).execute();
if (response.isSuccessful() && response.body() != null) {
@@ -5065,32 +5199,23 @@ public class NotificationWorker extends Worker {
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.");
+ Log.e(TAG, "NotificationWorker: MANGLER fortsatt tillatelse!");
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());
-
int countSet = 0;
for (CalendarEvent event : events) {
try {
- // 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);
@@ -5098,14 +5223,26 @@ public class NotificationWorker extends Worker {
if (eventDate == null) continue;
- // Beregn når alarmen skal gå
long triggerTime = eventDate.getTime() - (event.getReminderMinutes() * 60 * 1000L);
- // 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)
+ // --- DETALJERT LOGGING FOR Å FEILSØKE ---
+ if (event.getTitle().toLowerCase().contains("test")) {
+ Log.d(TAG, "SJEKKER TEST-EVENT:");
+ Log.d(TAG, " Start: " + eventDate);
+ Log.d(TAG, " Varsling valgt: " + event.getReminderMinutes() + " min før");
+ Log.d(TAG, " Alarmtid: " + new Date(triggerTime));
+ Log.d(TAG, " Nå: " + new Date(now));
+
+ if (triggerTime > now) {
+ Log.d(TAG, " Status: I FREMTIDEN (Alarm skal settes)");
+ } else {
+ Log.d(TAG, " Status: I FORTIDEN (Hoppes over)");
+ }
+ }
+ // ----------------------------------------
+
if (triggerTime > now && triggerTime < (now + 24 * 60 * 60 * 1000L)) {
- // Lag en unik ID for alarmen
String uniqueIdString = event.getTitle() + event.getRawDate();
int alarmId = uniqueIdString.hashCode();
@@ -5121,17 +5258,13 @@ public class NotificationWorker extends Worker {
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));
+ Log.i(TAG, ">>> ALARM SATT: " + event.getTitle());
countSet++;
}
@@ -5141,7 +5274,7 @@ public class NotificationWorker extends Worker {
}
if (countSet == 0) {
- Log.d(TAG, "Ingen kommende alarmer (innenfor neste 24t) funnet akkurat nå.");
+ Log.d(TAG, "Ingen nye alarmer satt (ingen hendelser innenfor neste 24t).");
}
}
}
@@ -5260,7 +5393,7 @@ import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
-import okhttp3.logging.HttpLoggingInterceptor; // NY IMPORT
+import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
@@ -5271,12 +5404,16 @@ public class RetrofitClient {
public static WordPressApiService getApiService() {
if (retrofit == null) {
- // NYTT: Logging Interceptor
+ // ENDRET: Redusert loggnivå fra BODY til BASIC for å unngå spam i Logcat
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
- logging.setLevel(HttpLoggingInterceptor.Level.BODY); // Logger ALT (Body, Headers)
+ if (BuildConfig.DEBUG) {
+ logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
+ } else {
+ logging.setLevel(HttpLoggingInterceptor.Level.NONE);
+ }
OkHttpClient client = new OkHttpClient.Builder()
- .addInterceptor(logging) // Legg til loggingen her
+ .addInterceptor(logging)
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
@@ -5295,7 +5432,7 @@ public class RetrofitClient {
Gson gson = new GsonBuilder()
.registerTypeAdapter(new TypeToken>(){}.getType(), new ChoicesAdapter())
- .setLenient() // NYTT: Gjør parsing litt mer tilgivende
+ .setLenient()
.create();
retrofit = new Retrofit.Builder()