Før nytt forsøk på Google Kalender
This commit is contained in:
parent
29a36e8a61
commit
d9d128245c
10 changed files with 714 additions and 174 deletions
|
|
@ -13,6 +13,7 @@
|
||||||
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
|
@ -38,6 +39,8 @@
|
||||||
|
|
||||||
<activity android:name=".WebViewActivity" />
|
<activity android:name=".WebViewActivity" />
|
||||||
|
|
||||||
|
<receiver android:name=".AlarmReceiver" android:exported="false" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="com.kbs.kbsintranett.fileprovider"
|
android:authorities="com.kbs.kbsintranett.fileprovider"
|
||||||
|
|
|
||||||
65
app/src/main/java/com/kbs/kbsintranett/AlarmReceiver.java
Normal file
65
app/src/main/java/com/kbs/kbsintranett/AlarmReceiver.java
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,7 +38,6 @@ public class CalendarFullFragment extends Fragment {
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
recyclerView = view.findViewById(R.id.recycler_full_calendar);
|
recyclerView = view.findViewById(R.id.recycler_full_calendar);
|
||||||
progressBar = view.findViewById(R.id.loading_full_calendar);
|
progressBar = view.findViewById(R.id.loading_full_calendar);
|
||||||
emptyView = view.findViewById(R.id.empty_view_calendar);
|
emptyView = view.findViewById(R.id.empty_view_calendar);
|
||||||
|
|
@ -46,7 +45,6 @@ public class CalendarFullFragment extends Fragment {
|
||||||
|
|
||||||
layoutManager = new LinearLayoutManager(getContext());
|
layoutManager = new LinearLayoutManager(getContext());
|
||||||
recyclerView.setLayoutManager(layoutManager);
|
recyclerView.setLayoutManager(layoutManager);
|
||||||
|
|
||||||
backBtn.setOnClickListener(v -> Navigation.findNavController(view).navigateUp());
|
backBtn.setOnClickListener(v -> Navigation.findNavController(view).navigateUp());
|
||||||
|
|
||||||
fetchAllEvents();
|
fetchAllEvents();
|
||||||
|
|
@ -54,22 +52,21 @@ public class CalendarFullFragment extends Fragment {
|
||||||
|
|
||||||
private void fetchAllEvents() {
|
private void fetchAllEvents() {
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
// Hent personlige hendelser (Nå med historikk)
|
// Hent personlige hendelser (Nå med historikk)
|
||||||
List<CalendarEvent> deviceEvents = CalendarManager.getDeviceEvents(getContext());
|
List<CalendarEvent> deviceEvents = CalendarManager.getDeviceEvents(getContext());
|
||||||
|
|
||||||
RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback<List<CalendarEvent>>() {
|
// NYTT: Hent fra Google direkte
|
||||||
|
String url = CalendarManager.getGoogleCalendarUrl();
|
||||||
|
RetrofitClient.getApiService().getDirectGoogleEvents(url).enqueue(new Callback<GoogleCalendarModels.Response>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<List<CalendarEvent>> call, Response<List<CalendarEvent>> response) {
|
public void onResponse(Call<GoogleCalendarModels.Response> call, Response<GoogleCalendarModels.Response> response) {
|
||||||
if (!isAdded()) return;
|
if (!isAdded()) return;
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
|
|
||||||
List<CalendarEvent> apiEvents = new ArrayList<>();
|
List<CalendarEvent> apiEvents = new ArrayList<>();
|
||||||
if (response.isSuccessful() && response.body() != null) {
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
for (CalendarEvent e : response.body()) {
|
// Konverter og formatér
|
||||||
CalendarManager.formatEventForUI(e);
|
apiEvents = CalendarManager.convertGoogleResponse(response.body());
|
||||||
apiEvents.add(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flett og vis
|
// Flett og vis
|
||||||
|
|
@ -94,7 +91,7 @@ public class CalendarFullFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Call<List<CalendarEvent>> call, Throwable t) {
|
public void onFailure(Call<GoogleCalendarModels.Response> call, Throwable t) {
|
||||||
if (!isAdded()) return;
|
if (!isAdded()) return;
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,86 @@ import java.util.TimeZone;
|
||||||
|
|
||||||
public class CalendarManager {
|
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<CalendarEvent> convertGoogleResponse(GoogleCalendarModels.Response response) {
|
||||||
|
List<CalendarEvent> 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
|
// NY HJELPEMETODE: Finner ID-ene til kalendere som tilhører @kbs.no
|
||||||
private static List<String> getKbsCalendarIds(Context context) {
|
private static List<String> getKbsCalendarIds(Context context) {
|
||||||
List<String> ids = new ArrayList<>();
|
List<String> ids = new ArrayList<>();
|
||||||
|
|
||||||
String[] projection = new String[] {
|
String[] projection = new String[] {
|
||||||
CalendarContract.Calendars._ID,
|
CalendarContract.Calendars._ID,
|
||||||
CalendarContract.Calendars.ACCOUNT_NAME
|
CalendarContract.Calendars.ACCOUNT_NAME
|
||||||
};
|
};
|
||||||
|
|
||||||
// Vi ser etter kontoer som slutter på @kbs.no
|
// Vi ser etter kontoer som slutter på @kbs.no
|
||||||
// (SQL: account_name LIKE '%@kbs.no')
|
// (SQL: account_name LIKE '%@kbs.no')
|
||||||
String selection = CalendarContract.Calendars.ACCOUNT_NAME + " LIKE ?";
|
String selection = CalendarContract.Calendars.ACCOUNT_NAME + " LIKE ?";
|
||||||
|
|
@ -38,7 +109,8 @@ public class CalendarManager {
|
||||||
)) {
|
)) {
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
ids.add(cursor.getString(0)); // Legg til kalender-ID
|
ids.add(cursor.getString(0));
|
||||||
|
// Legg til kalender-ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
@ -49,7 +121,6 @@ public class CalendarManager {
|
||||||
|
|
||||||
public static List<CalendarEvent> getDeviceEvents(Context context) {
|
public static List<CalendarEvent> getDeviceEvents(Context context) {
|
||||||
List<CalendarEvent> deviceEvents = new ArrayList<>();
|
List<CalendarEvent> deviceEvents = new ArrayList<>();
|
||||||
|
|
||||||
if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR)
|
if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR)
|
||||||
!= PackageManager.PERMISSION_GRANTED) {
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
return deviceEvents;
|
return deviceEvents;
|
||||||
|
|
@ -57,7 +128,6 @@ public class CalendarManager {
|
||||||
|
|
||||||
// 1. Finn først ID-ene til KBS-kalenderne
|
// 1. Finn først ID-ene til KBS-kalenderne
|
||||||
List<String> kbsCalendarIds = getKbsCalendarIds(context);
|
List<String> kbsCalendarIds = getKbsCalendarIds(context);
|
||||||
|
|
||||||
// Hvis ingen kbs-kalendere finnes på telefonen, returner tom liste
|
// Hvis ingen kbs-kalendere finnes på telefonen, returner tom liste
|
||||||
if (kbsCalendarIds.isEmpty()) {
|
if (kbsCalendarIds.isEmpty()) {
|
||||||
return deviceEvents;
|
return deviceEvents;
|
||||||
|
|
@ -67,7 +137,6 @@ public class CalendarManager {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long startMillis = now - (365L * 24 * 60 * 60 * 1000);
|
long startMillis = now - (365L * 24 * 60 * 60 * 1000);
|
||||||
long endMillis = now + (365L * 24 * 60 * 60 * 1000);
|
long endMillis = now + (365L * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
String[] projection = new String[]{
|
String[] projection = new String[]{
|
||||||
CalendarContract.Events.TITLE,
|
CalendarContract.Events.TITLE,
|
||||||
CalendarContract.Events.DTSTART,
|
CalendarContract.Events.DTSTART,
|
||||||
|
|
@ -90,7 +159,6 @@ public class CalendarManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
selection.append(") AND ");
|
selection.append(") AND ");
|
||||||
|
|
||||||
selection.append(CalendarContract.Events.DTSTART).append(" >= ? AND ");
|
selection.append(CalendarContract.Events.DTSTART).append(" >= ? AND ");
|
||||||
selection.append(CalendarContract.Events.DTSTART).append(" <= ?");
|
selection.append(CalendarContract.Events.DTSTART).append(" <= ?");
|
||||||
|
|
||||||
|
|
@ -98,7 +166,6 @@ public class CalendarManager {
|
||||||
selectionArgsList.add(String.valueOf(endMillis));
|
selectionArgsList.add(String.valueOf(endMillis));
|
||||||
|
|
||||||
String[] selectionArgs = selectionArgsList.toArray(new String[0]);
|
String[] selectionArgs = selectionArgsList.toArray(new String[0]);
|
||||||
|
|
||||||
try (Cursor cursor = context.getContentResolver().query(
|
try (Cursor cursor = context.getContentResolver().query(
|
||||||
CalendarContract.Events.CONTENT_URI,
|
CalendarContract.Events.CONTENT_URI,
|
||||||
projection,
|
projection,
|
||||||
|
|
@ -108,7 +175,6 @@ public class CalendarManager {
|
||||||
)) {
|
)) {
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
|
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
|
||||||
|
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
String title = cursor.getString(0);
|
String title = cursor.getString(0);
|
||||||
long dtStart = cursor.getLong(1);
|
long dtStart = cursor.getLong(1);
|
||||||
|
|
@ -116,7 +182,6 @@ public class CalendarManager {
|
||||||
String desc = cursor.getString(3);
|
String desc = cursor.getString(3);
|
||||||
String loc = cursor.getString(4);
|
String loc = cursor.getString(4);
|
||||||
int allDay = cursor.getInt(5);
|
int allDay = cursor.getInt(5);
|
||||||
|
|
||||||
String rawStart;
|
String rawStart;
|
||||||
String rawEnd;
|
String rawEnd;
|
||||||
|
|
||||||
|
|
@ -140,14 +205,11 @@ public class CalendarManager {
|
||||||
return deviceEvents;
|
return deviceEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- (formatEventForUI og mergeAndSort er uendret fra forrige versjon) ---
|
|
||||||
public static void formatEventForUI(CalendarEvent event) {
|
public static void formatEventForUI(CalendarEvent event) {
|
||||||
if (event.getRawDate() == null) return;
|
if (event.getRawDate() == null) return;
|
||||||
|
|
||||||
SimpleDateFormat outputDay = new SimpleDateFormat("dd", Locale.getDefault());
|
SimpleDateFormat outputDay = new SimpleDateFormat("dd", Locale.getDefault());
|
||||||
SimpleDateFormat outputMonth = new SimpleDateFormat("MMM", new Locale("no", "NO"));
|
SimpleDateFormat outputMonth = new SimpleDateFormat("MMM", new Locale("no", "NO"));
|
||||||
SimpleDateFormat outputTime = new SimpleDateFormat("HH:mm", Locale.getDefault());
|
SimpleDateFormat outputTime = new SimpleDateFormat("HH:mm", Locale.getDefault());
|
||||||
|
|
||||||
outputMonth.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
|
outputMonth.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
|
||||||
outputTime.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
|
outputTime.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
|
||||||
|
|
||||||
|
|
@ -157,7 +219,6 @@ public class CalendarManager {
|
||||||
boolean isAllDay = false;
|
boolean isAllDay = false;
|
||||||
|
|
||||||
String raw = event.getRawDate();
|
String raw = event.getRawDate();
|
||||||
|
|
||||||
if (raw.length() == 10 && !raw.contains("T") && !raw.contains(" ")) {
|
if (raw.length() == 10 && !raw.contains("T") && !raw.contains(" ")) {
|
||||||
SimpleDateFormat shortFmt = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
SimpleDateFormat shortFmt = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||||
date = shortFmt.parse(raw);
|
date = shortFmt.parse(raw);
|
||||||
|
|
@ -215,7 +276,6 @@ public class CalendarManager {
|
||||||
String d2 = e2.getRawDate() != null ? e2.getRawDate() : "";
|
String d2 = e2.getRawDate() != null ? e2.getRawDate() : "";
|
||||||
return d1.compareTo(d2);
|
return d1.compareTo(d2);
|
||||||
});
|
});
|
||||||
|
|
||||||
return all;
|
return all;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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<Item> 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<Override> overrides;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Override {
|
||||||
|
@SerializedName("method")
|
||||||
|
public String method; // f.eks "popup"
|
||||||
|
|
||||||
|
@SerializedName("minutes")
|
||||||
|
public int minutes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -37,7 +37,6 @@ public class HomeFragment extends Fragment {
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// Håndter svar på kalendertillatelse
|
// Håndter svar på kalendertillatelse
|
||||||
requestPermissionLauncher = registerForActivityResult(
|
requestPermissionLauncher = registerForActivityResult(
|
||||||
new ActivityResultContracts.RequestPermission(),
|
new ActivityResultContracts.RequestPermission(),
|
||||||
|
|
@ -48,7 +47,6 @@ public class HomeFragment extends Fragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Start bakgrunnsjobb for varsling (kjører hver 15. minutt)
|
// Start bakgrunnsjobb for varsling (kjører hver 15. minutt)
|
||||||
startNotificationWorker();
|
startNotificationWorker();
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +60,6 @@ public class HomeFragment extends Fragment {
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
// 0. Profil-knapp
|
// 0. Profil-knapp
|
||||||
View profileBtn = view.findViewById(R.id.btn_profile);
|
View profileBtn = view.findViewById(R.id.btn_profile);
|
||||||
if (profileBtn != null) {
|
if (profileBtn != null) {
|
||||||
|
|
@ -74,7 +71,6 @@ public class HomeFragment extends Fragment {
|
||||||
calendarRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
|
calendarRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
// Sett tom adapter midlertidig
|
// Sett tom adapter midlertidig
|
||||||
calendarRecycler.setAdapter(new CalendarAdapter(new ArrayList<>(), event -> {}));
|
calendarRecycler.setAdapter(new CalendarAdapter(new ArrayList<>(), event -> {}));
|
||||||
|
|
||||||
// "Se alle" knapp for kalender
|
// "Se alle" knapp for kalender
|
||||||
TextView viewAllCalendar = view.findViewById(R.id.btn_view_all_calendar);
|
TextView viewAllCalendar = view.findViewById(R.id.btn_view_all_calendar);
|
||||||
if (viewAllCalendar != null) {
|
if (viewAllCalendar != null) {
|
||||||
|
|
@ -102,7 +98,6 @@ public class HomeFragment extends Fragment {
|
||||||
newsRecycler.setNestedScrollingEnabled(false);
|
newsRecycler.setNestedScrollingEnabled(false);
|
||||||
// Sett tom adapter midlertidig
|
// Sett tom adapter midlertidig
|
||||||
newsRecycler.setAdapter(new NewsAdapter(new ArrayList<>(), item -> {}));
|
newsRecycler.setAdapter(new NewsAdapter(new ArrayList<>(), item -> {}));
|
||||||
|
|
||||||
// "Se alle" knapp for nyheter
|
// "Se alle" knapp for nyheter
|
||||||
TextView viewAllNews = view.findViewById(R.id.btn_view_all_news);
|
TextView viewAllNews = view.findViewById(R.id.btn_view_all_news);
|
||||||
if (viewAllNews != null) {
|
if (viewAllNews != null) {
|
||||||
|
|
@ -118,26 +113,23 @@ public class HomeFragment extends Fragment {
|
||||||
// 1. Hent personlige hendelser først (fra CalendarManager)
|
// 1. Hent personlige hendelser først (fra CalendarManager)
|
||||||
List<CalendarEvent> deviceEvents = CalendarManager.getDeviceEvents(getContext());
|
List<CalendarEvent> deviceEvents = CalendarManager.getDeviceEvents(getContext());
|
||||||
|
|
||||||
// 2. Hent API-hendelser fra WordPress
|
// 2. Hent API-hendelser DIREKTE fra Google
|
||||||
WordPressApiService apiService = RetrofitClient.getApiService();
|
String url = CalendarManager.getGoogleCalendarUrl();
|
||||||
apiService.getCalendarEvents().enqueue(new Callback<List<CalendarEvent>>() {
|
RetrofitClient.getApiService().getDirectGoogleEvents(url).enqueue(new Callback<GoogleCalendarModels.Response>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<List<CalendarEvent>> call, Response<List<CalendarEvent>> response) {
|
public void onResponse(Call<GoogleCalendarModels.Response> call, Response<GoogleCalendarModels.Response> response) {
|
||||||
if (!isAdded()) return;
|
if (!isAdded()) return;
|
||||||
|
|
||||||
List<CalendarEvent> apiEvents = new ArrayList<>();
|
List<CalendarEvent> apiEvents = new ArrayList<>();
|
||||||
if (response.isSuccessful() && response.body() != null) {
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
for (CalendarEvent e : response.body()) {
|
// Konverter fra Google-modell til KBS-modell
|
||||||
CalendarManager.formatEventForUI(e); // Formatér datoer
|
apiEvents = CalendarManager.convertGoogleResponse(response.body());
|
||||||
apiEvents.add(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Flett listene (API + Personlig) og sorter
|
// 3. Flett listene (API + Personlig) og sorter
|
||||||
List<CalendarEvent> merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents);
|
List<CalendarEvent> merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents);
|
||||||
|
|
||||||
// 4. Filtrer ut hendelser som har passert (vis kun fremtidige + i dag)
|
// 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<CalendarEvent> upcomingEvents = new ArrayList<>();
|
List<CalendarEvent> upcomingEvents = new ArrayList<>();
|
||||||
String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
|
String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
|
||||||
|
|
||||||
|
|
@ -160,14 +152,12 @@ public class HomeFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Call<List<CalendarEvent>> call, Throwable t) {
|
public void onFailure(Call<GoogleCalendarModels.Response> call, Throwable t) {
|
||||||
if (!isAdded()) return;
|
if (!isAdded()) return;
|
||||||
// Hvis API feiler, vis bare personlige events hvis vi har noen
|
// Hvis API feiler, vis bare personlige events hvis vi har noen
|
||||||
if (!deviceEvents.isEmpty()) {
|
if (!deviceEvents.isEmpty()) {
|
||||||
List<CalendarEvent> top5 = new ArrayList<>();
|
List<CalendarEvent> top5 = new ArrayList<>();
|
||||||
// Filtrer og plukk topp 5 fra lokale også
|
|
||||||
for(int i=0; i<Math.min(deviceEvents.size(), 5); i++) top5.add(deviceEvents.get(i));
|
for(int i=0; i<Math.min(deviceEvents.size(), 5); i++) top5.add(deviceEvents.get(i));
|
||||||
|
|
||||||
recyclerView.setAdapter(new CalendarAdapter(top5, event -> {
|
recyclerView.setAdapter(new CalendarAdapter(top5, event -> {
|
||||||
CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
|
CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
|
||||||
sheet.show(getParentFragmentManager(), "CalendarDetails");
|
sheet.show(getParentFragmentManager(), "CalendarDetails");
|
||||||
|
|
@ -201,7 +191,8 @@ public class HomeFragment extends Fragment {
|
||||||
for (WpPost post : wpPosts) {
|
for (WpPost post : wpPosts) {
|
||||||
try {
|
try {
|
||||||
Date date = rawFormat.parse(post.date);
|
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) {}
|
} catch (Exception e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
package com.kbs.kbsintranett;
|
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.os.Bundle;
|
||||||
|
import android.provider.Settings;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
|
@ -8,6 +15,8 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.navigation.NavController;
|
import androidx.navigation.NavController;
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
import androidx.navigation.ui.NavigationUI;
|
import androidx.navigation.ui.NavigationUI;
|
||||||
|
import androidx.work.OneTimeWorkRequest;
|
||||||
|
import androidx.work.WorkManager;
|
||||||
|
|
||||||
import com.google.android.gms.auth.api.signin.GoogleSignIn;
|
import com.google.android.gms.auth.api.signin.GoogleSignIn;
|
||||||
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
|
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
|
||||||
|
|
@ -32,6 +41,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
bottomNav = findViewById(R.id.bottom_nav_view);
|
bottomNav = findViewById(R.id.bottom_nav_view);
|
||||||
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
|
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
|
||||||
.findFragmentById(R.id.nav_host_fragment);
|
.findFragmentById(R.id.nav_host_fragment);
|
||||||
|
|
||||||
if (navHostFragment != null) {
|
if (navHostFragment != null) {
|
||||||
navController = navHostFragment.getNavController();
|
navController = navHostFragment.getNavController();
|
||||||
NavigationUI.setupWithNavController(bottomNav, navController);
|
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
|
// 2. Start Silent Sign-In ved oppstart
|
||||||
checkLoginState();
|
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() {
|
private void checkLoginState() {
|
||||||
GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
|
GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package com.kbs.kbsintranett;
|
package com.kbs.kbsintranett;
|
||||||
|
|
||||||
import android.app.NotificationChannel;
|
import android.app.AlarmManager;
|
||||||
import android.app.NotificationManager;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.app.NotificationCompat;
|
|
||||||
import androidx.work.Worker;
|
import androidx.work.Worker;
|
||||||
import androidx.work.WorkerParameters;
|
import androidx.work.WorkerParameters;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -18,8 +18,7 @@ import retrofit2.Response;
|
||||||
|
|
||||||
public class NotificationWorker extends Worker {
|
public class NotificationWorker extends Worker {
|
||||||
|
|
||||||
private static final String CHANNEL_ID = "kbs_calendar_channel";
|
private static final String TAG = "KBS_DEBUG";
|
||||||
private static final String PREFS_NAME = "KBSNotificationPrefs";
|
|
||||||
|
|
||||||
public NotificationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
public NotificationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
||||||
super(context, workerParams);
|
super(context, workerParams);
|
||||||
|
|
@ -28,72 +27,108 @@ public class NotificationWorker extends Worker {
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Result doWork() {
|
public Result doWork() {
|
||||||
// Dette kjører i bakgrunnen
|
Log.d(TAG, "NotificationWorker: Starter sjekk av kalender...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Hent events synkront (ikke enqueue)
|
String url = CalendarManager.getGoogleCalendarUrl();
|
||||||
Response<List<CalendarEvent>> response = RetrofitClient.getApiService().getCalendarEvents().execute();
|
// Hent events synkront
|
||||||
|
Response<GoogleCalendarModels.Response> response = RetrofitClient.getApiService().getDirectGoogleEvents(url).execute();
|
||||||
|
|
||||||
if (response.isSuccessful() && response.body() != null) {
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
checkAndNotify(response.body());
|
List<CalendarEvent> events = CalendarManager.convertGoogleResponse(response.body());
|
||||||
|
scheduleAlarms(events);
|
||||||
return Result.success();
|
return Result.success();
|
||||||
} else {
|
} else {
|
||||||
|
Log.e(TAG, "NotificationWorker: API-kall feilet. Kode: " + response.code());
|
||||||
return Result.retry();
|
return Result.retry();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "NotificationWorker: Nettverksfeil", e);
|
||||||
return Result.retry();
|
return Result.retry();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkAndNotify(List<CalendarEvent> events) {
|
private void scheduleAlarms(List<CalendarEvent> events) {
|
||||||
SharedPreferences prefs = getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
Context context = getApplicationContext();
|
||||||
long now = System.currentTimeMillis();
|
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||||
long fifteenMinutes = 15 * 60 * 1000;
|
|
||||||
|
|
||||||
|
// 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 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) {
|
for (CalendarEvent event : events) {
|
||||||
try {
|
try {
|
||||||
Date eventDate;
|
// Hopp over heldagshendelser (dato uten klokkeslett)
|
||||||
if (event.getRawDate().contains("T")) eventDate = isoFormat.parse(event.getRawDate());
|
if (event.getRawDate().length() == 10) continue;
|
||||||
else eventDate = sqlFormat.parse(event.getRawDate());
|
|
||||||
|
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;
|
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
|
// Vi setter alarmen hvis tidspunktet er i fremtiden
|
||||||
if (diff > 0 && diff < (30 * 60 * 1000)) {
|
// Vi sjekker også at det ikke er mer enn 24 timer frem i tid (for å spare ressurser)
|
||||||
String eventId = event.getTitle() + event.getRawDate(); // Enkel ID
|
if (triggerTime > now && triggerTime < (now + 24 * 60 * 60 * 1000L)) {
|
||||||
boolean alreadyNotified = prefs.getBoolean(eventId, false);
|
|
||||||
|
|
||||||
if (!alreadyNotified) {
|
// Lag en unik ID for alarmen
|
||||||
sendNotification(event.getTitle(), "Starter kl " + event.getTime());
|
String uniqueIdString = event.getTitle() + event.getRawDate();
|
||||||
// Lagre at vi har varslet
|
int alarmId = uniqueIdString.hashCode();
|
||||||
prefs.edit().putBoolean(eventId, true).apply();
|
|
||||||
|
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) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
Log.e(TAG, "Feil ved behandling av event: " + event.getTitle(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void sendNotification(String title, String content) {
|
if (countSet == 0) {
|
||||||
NotificationManager manager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
Log.d(TAG, "Ingen kommende alarmer (innenfor neste 24t) funnet akkurat nå.");
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "KBS Kalender", NotificationManager.IMPORTANCE_HIGH);
|
|
||||||
manager.createNotificationChannel(channel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ import retrofit2.http.Multipart;
|
||||||
import retrofit2.http.Part;
|
import retrofit2.http.Part;
|
||||||
import retrofit2.http.PartMap;
|
import retrofit2.http.PartMap;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
import retrofit2.http.Url; // NY IMPORT
|
||||||
|
|
||||||
public interface WordPressApiService {
|
public interface WordPressApiService {
|
||||||
@GET("wp-json/wp/v2/posts?per_page=10&_embed")
|
@GET("wp-json/wp/v2/posts?per_page=10&_embed")
|
||||||
|
|
@ -40,9 +41,14 @@ public interface WordPressApiService {
|
||||||
@Part List<MultipartBody.Part> files
|
@Part List<MultipartBody.Part> files
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ENDRET: Denne brukes ikke lenger for kalender, men beholdes for bakoverkompatibilitet
|
||||||
@GET("wp-json/kbs/v1/calendar/events")
|
@GET("wp-json/kbs/v1/calendar/events")
|
||||||
Call<List<CalendarEvent>> getCalendarEvents();
|
Call<List<CalendarEvent>> getCalendarEvents();
|
||||||
|
|
||||||
|
// NY: Direkte kall mot Google (bruker @Url for å override base URL)
|
||||||
|
@GET
|
||||||
|
Call<GoogleCalendarModels.Response> getDirectGoogleEvents(@Url String fullUrl);
|
||||||
|
|
||||||
@GET("wp-json/gf/v2/entries")
|
@GET("wp-json/gf/v2/entries")
|
||||||
Call<GravityEntryResponse> getEntries(
|
Call<GravityEntryResponse> getEntries(
|
||||||
@Query("form_ids") int formId,
|
@Query("form_ids") int formId,
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,7 @@ FILSTI: app\src\main\AndroidManifest.xml
|
||||||
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
|
@ -202,6 +203,8 @@ FILSTI: app\src\main\AndroidManifest.xml
|
||||||
|
|
||||||
<activity android:name=".WebViewActivity" />
|
<activity android:name=".WebViewActivity" />
|
||||||
|
|
||||||
|
<receiver android:name=".AlarmReceiver" android:exported="false" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="com.kbs.kbsintranett.fileprovider"
|
android:authorities="com.kbs.kbsintranett.fileprovider"
|
||||||
|
|
@ -216,6 +219,75 @@ FILSTI: app\src\main\AndroidManifest.xml
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
||||||
|
============================================================
|
||||||
|
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
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\AuthRepository.java
|
||||||
============================================================
|
============================================================
|
||||||
|
|
@ -568,7 +640,6 @@ public class CalendarFullFragment extends Fragment {
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
recyclerView = view.findViewById(R.id.recycler_full_calendar);
|
recyclerView = view.findViewById(R.id.recycler_full_calendar);
|
||||||
progressBar = view.findViewById(R.id.loading_full_calendar);
|
progressBar = view.findViewById(R.id.loading_full_calendar);
|
||||||
emptyView = view.findViewById(R.id.empty_view_calendar);
|
emptyView = view.findViewById(R.id.empty_view_calendar);
|
||||||
|
|
@ -576,7 +647,6 @@ public class CalendarFullFragment extends Fragment {
|
||||||
|
|
||||||
layoutManager = new LinearLayoutManager(getContext());
|
layoutManager = new LinearLayoutManager(getContext());
|
||||||
recyclerView.setLayoutManager(layoutManager);
|
recyclerView.setLayoutManager(layoutManager);
|
||||||
|
|
||||||
backBtn.setOnClickListener(v -> Navigation.findNavController(view).navigateUp());
|
backBtn.setOnClickListener(v -> Navigation.findNavController(view).navigateUp());
|
||||||
|
|
||||||
fetchAllEvents();
|
fetchAllEvents();
|
||||||
|
|
@ -584,22 +654,21 @@ public class CalendarFullFragment extends Fragment {
|
||||||
|
|
||||||
private void fetchAllEvents() {
|
private void fetchAllEvents() {
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
// Hent personlige hendelser (Nå med historikk)
|
// Hent personlige hendelser (Nå med historikk)
|
||||||
List<CalendarEvent> deviceEvents = CalendarManager.getDeviceEvents(getContext());
|
List<CalendarEvent> deviceEvents = CalendarManager.getDeviceEvents(getContext());
|
||||||
|
|
||||||
RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback<List<CalendarEvent>>() {
|
// NYTT: Hent fra Google direkte
|
||||||
|
String url = CalendarManager.getGoogleCalendarUrl();
|
||||||
|
RetrofitClient.getApiService().getDirectGoogleEvents(url).enqueue(new Callback<GoogleCalendarModels.Response>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<List<CalendarEvent>> call, Response<List<CalendarEvent>> response) {
|
public void onResponse(Call<GoogleCalendarModels.Response> call, Response<GoogleCalendarModels.Response> response) {
|
||||||
if (!isAdded()) return;
|
if (!isAdded()) return;
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
|
|
||||||
List<CalendarEvent> apiEvents = new ArrayList<>();
|
List<CalendarEvent> apiEvents = new ArrayList<>();
|
||||||
if (response.isSuccessful() && response.body() != null) {
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
for (CalendarEvent e : response.body()) {
|
// Konverter og formatér
|
||||||
CalendarManager.formatEventForUI(e);
|
apiEvents = CalendarManager.convertGoogleResponse(response.body());
|
||||||
apiEvents.add(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flett og vis
|
// Flett og vis
|
||||||
|
|
@ -624,7 +693,7 @@ public class CalendarFullFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Call<List<CalendarEvent>> call, Throwable t) {
|
public void onFailure(Call<GoogleCalendarModels.Response> call, Throwable t) {
|
||||||
if (!isAdded()) return;
|
if (!isAdded()) return;
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
|
@ -684,15 +753,86 @@ import java.util.TimeZone;
|
||||||
|
|
||||||
public class CalendarManager {
|
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<CalendarEvent> convertGoogleResponse(GoogleCalendarModels.Response response) {
|
||||||
|
List<CalendarEvent> 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
|
// NY HJELPEMETODE: Finner ID-ene til kalendere som tilhører @kbs.no
|
||||||
private static List<String> getKbsCalendarIds(Context context) {
|
private static List<String> getKbsCalendarIds(Context context) {
|
||||||
List<String> ids = new ArrayList<>();
|
List<String> ids = new ArrayList<>();
|
||||||
|
|
||||||
String[] projection = new String[] {
|
String[] projection = new String[] {
|
||||||
CalendarContract.Calendars._ID,
|
CalendarContract.Calendars._ID,
|
||||||
CalendarContract.Calendars.ACCOUNT_NAME
|
CalendarContract.Calendars.ACCOUNT_NAME
|
||||||
};
|
};
|
||||||
|
|
||||||
// Vi ser etter kontoer som slutter på @kbs.no
|
// Vi ser etter kontoer som slutter på @kbs.no
|
||||||
// (SQL: account_name LIKE '%@kbs.no')
|
// (SQL: account_name LIKE '%@kbs.no')
|
||||||
String selection = CalendarContract.Calendars.ACCOUNT_NAME + " LIKE ?";
|
String selection = CalendarContract.Calendars.ACCOUNT_NAME + " LIKE ?";
|
||||||
|
|
@ -707,7 +847,8 @@ public class CalendarManager {
|
||||||
)) {
|
)) {
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
ids.add(cursor.getString(0)); // Legg til kalender-ID
|
ids.add(cursor.getString(0));
|
||||||
|
// Legg til kalender-ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
@ -718,7 +859,6 @@ public class CalendarManager {
|
||||||
|
|
||||||
public static List<CalendarEvent> getDeviceEvents(Context context) {
|
public static List<CalendarEvent> getDeviceEvents(Context context) {
|
||||||
List<CalendarEvent> deviceEvents = new ArrayList<>();
|
List<CalendarEvent> deviceEvents = new ArrayList<>();
|
||||||
|
|
||||||
if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR)
|
if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR)
|
||||||
!= PackageManager.PERMISSION_GRANTED) {
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
return deviceEvents;
|
return deviceEvents;
|
||||||
|
|
@ -726,7 +866,6 @@ public class CalendarManager {
|
||||||
|
|
||||||
// 1. Finn først ID-ene til KBS-kalenderne
|
// 1. Finn først ID-ene til KBS-kalenderne
|
||||||
List<String> kbsCalendarIds = getKbsCalendarIds(context);
|
List<String> kbsCalendarIds = getKbsCalendarIds(context);
|
||||||
|
|
||||||
// Hvis ingen kbs-kalendere finnes på telefonen, returner tom liste
|
// Hvis ingen kbs-kalendere finnes på telefonen, returner tom liste
|
||||||
if (kbsCalendarIds.isEmpty()) {
|
if (kbsCalendarIds.isEmpty()) {
|
||||||
return deviceEvents;
|
return deviceEvents;
|
||||||
|
|
@ -736,7 +875,6 @@ public class CalendarManager {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long startMillis = now - (365L * 24 * 60 * 60 * 1000);
|
long startMillis = now - (365L * 24 * 60 * 60 * 1000);
|
||||||
long endMillis = now + (365L * 24 * 60 * 60 * 1000);
|
long endMillis = now + (365L * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
String[] projection = new String[]{
|
String[] projection = new String[]{
|
||||||
CalendarContract.Events.TITLE,
|
CalendarContract.Events.TITLE,
|
||||||
CalendarContract.Events.DTSTART,
|
CalendarContract.Events.DTSTART,
|
||||||
|
|
@ -759,7 +897,6 @@ public class CalendarManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
selection.append(") AND ");
|
selection.append(") AND ");
|
||||||
|
|
||||||
selection.append(CalendarContract.Events.DTSTART).append(" >= ? AND ");
|
selection.append(CalendarContract.Events.DTSTART).append(" >= ? AND ");
|
||||||
selection.append(CalendarContract.Events.DTSTART).append(" <= ?");
|
selection.append(CalendarContract.Events.DTSTART).append(" <= ?");
|
||||||
|
|
||||||
|
|
@ -767,7 +904,6 @@ public class CalendarManager {
|
||||||
selectionArgsList.add(String.valueOf(endMillis));
|
selectionArgsList.add(String.valueOf(endMillis));
|
||||||
|
|
||||||
String[] selectionArgs = selectionArgsList.toArray(new String[0]);
|
String[] selectionArgs = selectionArgsList.toArray(new String[0]);
|
||||||
|
|
||||||
try (Cursor cursor = context.getContentResolver().query(
|
try (Cursor cursor = context.getContentResolver().query(
|
||||||
CalendarContract.Events.CONTENT_URI,
|
CalendarContract.Events.CONTENT_URI,
|
||||||
projection,
|
projection,
|
||||||
|
|
@ -777,7 +913,6 @@ public class CalendarManager {
|
||||||
)) {
|
)) {
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
|
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
|
||||||
|
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
String title = cursor.getString(0);
|
String title = cursor.getString(0);
|
||||||
long dtStart = cursor.getLong(1);
|
long dtStart = cursor.getLong(1);
|
||||||
|
|
@ -785,7 +920,6 @@ public class CalendarManager {
|
||||||
String desc = cursor.getString(3);
|
String desc = cursor.getString(3);
|
||||||
String loc = cursor.getString(4);
|
String loc = cursor.getString(4);
|
||||||
int allDay = cursor.getInt(5);
|
int allDay = cursor.getInt(5);
|
||||||
|
|
||||||
String rawStart;
|
String rawStart;
|
||||||
String rawEnd;
|
String rawEnd;
|
||||||
|
|
||||||
|
|
@ -809,14 +943,11 @@ public class CalendarManager {
|
||||||
return deviceEvents;
|
return deviceEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- (formatEventForUI og mergeAndSort er uendret fra forrige versjon) ---
|
|
||||||
public static void formatEventForUI(CalendarEvent event) {
|
public static void formatEventForUI(CalendarEvent event) {
|
||||||
if (event.getRawDate() == null) return;
|
if (event.getRawDate() == null) return;
|
||||||
|
|
||||||
SimpleDateFormat outputDay = new SimpleDateFormat("dd", Locale.getDefault());
|
SimpleDateFormat outputDay = new SimpleDateFormat("dd", Locale.getDefault());
|
||||||
SimpleDateFormat outputMonth = new SimpleDateFormat("MMM", new Locale("no", "NO"));
|
SimpleDateFormat outputMonth = new SimpleDateFormat("MMM", new Locale("no", "NO"));
|
||||||
SimpleDateFormat outputTime = new SimpleDateFormat("HH:mm", Locale.getDefault());
|
SimpleDateFormat outputTime = new SimpleDateFormat("HH:mm", Locale.getDefault());
|
||||||
|
|
||||||
outputMonth.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
|
outputMonth.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
|
||||||
outputTime.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
|
outputTime.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
|
||||||
|
|
||||||
|
|
@ -826,7 +957,6 @@ public class CalendarManager {
|
||||||
boolean isAllDay = false;
|
boolean isAllDay = false;
|
||||||
|
|
||||||
String raw = event.getRawDate();
|
String raw = event.getRawDate();
|
||||||
|
|
||||||
if (raw.length() == 10 && !raw.contains("T") && !raw.contains(" ")) {
|
if (raw.length() == 10 && !raw.contains("T") && !raw.contains(" ")) {
|
||||||
SimpleDateFormat shortFmt = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
SimpleDateFormat shortFmt = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||||
date = shortFmt.parse(raw);
|
date = shortFmt.parse(raw);
|
||||||
|
|
@ -884,7 +1014,6 @@ public class CalendarManager {
|
||||||
String d2 = e2.getRawDate() != null ? e2.getRawDate() : "";
|
String d2 = e2.getRawDate() != null ? e2.getRawDate() : "";
|
||||||
return d1.compareTo(d2);
|
return d1.compareTo(d2);
|
||||||
});
|
});
|
||||||
|
|
||||||
return all;
|
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<Item> 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<Override> 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
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\GravityEntryResponse.java
|
||||||
============================================================
|
============================================================
|
||||||
|
|
@ -3767,7 +3959,6 @@ public class HomeFragment extends Fragment {
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// Håndter svar på kalendertillatelse
|
// Håndter svar på kalendertillatelse
|
||||||
requestPermissionLauncher = registerForActivityResult(
|
requestPermissionLauncher = registerForActivityResult(
|
||||||
new ActivityResultContracts.RequestPermission(),
|
new ActivityResultContracts.RequestPermission(),
|
||||||
|
|
@ -3778,7 +3969,6 @@ public class HomeFragment extends Fragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Start bakgrunnsjobb for varsling (kjører hver 15. minutt)
|
// Start bakgrunnsjobb for varsling (kjører hver 15. minutt)
|
||||||
startNotificationWorker();
|
startNotificationWorker();
|
||||||
}
|
}
|
||||||
|
|
@ -3792,7 +3982,6 @@ public class HomeFragment extends Fragment {
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
// 0. Profil-knapp
|
// 0. Profil-knapp
|
||||||
View profileBtn = view.findViewById(R.id.btn_profile);
|
View profileBtn = view.findViewById(R.id.btn_profile);
|
||||||
if (profileBtn != null) {
|
if (profileBtn != null) {
|
||||||
|
|
@ -3804,7 +3993,6 @@ public class HomeFragment extends Fragment {
|
||||||
calendarRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
|
calendarRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
// Sett tom adapter midlertidig
|
// Sett tom adapter midlertidig
|
||||||
calendarRecycler.setAdapter(new CalendarAdapter(new ArrayList<>(), event -> {}));
|
calendarRecycler.setAdapter(new CalendarAdapter(new ArrayList<>(), event -> {}));
|
||||||
|
|
||||||
// "Se alle" knapp for kalender
|
// "Se alle" knapp for kalender
|
||||||
TextView viewAllCalendar = view.findViewById(R.id.btn_view_all_calendar);
|
TextView viewAllCalendar = view.findViewById(R.id.btn_view_all_calendar);
|
||||||
if (viewAllCalendar != null) {
|
if (viewAllCalendar != null) {
|
||||||
|
|
@ -3832,7 +4020,6 @@ public class HomeFragment extends Fragment {
|
||||||
newsRecycler.setNestedScrollingEnabled(false);
|
newsRecycler.setNestedScrollingEnabled(false);
|
||||||
// Sett tom adapter midlertidig
|
// Sett tom adapter midlertidig
|
||||||
newsRecycler.setAdapter(new NewsAdapter(new ArrayList<>(), item -> {}));
|
newsRecycler.setAdapter(new NewsAdapter(new ArrayList<>(), item -> {}));
|
||||||
|
|
||||||
// "Se alle" knapp for nyheter
|
// "Se alle" knapp for nyheter
|
||||||
TextView viewAllNews = view.findViewById(R.id.btn_view_all_news);
|
TextView viewAllNews = view.findViewById(R.id.btn_view_all_news);
|
||||||
if (viewAllNews != null) {
|
if (viewAllNews != null) {
|
||||||
|
|
@ -3848,26 +4035,23 @@ public class HomeFragment extends Fragment {
|
||||||
// 1. Hent personlige hendelser først (fra CalendarManager)
|
// 1. Hent personlige hendelser først (fra CalendarManager)
|
||||||
List<CalendarEvent> deviceEvents = CalendarManager.getDeviceEvents(getContext());
|
List<CalendarEvent> deviceEvents = CalendarManager.getDeviceEvents(getContext());
|
||||||
|
|
||||||
// 2. Hent API-hendelser fra WordPress
|
// 2. Hent API-hendelser DIREKTE fra Google
|
||||||
WordPressApiService apiService = RetrofitClient.getApiService();
|
String url = CalendarManager.getGoogleCalendarUrl();
|
||||||
apiService.getCalendarEvents().enqueue(new Callback<List<CalendarEvent>>() {
|
RetrofitClient.getApiService().getDirectGoogleEvents(url).enqueue(new Callback<GoogleCalendarModels.Response>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<List<CalendarEvent>> call, Response<List<CalendarEvent>> response) {
|
public void onResponse(Call<GoogleCalendarModels.Response> call, Response<GoogleCalendarModels.Response> response) {
|
||||||
if (!isAdded()) return;
|
if (!isAdded()) return;
|
||||||
|
|
||||||
List<CalendarEvent> apiEvents = new ArrayList<>();
|
List<CalendarEvent> apiEvents = new ArrayList<>();
|
||||||
if (response.isSuccessful() && response.body() != null) {
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
for (CalendarEvent e : response.body()) {
|
// Konverter fra Google-modell til KBS-modell
|
||||||
CalendarManager.formatEventForUI(e); // Formatér datoer
|
apiEvents = CalendarManager.convertGoogleResponse(response.body());
|
||||||
apiEvents.add(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Flett listene (API + Personlig) og sorter
|
// 3. Flett listene (API + Personlig) og sorter
|
||||||
List<CalendarEvent> merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents);
|
List<CalendarEvent> merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents);
|
||||||
|
|
||||||
// 4. Filtrer ut hendelser som har passert (vis kun fremtidige + i dag)
|
// 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<CalendarEvent> upcomingEvents = new ArrayList<>();
|
List<CalendarEvent> upcomingEvents = new ArrayList<>();
|
||||||
String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
|
String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
|
||||||
|
|
||||||
|
|
@ -3890,14 +4074,12 @@ public class HomeFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Call<List<CalendarEvent>> call, Throwable t) {
|
public void onFailure(Call<GoogleCalendarModels.Response> call, Throwable t) {
|
||||||
if (!isAdded()) return;
|
if (!isAdded()) return;
|
||||||
// Hvis API feiler, vis bare personlige events hvis vi har noen
|
// Hvis API feiler, vis bare personlige events hvis vi har noen
|
||||||
if (!deviceEvents.isEmpty()) {
|
if (!deviceEvents.isEmpty()) {
|
||||||
List<CalendarEvent> top5 = new ArrayList<>();
|
List<CalendarEvent> top5 = new ArrayList<>();
|
||||||
// Filtrer og plukk topp 5 fra lokale også
|
|
||||||
for(int i=0; i<Math.min(deviceEvents.size(), 5); i++) top5.add(deviceEvents.get(i));
|
for(int i=0; i<Math.min(deviceEvents.size(), 5); i++) top5.add(deviceEvents.get(i));
|
||||||
|
|
||||||
recyclerView.setAdapter(new CalendarAdapter(top5, event -> {
|
recyclerView.setAdapter(new CalendarAdapter(top5, event -> {
|
||||||
CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
|
CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
|
||||||
sheet.show(getParentFragmentManager(), "CalendarDetails");
|
sheet.show(getParentFragmentManager(), "CalendarDetails");
|
||||||
|
|
@ -3931,7 +4113,8 @@ public class HomeFragment extends Fragment {
|
||||||
for (WpPost post : wpPosts) {
|
for (WpPost post : wpPosts) {
|
||||||
try {
|
try {
|
||||||
Date date = rawFormat.parse(post.date);
|
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) {}
|
} catch (Exception e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4314,7 +4497,14 @@ FILSTI: app\src\main\java\com\kbs\kbsintranett\MainActivity.java
|
||||||
============================================================
|
============================================================
|
||||||
package com.kbs.kbsintranett;
|
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.os.Bundle;
|
||||||
|
import android.provider.Settings;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
|
@ -4322,6 +4512,8 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.navigation.NavController;
|
import androidx.navigation.NavController;
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
import androidx.navigation.ui.NavigationUI;
|
import androidx.navigation.ui.NavigationUI;
|
||||||
|
import androidx.work.OneTimeWorkRequest;
|
||||||
|
import androidx.work.WorkManager;
|
||||||
|
|
||||||
import com.google.android.gms.auth.api.signin.GoogleSignIn;
|
import com.google.android.gms.auth.api.signin.GoogleSignIn;
|
||||||
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
|
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
|
||||||
|
|
@ -4346,6 +4538,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
bottomNav = findViewById(R.id.bottom_nav_view);
|
bottomNav = findViewById(R.id.bottom_nav_view);
|
||||||
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
|
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
|
||||||
.findFragmentById(R.id.nav_host_fragment);
|
.findFragmentById(R.id.nav_host_fragment);
|
||||||
|
|
||||||
if (navHostFragment != null) {
|
if (navHostFragment != null) {
|
||||||
navController = navHostFragment.getNavController();
|
navController = navHostFragment.getNavController();
|
||||||
NavigationUI.setupWithNavController(bottomNav, navController);
|
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
|
// 2. Start Silent Sign-In ved oppstart
|
||||||
checkLoginState();
|
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() {
|
private void checkLoginState() {
|
||||||
GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
|
GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
|
|
@ -4780,13 +5013,13 @@ FILSTI: app\src\main\java\com\kbs\kbsintranett\NotificationWorker.java
|
||||||
============================================================
|
============================================================
|
||||||
package com.kbs.kbsintranett;
|
package com.kbs.kbsintranett;
|
||||||
|
|
||||||
import android.app.NotificationChannel;
|
import android.app.AlarmManager;
|
||||||
import android.app.NotificationManager;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.app.NotificationCompat;
|
|
||||||
import androidx.work.Worker;
|
import androidx.work.Worker;
|
||||||
import androidx.work.WorkerParameters;
|
import androidx.work.WorkerParameters;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -4798,8 +5031,7 @@ import retrofit2.Response;
|
||||||
|
|
||||||
public class NotificationWorker extends Worker {
|
public class NotificationWorker extends Worker {
|
||||||
|
|
||||||
private static final String CHANNEL_ID = "kbs_calendar_channel";
|
private static final String TAG = "KBS_DEBUG";
|
||||||
private static final String PREFS_NAME = "KBSNotificationPrefs";
|
|
||||||
|
|
||||||
public NotificationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
public NotificationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
||||||
super(context, workerParams);
|
super(context, workerParams);
|
||||||
|
|
@ -4808,73 +5040,109 @@ public class NotificationWorker extends Worker {
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Result doWork() {
|
public Result doWork() {
|
||||||
// Dette kjører i bakgrunnen
|
Log.d(TAG, "NotificationWorker: Starter sjekk av kalender...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Hent events synkront (ikke enqueue)
|
String url = CalendarManager.getGoogleCalendarUrl();
|
||||||
Response<List<CalendarEvent>> response = RetrofitClient.getApiService().getCalendarEvents().execute();
|
// Hent events synkront
|
||||||
|
Response<GoogleCalendarModels.Response> response = RetrofitClient.getApiService().getDirectGoogleEvents(url).execute();
|
||||||
|
|
||||||
if (response.isSuccessful() && response.body() != null) {
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
checkAndNotify(response.body());
|
List<CalendarEvent> events = CalendarManager.convertGoogleResponse(response.body());
|
||||||
|
scheduleAlarms(events);
|
||||||
return Result.success();
|
return Result.success();
|
||||||
} else {
|
} else {
|
||||||
|
Log.e(TAG, "NotificationWorker: API-kall feilet. Kode: " + response.code());
|
||||||
return Result.retry();
|
return Result.retry();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "NotificationWorker: Nettverksfeil", e);
|
||||||
return Result.retry();
|
return Result.retry();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkAndNotify(List<CalendarEvent> events) {
|
private void scheduleAlarms(List<CalendarEvent> events) {
|
||||||
SharedPreferences prefs = getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
Context context = getApplicationContext();
|
||||||
long now = System.currentTimeMillis();
|
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||||
long fifteenMinutes = 15 * 60 * 1000;
|
|
||||||
|
|
||||||
|
// 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 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) {
|
for (CalendarEvent event : events) {
|
||||||
try {
|
try {
|
||||||
Date eventDate;
|
// Hopp over heldagshendelser (dato uten klokkeslett)
|
||||||
if (event.getRawDate().contains("T")) eventDate = isoFormat.parse(event.getRawDate());
|
if (event.getRawDate().length() == 10) continue;
|
||||||
else eventDate = sqlFormat.parse(event.getRawDate());
|
|
||||||
|
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;
|
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
|
// Vi setter alarmen hvis tidspunktet er i fremtiden
|
||||||
if (diff > 0 && diff < (30 * 60 * 1000)) {
|
// Vi sjekker også at det ikke er mer enn 24 timer frem i tid (for å spare ressurser)
|
||||||
String eventId = event.getTitle() + event.getRawDate(); // Enkel ID
|
if (triggerTime > now && triggerTime < (now + 24 * 60 * 60 * 1000L)) {
|
||||||
boolean alreadyNotified = prefs.getBoolean(eventId, false);
|
|
||||||
|
|
||||||
if (!alreadyNotified) {
|
// Lag en unik ID for alarmen
|
||||||
sendNotification(event.getTitle(), "Starter kl " + event.getTime());
|
String uniqueIdString = event.getTitle() + event.getRawDate();
|
||||||
// Lagre at vi har varslet
|
int alarmId = uniqueIdString.hashCode();
|
||||||
prefs.edit().putBoolean(eventId, true).apply();
|
|
||||||
|
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) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
Log.e(TAG, "Feil ved behandling av event: " + event.getTitle(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void sendNotification(String title, String content) {
|
if (countSet == 0) {
|
||||||
NotificationManager manager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
Log.d(TAG, "Ingen kommende alarmer (innenfor neste 24t) funnet akkurat nå.");
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "KBS Kalender", NotificationManager.IMPORTANCE_HIGH);
|
|
||||||
manager.createNotificationChannel(channel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.Part;
|
||||||
import retrofit2.http.PartMap;
|
import retrofit2.http.PartMap;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
import retrofit2.http.Url; // NY IMPORT
|
||||||
|
|
||||||
public interface WordPressApiService {
|
public interface WordPressApiService {
|
||||||
@GET("wp-json/wp/v2/posts?per_page=10&_embed")
|
@GET("wp-json/wp/v2/posts?per_page=10&_embed")
|
||||||
|
|
@ -5272,9 +5541,14 @@ public interface WordPressApiService {
|
||||||
@Part List<MultipartBody.Part> files
|
@Part List<MultipartBody.Part> files
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ENDRET: Denne brukes ikke lenger for kalender, men beholdes for bakoverkompatibilitet
|
||||||
@GET("wp-json/kbs/v1/calendar/events")
|
@GET("wp-json/kbs/v1/calendar/events")
|
||||||
Call<List<CalendarEvent>> getCalendarEvents();
|
Call<List<CalendarEvent>> getCalendarEvents();
|
||||||
|
|
||||||
|
// NY: Direkte kall mot Google (bruker @Url for å override base URL)
|
||||||
|
@GET
|
||||||
|
Call<GoogleCalendarModels.Response> getDirectGoogleEvents(@Url String fullUrl);
|
||||||
|
|
||||||
@GET("wp-json/gf/v2/entries")
|
@GET("wp-json/gf/v2/entries")
|
||||||
Call<GravityEntryResponse> getEntries(
|
Call<GravityEntryResponse> getEntries(
|
||||||
@Query("form_ids") int formId,
|
@Query("form_ids") int formId,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue