diff --git a/app/src/main/java/com/kbs/kbsintranett/CalendarFullFragment.java b/app/src/main/java/com/kbs/kbsintranett/CalendarFullFragment.java index 0878e49..eabc6d4 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CalendarFullFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/CalendarFullFragment.java @@ -27,7 +27,7 @@ public class CalendarFullFragment extends Fragment { private RecyclerView recyclerView; private ProgressBar progressBar; private TextView emptyView; - private LinearLayoutManager layoutManager; // Trenger denne for å scrolle + private LinearLayoutManager layoutManager; @Nullable @Override @@ -52,24 +52,25 @@ public class CalendarFullFragment extends Fragment { private void fetchAllEvents() { progressBar.setVisibility(View.VISIBLE); - // Hent personlige hendelser (Nå med historikk) + // Hent personlige hendelser List deviceEvents = CalendarManager.getDeviceEvents(getContext()); - // NYTT: Hent fra Google direkte - String url = CalendarManager.getGoogleCalendarUrl(); - RetrofitClient.getApiService().getDirectGoogleEvents(url).enqueue(new Callback() { + // Hent felles hendelser fra WordPress Proxy (sikrer at vi får reminders) + RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback>() { @Override - public void onResponse(Call call, Response response) { + public void onResponse(Call> call, Response> response) { if (!isAdded()) return; progressBar.setVisibility(View.GONE); List apiEvents = new ArrayList<>(); if (response.isSuccessful() && response.body() != null) { - // Konverter og formatér - apiEvents = CalendarManager.convertGoogleResponse(response.body()); + apiEvents = response.body(); + // Formater data for visning + for (CalendarEvent e : apiEvents) { + CalendarManager.formatEventForUI(e); + } } - // Flett og vis List allEvents = CalendarManager.mergeAndSort(apiEvents, deviceEvents); if (allEvents.isEmpty()) { @@ -85,13 +86,12 @@ public class CalendarFullFragment extends Fragment { }); recyclerView.setAdapter(adapter); - // --- SCROLL TIL I DAG --- scrollToToday(allEvents); } } @Override - public void onFailure(Call call, Throwable t) { + public void onFailure(Call> call, Throwable t) { if (!isAdded()) return; progressBar.setVisibility(View.GONE); @@ -114,7 +114,6 @@ public class CalendarFullFragment extends Fragment { String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date()); int scrollIndex = 0; - // Finn første event som er i dag eller senere for (int i = 0; i < events.size(); i++) { String raw = events.get(i).getRawDate(); if (raw != null && raw.compareTo(today) >= 0) { @@ -123,10 +122,9 @@ public class CalendarFullFragment extends Fragment { } } - // Scroll litt ned slik at "i dag" havner på toppen, men ikke helt (offset 0) - // Bruker scrollToPositionWithOffset for presisjon if (scrollIndex > 0) { layoutManager.scrollToPositionWithOffset(scrollIndex, 0); } } + } \ 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 1398c51..a58c761 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java +++ b/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java @@ -4,7 +4,6 @@ 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; @@ -15,96 +14,6 @@ import java.util.Locale; import java.util.TimeZone; public class CalendarManager { - - // --- KONFIGURASJON FOR GOOGLE DIREKTE-KOBLING --- - private static final String GOOGLE_CALENDAR_ID = "kbservice.no_o2bmp5f9f540vedveit51optfo@group.calendar.google.com"; - // TODO: Sett inn din API-nøkkel her! - private static final String GOOGLE_API_KEY = "AIzaSyCos8VW5mClUcuhs86gbSJo8uitY0fVPus"; - - public static String getGoogleCalendarUrl() { - 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; - } - - public static List convertGoogleResponse(GoogleCalendarModels.Response response) { - List events = new ArrayList<>(); - if (response == null || response.items == null) return events; - - for (GoogleCalendarModels.Item item : response.items) { - String title = item.summary != null ? item.summary : "(Uten tittel)"; - String desc = item.description; - String loc = item.location; - - // Dato-logikk - String start = null; - String end = null; - - if (item.start != null) { - if (item.start.dateTime != null) start = item.start.dateTime; - else start = item.start.date; - } - if (item.end != null) { - if (item.end.dateTime != null) end = item.end.dateTime; - else end = item.end.date; - } - - // --- 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; - } 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); - - formatEventForUI(event); - events.add(event); - } - return events; - } - - // --- RESTERENDE KODE (UENDRET) --- - private static List getKbsCalendarIds(Context context) { List ids = new ArrayList<>(); if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) { @@ -209,6 +118,12 @@ public class CalendarManager { } CalendarEvent event = new CalendarEvent(title, rawStart, rawEnd, desc, loc); + + // For lokale events bruker vi ikke reminderMinutes fra API, + // men systemet håndterer varsling selv for lokale kalendere. + // Vi setter den til 0 her for å unngå doble varsler fra appen. + event.setReminderMinutes(0); + formatEventForUI(event); deviceEvents.add(event); } @@ -296,4 +211,5 @@ public class CalendarManager { return all; } + } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/CreateEventRequest.java b/app/src/main/java/com/kbs/kbsintranett/CreateEventRequest.java new file mode 100644 index 0000000..457f7b3 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/CreateEventRequest.java @@ -0,0 +1,4 @@ +package com.kbs.kbsintranett; + +public class CreateEventRequest { +} diff --git a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java index 8e68992..42d8213 100644 --- a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java @@ -37,17 +37,14 @@ public class HomeFragment extends Fragment { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Håndter svar på kalendertillatelse requestPermissionLauncher = registerForActivityResult( new ActivityResultContracts.RequestPermission(), isGranted -> { - // Prøv å laste kalender på nytt (nå potensielt med personlig kalender) if (calendarRecycler != null) { fetchCalendarEvents(calendarRecycler); } } ); - // Start bakgrunnsjobb for varsling (kjører hver 15. minutt) startNotificationWorker(); } @@ -60,45 +57,38 @@ public class HomeFragment extends Fragment { @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - // 0. Profil-knapp + View profileBtn = view.findViewById(R.id.btn_profile); if (profileBtn != null) { profileBtn.setOnClickListener(v -> Navigation.findNavController(view).navigate(R.id.navigation_profile)); } - // 1. Kalender oppsett calendarRecycler = view.findViewById(R.id.recycler_calendar); calendarRecycler.setLayoutManager(new LinearLayoutManager(getContext())); - // Sett tom adapter midlertidig calendarRecycler.setAdapter(new CalendarAdapter(new ArrayList<>(), event -> {})); - // "Se alle" knapp for kalender + TextView viewAllCalendar = view.findViewById(R.id.btn_view_all_calendar); if (viewAllCalendar != null) { viewAllCalendar.setOnClickListener(v -> Navigation.findNavController(view).navigate(R.id.action_home_to_calendarFull)); } - // Sjekk tillatelse for personlig kalender if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED) { fetchCalendarEvents(calendarRecycler); } else { - // Spør om lov til å lese kalender requestPermissionLauncher.launch(Manifest.permission.READ_CALENDAR); } - // Spør også om lov til å sende varsler (Android 13+) if (android.os.Build.VERSION.SDK_INT >= 33) { if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {}).launch(Manifest.permission.POST_NOTIFICATIONS); } } - // 2. Nyheter oppsett RecyclerView newsRecycler = view.findViewById(R.id.recycler_news); newsRecycler.setLayoutManager(new LinearLayoutManager(getContext())); newsRecycler.setNestedScrollingEnabled(false); - // Sett tom adapter midlertidig newsRecycler.setAdapter(new NewsAdapter(new ArrayList<>(), item -> {})); - // "Se alle" knapp for nyheter + TextView viewAllNews = view.findViewById(R.id.btn_view_all_news); if (viewAllNews != null) { viewAllNews.setOnClickListener(v -> { @@ -110,26 +100,28 @@ public class HomeFragment extends Fragment { } private void fetchCalendarEvents(RecyclerView recyclerView) { - // 1. Hent personlige hendelser først (fra CalendarManager) + // 1. Hent personlige hendelser List deviceEvents = CalendarManager.getDeviceEvents(getContext()); - // 2. Hent API-hendelser DIREKTE fra Google - String url = CalendarManager.getGoogleCalendarUrl(); - RetrofitClient.getApiService().getDirectGoogleEvents(url).enqueue(new Callback() { + // 2. Hent felles hendelser fra WordPress (Proxy mot Google) + RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback>() { @Override - public void onResponse(Call call, Response response) { + public void onResponse(Call> call, Response> response) { if (!isAdded()) return; List apiEvents = new ArrayList<>(); if (response.isSuccessful() && response.body() != null) { - // Konverter fra Google-modell til KBS-modell - apiEvents = CalendarManager.convertGoogleResponse(response.body()); + apiEvents = response.body(); + // Formater datoer for visning (UI-logikk) + for (CalendarEvent e : apiEvents) { + CalendarManager.formatEventForUI(e); + } } - // 3. Flett listene (API + Personlig) og sorter + // 3. Flett og sorter List merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents); - // 4. Filtrer ut hendelser som har passert (vis kun fremtidige + i dag) + // 4. Filtrer (kun fremtidige + i dag) List upcomingEvents = new ArrayList<>(); String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date()); @@ -139,7 +131,7 @@ public class HomeFragment extends Fragment { } } - // 5. Vis kun de 5 første av de kommende + // 5. Vis topp 5 List top5 = new ArrayList<>(); for(int i=0; i call, Throwable t) { + public void onFailure(Call> call, Throwable t) { if (!isAdded()) return; - // Hvis API feiler, vis bare personlige events hvis vi har noen + // Fallback til kun lokale events if (!deviceEvents.isEmpty()) { List top5 = new ArrayList<>(); for(int i=0; i>() { @Override public void onResponse(Call> call, Response> response) { @@ -181,8 +172,6 @@ public class HomeFragment extends Fragment { if (response.isSuccessful() && response.body() != null) { List wpPosts = response.body(); - - // Datoformatering SimpleDateFormat rawFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); rawFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); SimpleDateFormat targetFormat = new SimpleDateFormat("dd. MMM yyyy", Locale.getDefault()); @@ -192,15 +181,12 @@ public class HomeFragment extends Fragment { try { Date date = rawFormat.parse(post.date); post.date = targetFormat.format(date); - // Setter pen dato } catch (Exception e) {} } - // Sett adapter med Click Listener NewsAdapter adapter = new NewsAdapter(wpPosts, post -> { - // Naviger til detaljvisning og send med post-objektet Bundle bundle = new Bundle(); - bundle.putSerializable("post_data", post); // WpPost er nå Serializable + bundle.putSerializable("post_data", post); Navigation.findNavController(getView()).navigate(R.id.action_home_to_newsDetail, bundle); }); recyclerView.setAdapter(adapter); @@ -210,17 +196,16 @@ public class HomeFragment extends Fragment { @Override public void onFailure(Call> call, Throwable t) { if (getContext() == null) return; - // Ved feil, sett tom liste (eller vis feilmelding) recyclerView.setAdapter(new NewsAdapter(new ArrayList<>(), null)); } }); } private void startNotificationWorker() { - // Kjører en jobb hvert 15. minutt for å sjekke om det er nye møter i HMS-kalenderen PeriodicWorkRequest notifRequest = new PeriodicWorkRequest.Builder(NotificationWorker.class, 15, TimeUnit.MINUTES) .build(); WorkManager.getInstance(requireContext()).enqueue(notifRequest); } + } \ 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 df600de..ad618aa 100644 --- a/app/src/main/java/com/kbs/kbsintranett/NotificationWorker.java +++ b/app/src/main/java/com/kbs/kbsintranett/NotificationWorker.java @@ -17,7 +17,6 @@ import java.util.Locale; import retrofit2.Response; public class NotificationWorker extends Worker { - private static final String TAG = "KBS_DEBUG"; public NotificationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { @@ -27,18 +26,22 @@ public class NotificationWorker extends Worker { @NonNull @Override public Result doWork() { - Log.d(TAG, "NotificationWorker: Starter sjekk av kalender..."); + Log.d(TAG, "NotificationWorker: Starter sjekk av kalender via WordPress Proxy..."); try { - String url = CalendarManager.getGoogleCalendarUrl(); - Response response = RetrofitClient.getApiService().getDirectGoogleEvents(url).execute(); + Response> response = RetrofitClient.getApiService().getCalendarEvents().execute(); if (response.isSuccessful() && response.body() != null) { - List events = CalendarManager.convertGoogleResponse(response.body()); + List events = response.body(); scheduleAlarms(events); return Result.success(); } else { - Log.e(TAG, "NotificationWorker: API-kall feilet. Kode: " + response.code()); + int code = response.code(); + Log.e(TAG, "NotificationWorker: API-kall mot WP feilet. Kode: " + code); + // Stopp retry ved klientfeil (4xx) inkludert 429 + if (code >= 400 && code < 500) { + return Result.failure(); + } return Result.retry(); } } catch (IOException e) { @@ -53,54 +56,73 @@ public class NotificationWorker extends Worker { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (!alarmManager.canScheduleExactAlarms()) { - Log.e(TAG, "NotificationWorker: MANGLER fortsatt tillatelse!"); + Log.e(TAG, "NotificationWorker: MANGLER tillatelse for nøyaktige alarmer!"); return; } } long now = System.currentTimeMillis(); SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); + + // Tillater alarmer som skulle gått for inntil 30 minutter siden (Catch-up) + long catchUpWindow = now - (30 * 60 * 1000L); + // Setter ikke alarmer lenger frem enn 24 timer + long futureWindow = now + (24 * 60 * 60 * 1000L); + int countSet = 0; for (CalendarEvent event : events) { try { - if (event.getRawDate().length() == 10) continue; + String title = event.getTitle(); + + // Ignorer heldagshendelser (datoformat uten klokkeslett) + if (event.getRawDate() == null || event.getRawDate().length() == 10) continue; Date eventDate = null; if (event.getRawDate().contains("T")) { String raw = event.getRawDate(); + // Kutter tidssone for å parse som lokal tid "face value" if (raw.length() > 19) raw = raw.substring(0, 19); eventDate = isoFormat.parse(raw); } if (eventDate == null) continue; - long triggerTime = eventDate.getTime() - (event.getReminderMinutes() * 60 * 1000L); + int minutesBefore = event.getReminderMinutes(); + long triggerTime = eventDate.getTime() - (minutesBefore * 60 * 1000L); - // --- 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)); + // --- DIAGNOSE: Logg alle "Test"-events for å se hva PHP faktisk leverer --- + if (title.toLowerCase().contains("test")) { + Log.d(TAG, "--------------------------------------------------"); + Log.d(TAG, "DIAGNOSE EVENT: " + title); + Log.d(TAG, " Starttid: " + eventDate); + Log.d(TAG, " PHP sier reminderMinutes: " + minutesBefore); - if (triggerTime > now) { - Log.d(TAG, " Status: I FREMTIDEN (Alarm skal settes)"); + if (minutesBefore <= 0) { + Log.d(TAG, " HANDLING: Skippes (Ingen varsling)"); } else { - Log.d(TAG, " Status: I FORTIDEN (Hoppes over)"); + Log.d(TAG, " Ønsket alarmtid: " + new Date(triggerTime)); + Log.d(TAG, " Nå: " + new Date(now)); } } - // ---------------------------------------- + // ------------------------------------------------------------------------ - if (triggerTime > now && triggerTime < (now + 24 * 60 * 60 * 1000L)) { + if (minutesBefore <= 0) continue; - String uniqueIdString = event.getTitle() + event.getRawDate(); - int alarmId = uniqueIdString.hashCode(); + if (triggerTime > catchUpWindow && triggerTime < futureWindow) { + + // CATCH-UP: Hvis tiden har passert, fyr av NÅ. + if (triggerTime < now) { + Log.i(TAG, " -> Alarmtid passert (" + new Date(triggerTime) + "). Kjører Catch-up NÅ."); + triggerTime = now + 1000; + } + + int alarmId = (title + event.getRawDate()).hashCode(); Intent intent = new Intent(context, AlarmReceiver.class); - intent.putExtra("TITLE", event.getTitle()); - intent.putExtra("MESSAGE", "Starter kl " + event.getTime()); + intent.putExtra("TITLE", title); + String timeStr = new SimpleDateFormat("HH:mm", Locale.getDefault()).format(eventDate); + intent.putExtra("MESSAGE", "Starter kl " + timeStr); intent.putExtra("ID", alarmId); PendingIntent pendingIntent = PendingIntent.getBroadcast( @@ -116,7 +138,10 @@ public class NotificationWorker extends Worker { alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); } - Log.i(TAG, ">>> ALARM SATT: " + event.getTitle()); + if (title.toLowerCase().contains("test")) { + Log.d(TAG, " RESULTAT: ALARM SATT OK"); + Log.d(TAG, "--------------------------------------------------"); + } countSet++; } @@ -126,7 +151,7 @@ public class NotificationWorker extends Worker { } if (countSet == 0) { - Log.d(TAG, "Ingen nye alarmer satt (ingen hendelser innenfor neste 24t)."); + Log.d(TAG, "Ingen nye alarmer satt i denne runden."); } } } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java b/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java index b380da3..dff915a 100644 --- a/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java +++ b/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java @@ -1,7 +1,7 @@ package com.kbs.kbsintranett; import com.google.gson.JsonElement; -import com.google.gson.JsonObject; // NY IMPORT +import com.google.gson.JsonObject; import java.util.List; import java.util.Map; import okhttp3.MultipartBody; @@ -15,7 +15,6 @@ import retrofit2.http.Multipart; import retrofit2.http.Part; import retrofit2.http.PartMap; import retrofit2.http.Query; -import retrofit2.http.Url; // NY IMPORT public interface WordPressApiService { @GET("wp-json/wp/v2/posts?per_page=10&_embed") @@ -41,14 +40,11 @@ public interface WordPressApiService { @Part List files ); - // ENDRET: Denne brukes ikke lenger for kalender, men beholdes for bakoverkompatibilitet + // BRUKES NÅ: Henter kalenderhendelser via WordPress proxy. +// Dette sikrer at vi får med reminder_minutes fra serveren. @GET("wp-json/kbs/v1/calendar/events") Call> getCalendarEvents(); - // NY: Direkte kall mot Google (bruker @Url for å override base URL) - @GET - Call getDirectGoogleEvents(@Url String fullUrl); - @GET("wp-json/gf/v2/entries") Call getEntries( @Query("form_ids") int formId, @@ -68,7 +64,7 @@ public interface WordPressApiService { @GET("wp-json/kbs/v1/handbook/{id}") Call getHandbookPage(@Path("id") int id); - // NY: Slå opp ID fra URL @GET("wp-json/kbs/v1/lookup-id") Call lookupPageId(@Query("url") String url); + } \ No newline at end of file