diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 93a976a..1521411 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -59,6 +59,16 @@
android:resource="@xml/file_paths" />
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/kbs/kbsintranett/AlarmReceiver.java b/app/src/main/java/com/kbs/kbsintranett/AlarmReceiver.java
index 6811f6e..51ddf3f 100644
--- a/app/src/main/java/com/kbs/kbsintranett/AlarmReceiver.java
+++ b/app/src/main/java/com/kbs/kbsintranett/AlarmReceiver.java
@@ -12,6 +12,7 @@ import android.os.Build;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
+import androidx.core.content.ContextCompat; // <-- Denne manglet
public class AlarmReceiver extends BroadcastReceiver {
@@ -48,7 +49,8 @@ public class AlarmReceiver extends BroadcastReceiver {
);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
- .setSmallIcon(R.drawable.ic_launcher_foreground) // Pass på at du har et ikon her, ellers bruk R.mipmap.ic_launcher
+ .setSmallIcon(R.drawable.ic_stat_kbs)
+ .setColor(ContextCompat.getColor(context, R.color.kbs_logo_blue)) // Setter KBS-blå farge på ikonet
.setContentTitle(title)
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_HIGH)
diff --git a/app/src/main/java/com/kbs/kbsintranett/CacheManager.java b/app/src/main/java/com/kbs/kbsintranett/CacheManager.java
new file mode 100644
index 0000000..de254a6
--- /dev/null
+++ b/app/src/main/java/com/kbs/kbsintranett/CacheManager.java
@@ -0,0 +1,95 @@
+package com.kbs.kbsintranett;
+
+import android.content.Context;
+import android.util.Log;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CacheManager {
+ private static final String FILE_CALENDAR = "cache_calendar.json";
+ private static final String FILE_NEWS = "cache_news.json";
+ private static final String FILE_HANDBOOK = "cache_handbook.json";
+
+ private static final String TAG = "CacheManager";
+ private static final Gson gson = new Gson();
+
+ // --- KALENDER ---
+ public static void saveCalendarEvents(Context context, List events) {
+ saveList(context, FILE_CALENDAR, events);
+ }
+
+ public static List getCachedCalendarEvents(Context context) {
+ Type type = new TypeToken>() {}.getType();
+ List list = loadList(context, FILE_CALENDAR, type);
+ return list != null ? list : new ArrayList<>();
+ }
+
+ // --- NYHETER ---
+ public static void saveNewsPosts(Context context, List posts) {
+ saveList(context, FILE_NEWS, posts);
+ }
+
+ public static List getCachedNewsPosts(Context context) {
+ Type type = new TypeToken>() {}.getType();
+ List list = loadList(context, FILE_NEWS, type);
+ return list != null ? list : new ArrayList<>();
+ }
+
+ // --- HÅNDBOK ---
+ public static void saveHandbookItems(Context context, List items) {
+ saveList(context, FILE_HANDBOOK, items);
+ }
+
+ public static List getCachedHandbookItems(Context context) {
+ Type type = new TypeToken>() {}.getType();
+ List list = loadList(context, FILE_HANDBOOK, type);
+ return list != null ? list : new ArrayList<>();
+ }
+
+ // --- GENERISKE HJELPEMETODER ---
+
+ private static void saveList(Context context, String filename, List list) {
+ if (context == null || list == null) return;
+ new Thread(() -> {
+ try {
+ String json = gson.toJson(list);
+ FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE);
+ fos.write(json.getBytes());
+ fos.close();
+ Log.d(TAG, "Lagret cache til " + filename);
+ } catch (Exception e) {
+ Log.e(TAG, "Feil ved lagring av cache: " + filename, e);
+ }
+ }).start();
+ }
+
+ private static List loadList(Context context, String filename, Type type) {
+ if (context == null) return null;
+ File file = new File(context.getFilesDir(), filename);
+ if (!file.exists()) return null;
+
+ try {
+ FileInputStream fis = context.openFileInput(filename);
+ InputStreamReader isr = new InputStreamReader(fis);
+ BufferedReader bufferedReader = new BufferedReader(isr);
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ sb.append(line);
+ }
+ fis.close();
+ return gson.fromJson(sb.toString(), type);
+ } catch (Exception e) {
+ Log.e(TAG, "Feil ved lesing av cache: " + filename, e);
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java
index a871508..a4bdbee 100644
--- a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java
+++ b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java
@@ -11,6 +11,7 @@ import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
+import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
@@ -89,7 +90,6 @@ public class HomeFragment extends Fragment {
btnCreateEvent.setVisibility(View.GONE);
}
- // KALENDER: Standard scrolling (slik at man kan scrolle i vinduet på 230dp)
calendarRecycler = view.findViewById(R.id.recycler_calendar);
calendarRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
calendarRecycler.setAdapter(new CalendarAdapter(new ArrayList<>(), event -> {}));
@@ -105,7 +105,6 @@ public class HomeFragment extends Fragment {
}
}
- // NYHETER: Nested scrolling disabled (skal vises i full høyde under kalenderen)
newsRecycler = view.findViewById(R.id.recycler_news);
newsRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
newsRecycler.setNestedScrollingEnabled(false);
@@ -167,33 +166,21 @@ public class HomeFragment extends Fragment {
List apiEvents = new ArrayList<>();
if (response.isSuccessful() && response.body() != null) {
apiEvents = response.body();
+
+ CacheManager.saveCalendarEvents(getContext(), apiEvents);
+
for (CalendarEvent e : apiEvents) {
CalendarManager.formatEventForUI(e);
}
- }
-
- List merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents);
- List upcomingEvents = new ArrayList<>();
- String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
-
- for (CalendarEvent e : merged) {
- if (e.getRawDate() != null && e.getRawDate().compareTo(today) >= 0) {
- upcomingEvents.add(e);
+ } else {
+ apiEvents = CacheManager.getCachedCalendarEvents(getContext());
+ for (CalendarEvent e : apiEvents) CalendarManager.formatEventForUI(e);
+ if (!apiEvents.isEmpty()) {
+ Toast.makeText(getContext(), "Server utilgjengelig. Viser lagret kalender.", Toast.LENGTH_SHORT).show();
}
}
- List topEvents = new ArrayList<>();
- // ENDRET: Grensen er satt tilbake til 5
- for(int i=0; i {
- CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
- sheet.setOnEventChangeListener(HomeFragment.this::refreshData);
- sheet.show(getParentFragmentManager(), "CalendarDetails");
- }));
-
+ updateCalendarUI(recyclerView, apiEvents, deviceEvents);
checkLoadingComplete();
}
@@ -201,23 +188,42 @@ public class HomeFragment extends Fragment {
public void onFailure(Call> call, Throwable t) {
if (!isAdded()) return;
- if (!deviceEvents.isEmpty()) {
- List topEvents = new ArrayList<>();
- // ENDRET: Grensen er satt tilbake til 5 (Fallback)
- for(int i=0; i {
- CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
- sheet.show(getParentFragmentManager(), "CalendarDetails");
- }));
- } else {
- recyclerView.setAdapter(new CalendarAdapter(new ArrayList<>(), null));
+ List cachedApiEvents = CacheManager.getCachedCalendarEvents(getContext());
+ for (CalendarEvent e : cachedApiEvents) CalendarManager.formatEventForUI(e);
+
+ if (!cachedApiEvents.isEmpty()) {
+ Toast.makeText(getContext(), "Ingen nettverk. Viser lagret kalender.", Toast.LENGTH_SHORT).show();
}
+ updateCalendarUI(recyclerView, cachedApiEvents, deviceEvents);
checkLoadingComplete();
}
});
}
+ private void updateCalendarUI(RecyclerView recyclerView, List apiEvents, List deviceEvents) {
+ List merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents);
+ List upcomingEvents = new ArrayList<>();
+ String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
+
+ for (CalendarEvent e : merged) {
+ if (e.getRawDate() != null && e.getRawDate().compareTo(today) >= 0) {
+ upcomingEvents.add(e);
+ }
+ }
+
+ List topEvents = new ArrayList<>();
+ for(int i=0; i {
+ CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
+ sheet.setOnEventChangeListener(HomeFragment.this::refreshData);
+ sheet.show(getParentFragmentManager(), "CalendarDetails");
+ }));
+ }
+
private void fetchNewsFromWordpress(RecyclerView recyclerView) {
WordPressApiService apiService = RetrofitClient.getApiService();
apiService.getPosts().enqueue(new Callback>() {
@@ -225,39 +231,58 @@ public class HomeFragment extends Fragment {
public void onResponse(Call> call, Response> response) {
if (getContext() == null) return;
+ List postsToShow = new ArrayList<>();
+
if (response.isSuccessful() && response.body() != null) {
- List wpPosts = response.body();
- 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());
- targetFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
-
- for (WpPost post : wpPosts) {
- try {
- Date date = rawFormat.parse(post.date);
- post.date = targetFormat.format(date);
- } catch (Exception e) {}
+ postsToShow = response.body();
+ CacheManager.saveNewsPosts(getContext(), postsToShow);
+ } else {
+ postsToShow = CacheManager.getCachedNewsPosts(getContext());
+ if (!postsToShow.isEmpty()) {
+ Toast.makeText(getContext(), "Server utilgjengelig. Viser lagrede nyheter.", Toast.LENGTH_SHORT).show();
}
-
- NewsAdapter adapter = new NewsAdapter(wpPosts, post -> {
- Bundle bundle = new Bundle();
- bundle.putSerializable("post_data", post);
- Navigation.findNavController(getView()).navigate(R.id.action_home_to_newsDetail, bundle);
- });
- recyclerView.setAdapter(adapter);
}
+
+ updateNewsUI(recyclerView, postsToShow);
checkLoadingComplete();
}
@Override
public void onFailure(Call> call, Throwable t) {
if (getContext() == null) return;
- recyclerView.setAdapter(new NewsAdapter(new ArrayList<>(), null));
+
+ List cachedPosts = CacheManager.getCachedNewsPosts(getContext());
+ if (!cachedPosts.isEmpty()) {
+ Toast.makeText(getContext(), "Ingen nettverk. Viser lagrede nyheter.", Toast.LENGTH_SHORT).show();
+ }
+
+ updateNewsUI(recyclerView, cachedPosts);
checkLoadingComplete();
}
});
}
+ private void updateNewsUI(RecyclerView recyclerView, List posts) {
+ 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());
+ targetFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
+
+ for (WpPost post : posts) {
+ try {
+ Date date = rawFormat.parse(post.date);
+ post.date = targetFormat.format(date);
+ } catch (Exception e) {}
+ }
+
+ NewsAdapter adapter = new NewsAdapter(posts, post -> {
+ Bundle bundle = new Bundle();
+ bundle.putSerializable("post_data", post);
+ Navigation.findNavController(getView()).navigate(R.id.action_home_to_newsDetail, bundle);
+ });
+ recyclerView.setAdapter(adapter);
+ }
+
private void startNotificationWorker() {
PeriodicWorkRequest notifRequest =
new PeriodicWorkRequest.Builder(NotificationWorker.class, 15, TimeUnit.MINUTES)
diff --git a/app/src/main/res/drawable-hdpi/ic_stat_kbs.png b/app/src/main/res/drawable-hdpi/ic_stat_kbs.png
new file mode 100644
index 0000000..0aa6148
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stat_kbs.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_stat_kbs.png b/app/src/main/res/drawable-mdpi/ic_stat_kbs.png
new file mode 100644
index 0000000..789bb4b
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_stat_kbs.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_kbs.png b/app/src/main/res/drawable-xhdpi/ic_stat_kbs.png
new file mode 100644
index 0000000..7143739
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stat_kbs.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_kbs.png b/app/src/main/res/drawable-xxhdpi/ic_stat_kbs.png
new file mode 100644
index 0000000..e05de22
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_stat_kbs.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_stat_kbs.png b/app/src/main/res/drawable-xxxhdpi/ic_stat_kbs.png
new file mode 100644
index 0000000..819488a
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_stat_kbs.png differ
diff --git a/hele_prosjektet.txt b/hele_prosjektet.txt
index 13af76b..3bf6d6a 100644
--- a/hele_prosjektet.txt
+++ b/hele_prosjektet.txt
@@ -8,8 +8,11 @@ FILSTI: build.gradle.kts
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
+ // NY LINJE: Legg til Google Services plugin her
+ id("com.google.gms.google-services") version "4.4.2" apply false
}
+
============================================================
FILSTI: settings.gradle.kts
============================================================
@@ -43,6 +46,8 @@ FILSTI: app\build.gradle.kts
============================================================
plugins {
alias(libs.plugins.android.application)
+ // NY LINJE: Aktiver Google Services plugin her
+ id("com.google.gms.google-services")
}
android {
@@ -53,8 +58,8 @@ android {
applicationId = "com.kbs.kbsintranett"
minSdk = 28
targetSdk = 34
- versionCode = 1
- versionName = "1.0"
+ versionCode = 3
+ versionName = "1.4"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -106,8 +111,15 @@ dependencies {
// Swipe Refresh Layout
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
+
+ // NY LINJE: Firebase BOM (Bill of Materials) styrer versjoner
+ implementation(platform("com.google.firebase:firebase-bom:33.1.2"))
+
+ // NY LINJE: (Valgfritt, men lurt for statistikk)
+ implementation("com.google.firebase:firebase-analytics")
}
+
============================================================
FILSTI: app\proguard-rules.pro
============================================================
@@ -227,6 +239,16 @@ FILSTI: app\src\main\AndroidManifest.xml
android:resource="@xml/file_paths" />
+
+
+
+
+
+
@@ -248,6 +270,7 @@ import android.os.Build;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
+import androidx.core.content.ContextCompat; // <-- Denne manglet
public class AlarmReceiver extends BroadcastReceiver {
@@ -284,7 +307,8 @@ public class AlarmReceiver extends BroadcastReceiver {
);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
- .setSmallIcon(R.drawable.ic_launcher_foreground) // Pass på at du har et ikon her, ellers bruk R.mipmap.ic_launcher
+ .setSmallIcon(R.drawable.ic_stat_kbs)
+ .setColor(ContextCompat.getColor(context, R.color.kbs_logo_blue)) // Setter KBS-blå farge på ikonet
.setContentTitle(title)
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_HIGH)
@@ -388,14 +412,116 @@ public class AuthRepository {
}
}
+============================================================
+FILSTI: app\src\main\java\com\kbs\kbsintranett\CacheManager.java
+============================================================
+package com.kbs.kbsintranett;
+
+import android.content.Context;
+import android.util.Log;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CacheManager {
+ private static final String FILE_CALENDAR = "cache_calendar.json";
+ private static final String FILE_NEWS = "cache_news.json";
+ private static final String FILE_HANDBOOK = "cache_handbook.json";
+
+ private static final String TAG = "CacheManager";
+ private static final Gson gson = new Gson();
+
+ // --- KALENDER ---
+ public static void saveCalendarEvents(Context context, List events) {
+ saveList(context, FILE_CALENDAR, events);
+ }
+
+ public static List getCachedCalendarEvents(Context context) {
+ Type type = new TypeToken>() {}.getType();
+ List list = loadList(context, FILE_CALENDAR, type);
+ return list != null ? list : new ArrayList<>();
+ }
+
+ // --- NYHETER ---
+ public static void saveNewsPosts(Context context, List posts) {
+ saveList(context, FILE_NEWS, posts);
+ }
+
+ public static List getCachedNewsPosts(Context context) {
+ Type type = new TypeToken>() {}.getType();
+ List list = loadList(context, FILE_NEWS, type);
+ return list != null ? list : new ArrayList<>();
+ }
+
+ // --- HÅNDBOK ---
+ public static void saveHandbookItems(Context context, List items) {
+ saveList(context, FILE_HANDBOOK, items);
+ }
+
+ public static List getCachedHandbookItems(Context context) {
+ Type type = new TypeToken>() {}.getType();
+ List list = loadList(context, FILE_HANDBOOK, type);
+ return list != null ? list : new ArrayList<>();
+ }
+
+ // --- GENERISKE HJELPEMETODER ---
+
+ private static void saveList(Context context, String filename, List list) {
+ if (context == null || list == null) return;
+ new Thread(() -> {
+ try {
+ String json = gson.toJson(list);
+ FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE);
+ fos.write(json.getBytes());
+ fos.close();
+ Log.d(TAG, "Lagret cache til " + filename);
+ } catch (Exception e) {
+ Log.e(TAG, "Feil ved lagring av cache: " + filename, e);
+ }
+ }).start();
+ }
+
+ private static List loadList(Context context, String filename, Type type) {
+ if (context == null) return null;
+ File file = new File(context.getFilesDir(), filename);
+ if (!file.exists()) return null;
+
+ try {
+ FileInputStream fis = context.openFileInput(filename);
+ InputStreamReader isr = new InputStreamReader(fis);
+ BufferedReader bufferedReader = new BufferedReader(isr);
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ sb.append(line);
+ }
+ fis.close();
+ return gson.fromJson(sb.toString(), type);
+ } catch (Exception e) {
+ Log.e(TAG, "Feil ved lesing av cache: " + filename, e);
+ return null;
+ }
+ }
+}
+
============================================================
FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarAdapter.java
============================================================
package com.kbs.kbsintranett;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
@@ -410,7 +536,6 @@ public class CalendarAdapter extends RecyclerView.Adapter events, OnItemClickListener listener) {
this.events = events;
this.listener = listener;
@@ -431,6 +556,15 @@ public class CalendarAdapter extends RecyclerView.Adapter {
if (listener != null) {
listener.onItemClick(event);
@@ -445,6 +579,7 @@ public class CalendarAdapter extends RecyclerView.Adapter confirmDelete());
-
- btnEdit.setOnClickListener(v -> {
- // Send eventet videre til redigering
- Bundle bundle = new Bundle();
- bundle.putSerializable("edit_event", event);
- // Vi må navigere via parent fragmentets navController
- NavHostFragment.findNavController(this).navigate(R.id.navigation_create_event, bundle);
- dismiss();
- });
+ if (canEdit) {
+ adminLayout.setVisibility(View.VISIBLE);
+ btnDelete.setOnClickListener(v -> confirmDelete());
+ btnEdit.setOnClickListener(v -> {
+ Bundle bundle = new Bundle();
+ bundle.putSerializable("edit_event", event);
+ NavHostFragment.findNavController(this).navigate(R.id.navigation_create_event, bundle);
+ dismiss();
+ });
+ } else {
+ adminLayout.setVisibility(View.GONE);
+ }
}
return view;
@@ -553,15 +709,8 @@ public class CalendarDetailsBottomSheet extends BottomSheetDialogFragment {
}
private void deleteEvent() {
- // Vi må sende en CreateEventRequest med ID for å slette
- // Vi trenger ikke fylle ut alt, bare ID og kalendertype
- // Siden vi ikke vet nøyaktig hvilken kalender den kom fra (APIet gir ikke det),
- // prøver vi "Felles" som default, eller prøver å slette fra ID.
- // PHP-koden vi lagde støtter sletting basert på ID hvis vi sender riktig kalendertype.
- // For nå antar vi "Felles" eller looper i backend. I V11.3 PHP scriptet sletter den basert på ID i valgt kalender.
-
CreateEventRequest req = new CreateEventRequest(
- "", "", "", "", "", "Felles", new ArrayList<>(), false, ""
+ "", "", "", "", "", event.getCalendarName(), new ArrayList<>(), false, ""
);
req.id = event.getId();
@@ -570,10 +719,15 @@ public class CalendarDetailsBottomSheet extends BottomSheetDialogFragment {
public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
Toast.makeText(getContext(), "Slettet!", Toast.LENGTH_SHORT).show();
+
+ // VARSLE LISTEN OM AT NOE ER SLETTET
+ if (changeListener != null) {
+ changeListener.onEventChanged();
+ }
+
dismiss();
- // Her burde vi ideelt sett oppdatert listen bak, men brukeren kan dra for å oppdatere
} else {
- Toast.makeText(getContext(), "Kunne ikke slette (Er det en felles-hendelse?)", Toast.LENGTH_LONG).show();
+ Toast.makeText(getContext(), "Kunne ikke slette", Toast.LENGTH_LONG).show();
}
}
@@ -613,10 +767,16 @@ public class CalendarEvent implements Serializable {
@SerializedName("location")
private String location;
- // V11.0: Liste av minutter (f.eks [15, 60])
@SerializedName("reminders")
private List reminders = new ArrayList<>();
+ // NYE FELTER V12.2
+ @SerializedName("calendar_name")
+ private String calendarName;
+
+ @SerializedName("calendar_color")
+ private String calendarColor;
+
// UI-hjelpefelter
private String day;
private String month;
@@ -638,14 +798,18 @@ public class CalendarEvent implements Serializable {
public String getDescription() { return description != null ? description : ""; }
public String getLocation() { return location != null ? location : ""; }
- // Henter listen. Hvis den er null (gamle data), returner tom liste.
public List getReminders() {
return reminders != null ? reminders : new ArrayList<>();
}
-// --- KOMPATIBILITETS-METODER (For å fikse build-feil i CalendarManager og Worker) ---
+ // NYE GETTERS/SETTERS
+ public String getCalendarName() { return calendarName != null ? calendarName : "Ukjent"; }
+ public void setCalendarName(String name) { this.calendarName = name; }
- // Brukes av CalendarManager.java for lokale events
+ public String getCalendarColor() { return calendarColor != null ? calendarColor : "#888888"; }
+ public void setCalendarColor(String color) { this.calendarColor = color; }
+
+ // --- KOMPATIBILITETS-METODER ---
public void setReminderMinutes(int minutes) {
this.reminders = new ArrayList<>();
if (minutes > 0) {
@@ -653,7 +817,6 @@ public class CalendarEvent implements Serializable {
}
}
- // Brukes hvis gammel kode prøver å hente ett tall. Returnerer det første i listen.
public int getReminderMinutes() {
if (reminders != null && !reminders.isEmpty()) {
return reminders.get(0);
@@ -676,6 +839,8 @@ FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarFullFragment.java
package com.kbs.kbsintranett;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -721,16 +886,25 @@ public class CalendarFullFragment extends Fragment {
layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
backBtn.setOnClickListener(v -> Navigation.findNavController(view).navigateUp());
+ }
+ @Override
+ public void onResume() {
+ super.onResume();
fetchAllEvents();
}
private void fetchAllEvents() {
progressBar.setVisibility(View.VISIBLE);
- // Hent personlige hendelser
- List deviceEvents = CalendarManager.getDeviceEvents(getContext());
- // Hent felles hendelser fra WordPress Proxy (sikrer at vi får reminders)
+ new Thread(() -> {
+ // HER ER ENDRINGEN: isPreview = false (Hent alt)
+ List deviceEvents = CalendarManager.getDeviceEvents(getContext(), false);
+ new Handler(Looper.getMainLooper()).post(() -> fetchApiEvents(deviceEvents));
+ }).start();
+ }
+
+ private void fetchApiEvents(List deviceEvents) {
RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback>() {
@Override
public void onResponse(Call> call, Response> response) {
@@ -740,7 +914,6 @@ public class CalendarFullFragment extends Fragment {
List apiEvents = new ArrayList<>();
if (response.isSuccessful() && response.body() != null) {
apiEvents = response.body();
- // Formater data for visning
for (CalendarEvent e : apiEvents) {
CalendarManager.formatEventForUI(e);
}
@@ -757,6 +930,7 @@ public class CalendarFullFragment extends Fragment {
CalendarAdapter adapter = new CalendarAdapter(allEvents, event -> {
CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
+ sheet.setOnEventChangeListener(CalendarFullFragment.this::fetchAllEvents);
sheet.show(getParentFragmentManager(), "CalendarDetails");
});
recyclerView.setAdapter(adapter);
@@ -801,7 +975,6 @@ public class CalendarFullFragment extends Fragment {
layoutManager.scrollToPositionWithOffset(scrollIndex, 0);
}
}
-
}
============================================================
@@ -818,8 +991,10 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import java.util.TimeZone;
public class CalendarManager {
@@ -853,7 +1028,13 @@ public class CalendarManager {
return ids;
}
- public static List getDeviceEvents(Context context) {
+ /**
+ * Henter hendelser fra enheten.
+ * @param context App Context
+ * @param isPreview Hvis true: Henter kun kommende måned (Raskt). Hvis false: Henter -1 til +6 mnd (Tregere).
+ * @return Liste med events
+ */
+ public static List getDeviceEvents(Context context, boolean isPreview) {
List deviceEvents = new ArrayList<>();
if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR)
!= PackageManager.PERMISSION_GRANTED) {
@@ -866,8 +1047,20 @@ public class CalendarManager {
}
long now = System.currentTimeMillis();
- long startMillis = now - (365L * 24 * 60 * 60 * 1000);
- long endMillis = now + (365L * 24 * 60 * 60 * 1000);
+ long startMillis;
+ long endMillis;
+
+ if (isPreview) {
+ // FORSIDEN: Optimalisert for hastighet.
+ // Henter fra NÅ og 30 dager frem i tid.
+ startMillis = now;
+ endMillis = now + (30L * 24 * 60 * 60 * 1000);
+ } else {
+ // FULL VISNING: Bredere tidsvindu.
+ // 1 mnd tilbake til 6 mnd frem.
+ startMillis = now - (30L * 24 * 60 * 60 * 1000);
+ endMillis = now + (180L * 24 * 60 * 60 * 1000);
+ }
String[] projection = new String[]{
CalendarContract.Events.TITLE,
@@ -927,9 +1120,11 @@ public class CalendarManager {
}
CalendarEvent event = new CalendarEvent(title, rawStart, rawEnd, desc, loc);
- // Denne metoden eksisterer nå i CalendarEvent (se fil 1)
event.setReminderMinutes(0);
+ event.setCalendarColor("#888888");
+ event.setCalendarName("Min Kalender (Lokal)");
+
formatEventForUI(event);
deviceEvents.add(event);
}
@@ -1006,9 +1201,24 @@ public class CalendarManager {
}
public static List mergeAndSort(List apiEvents, List deviceEvents) {
- List all = new ArrayList<>(apiEvents);
- all.addAll(deviceEvents);
+ List all = new ArrayList<>();
+ Set uniqueKeys = new HashSet<>();
+ // 1. Legg til alle API-events først (Prioritert)
+ for (CalendarEvent apiEvent : apiEvents) {
+ all.add(apiEvent);
+ uniqueKeys.add(generateKey(apiEvent));
+ }
+
+ // 2. Legg til Device-events KUN hvis nøkkelen ikke finnes
+ for (CalendarEvent deviceEvent : deviceEvents) {
+ String key = generateKey(deviceEvent);
+ if (!uniqueKeys.contains(key)) {
+ all.add(deviceEvent);
+ }
+ }
+
+ // 3. Sorter alt kronologisk
Collections.sort(all, (e1, e2) -> {
String d1 = e1.getRawDate() != null ? e1.getRawDate() : "";
String d2 = e2.getRawDate() != null ? e2.getRawDate() : "";
@@ -1018,6 +1228,22 @@ public class CalendarManager {
return all;
}
+ private static String generateKey(CalendarEvent event) {
+ String title = event.getTitle() != null ? event.getTitle().toLowerCase().trim() : "";
+ String datePart = "";
+
+ if (event.getRawDate() != null) {
+ String digits = event.getRawDate().replaceAll("[^0-9]", "");
+ if (digits.length() >= 12) {
+ datePart = digits.substring(0, 12);
+ } else if (digits.length() >= 8) {
+ datePart = digits.substring(0, 8);
+ } else {
+ datePart = digits;
+ }
+ }
+ return title + "_" + datePart;
+ }
}
============================================================
@@ -1166,6 +1392,8 @@ package com.kbs.kbsintranett;
import android.app.AlertDialog;
import android.app.DatePickerDialog;
import android.app.TimePickerDialog;
+import android.graphics.Color;
+import android.graphics.Typeface;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
@@ -1297,12 +1525,21 @@ public class CreateEventFragment extends Fragment {
private void prefillForm(CalendarEvent event) {
etTitle.setText(event.getTitle());
- // Rens beskrivelsen for #varsel tag
String cleanDesc = event.getDescription().replaceAll("#varsel:[\\d,]+", "").trim();
etDesc.setText(cleanDesc);
etLocation.setText(event.getLocation());
- // Dato-parsing
+ // --- FIKS 404 FEIL VED OPPDATERING ---
+ ArrayAdapter adapter = (ArrayAdapter) spinnerCalendar.getAdapter();
+ if (adapter != null) {
+ int position = adapter.getPosition(event.getCalendarName());
+ if (position >= 0) {
+ spinnerCalendar.setSelection(position);
+ }
+ }
+ spinnerCalendar.setEnabled(false);
+ // -------------------------------------
+
try {
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
@@ -1310,7 +1547,6 @@ public class CreateEventFragment extends Fragment {
String start = event.getRawDate();
if (start != null) {
if (start.length() == 10) {
- // Heldags
switchAllDay.setChecked(true);
Date d = simpleFormat.parse(start);
startCal.setTime(d);
@@ -1325,7 +1561,6 @@ public class CreateEventFragment extends Fragment {
endCal.setTime(d);
}
} else if (start.contains("T")) {
- // Vanlig tid (kutt tidssone)
if (start.length() > 19) start = start.substring(0, 19);
startCal.setTime(isoFormat.parse(start));
@@ -1340,10 +1575,8 @@ public class CreateEventFragment extends Fragment {
}
}
- // Varsler
List existingReminders = event.getReminders();
if (!existingReminders.isEmpty()) {
- // Fjern alle sjekkmerker først (15 min er default checked)
for (int i = 0; i < chipGroupReminders.getChildCount(); i++) {
((Chip) chipGroupReminders.getChildAt(i)).setChecked(false);
}
@@ -1366,14 +1599,67 @@ public class CreateEventFragment extends Fragment {
}
}
- private void setupCalendarSpinner() {
- // HENT DYNAMISK LISTE FRA USERMANAGER
- List calendars = UserManager.getInstance().getWriteableCalendars();
+ // --- NY LOGIKK FOR FARGER I SPINNER ---
+ private String getCalendarColor(String name) {
+ // Matcher fargene i PHP-config (V12.6)
+ switch (name) {
+ case "Felles": return "#0069B3"; // KBS Blå
+ case "Administrasjonen": return "#607D8B"; // Blue Grey
+ case "Serviceavdelingen": return "#E65100"; // Orange
+ case "Automasjonsavdelingen": return "#2E7D32"; // Green
+ case "Prosjektavdelingen": return "#7B1FA2"; // Purple
+ default: return "#888888"; // Grå fallback
+ }
+ }
+
+ private void setupCalendarSpinner() {
+ List calendars = UserManager.getInstance().getWriteableCalendars();
if (calendars.isEmpty()) calendars.add("Felles");
- spinnerCalendar.setAdapter(new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, calendars));
+ // Vi bruker en Custom Adapter for å styre farger
+ ArrayAdapter adapter = new ArrayAdapter(getContext(), android.R.layout.simple_spinner_item, calendars) {
+
+ // getView: Dette er det som vises i selve boksen når noe er valgt
+ @NonNull
+ @Override
+ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ TextView view = (TextView) super.getView(position, convertView, parent);
+
+ String calName = getItem(position);
+ String colorHex = getCalendarColor(calName);
+
+ // Sett bakgrunnsfarge lik kalenderfarge
+ view.setBackgroundColor(Color.parseColor(colorHex));
+
+ // Hvit tekst for kontrast
+ view.setTextColor(Color.WHITE);
+ view.setTypeface(null, Typeface.BOLD);
+
+ return view;
+ }
+
+ // getDropDownView: Dette er listen som popper opp
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ TextView view = (TextView) super.getDropDownView(position, convertView, parent);
+
+ String calName = getItem(position);
+ String colorHex = getCalendarColor(calName);
+
+ // Her holder vi bakgrunnen hvit, men farger teksten
+ view.setBackgroundColor(Color.WHITE);
+ view.setTextColor(Color.parseColor(colorHex));
+ view.setTypeface(null, Typeface.BOLD);
+
+ return view;
+ }
+ };
+
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinnerCalendar.setAdapter(adapter);
}
+ // --------------------------------------
private void setupReminderChips() {
addChip("Ved start", 0);
@@ -1624,7 +1910,6 @@ public class CreateEventFragment extends Fragment {
}
}
- // NY: Henter navnet på kalenderen direkte fra Spinneren
private String getCalendarSlug() {
if (spinnerCalendar.getSelectedItem() != null) {
return spinnerCalendar.getSelectedItem().toString();
@@ -1796,14 +2081,15 @@ import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.fragment.app.Fragment;
+import androidx.navigation.Navigation;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonArray;
-import com.google.gson.JsonParser;
import org.json.JSONArray;
import org.json.JSONException;
@@ -1855,11 +2141,12 @@ public class FormsFragment extends Fragment {
private LinearLayout formContainer;
private LinearLayout historyContainer;
- private View historyWrapper; // Wrapper for historikk-modulen
+ private View historyWrapper;
private TextView txtStatus;
private TextView lblHistory;
private ProgressBar loadingSpinner;
private ImageView btnToggleHistory;
+ private Toolbar toolbar; // NYTT
// --- HOVEDSKJEMA STATE ---
private Map fieldWrappers = new HashMap<>();
@@ -1871,7 +2158,7 @@ public class FormsFragment extends Fragment {
private Map childInputViews = new HashMap<>();
private Map childRequiredFieldsMap = new HashMap<>();
private Map childFileUploads = new HashMap<>();
- // Lagring av Nested Entries
+
private List nestedEntries = new ArrayList<>();
private LinearLayout nestedEntriesContainer;
private TextView totalAmountView;
@@ -1937,12 +2224,17 @@ public class FormsFragment extends Fragment {
lblHistory = view.findViewById(R.id.lbl_history);
loadingSpinner = view.findViewById(R.id.loading_spinner);
+ // NYTT: Finn toolbar og sett listener
+ toolbar = view.findViewById(R.id.forms_toolbar);
+ if (toolbar != null) {
+ toolbar.setNavigationOnClickListener(v -> Navigation.findNavController(view).navigateUp());
+ }
+
btnToggleHistory = view.findViewById(R.id.btn_toggle_history);
if (btnToggleHistory != null) {
btnToggleHistory.setOnClickListener(v -> toggleHistoryVisibility());
}
- // --- FIKS FOR NULLPOINTER EXCEPTION PÅ LAYOUTTRANSITION ---
if (view instanceof ViewGroup) {
LayoutTransition transition = ((ViewGroup) view).getLayoutTransition();
if (transition == null) {
@@ -1951,7 +2243,6 @@ public class FormsFragment extends Fragment {
}
transition.enableTransitionType(LayoutTransition.CHANGING);
}
- // ----------------------------------------------------------
if (formContainer == null) {
formContainer = new LinearLayout(getContext());
@@ -1982,11 +2273,9 @@ public class FormsFragment extends Fragment {
if (historyWrapper == null || btnToggleHistory == null) return;
if (historyWrapper.getVisibility() == View.VISIBLE) {
- // Skjul historikk
historyWrapper.setVisibility(View.GONE);
btnToggleHistory.setImageResource(android.R.drawable.arrow_down_float);
} else {
- // Vis historikk
historyWrapper.setVisibility(View.VISIBLE);
btnToggleHistory.setImageResource(android.R.drawable.arrow_up_float);
}
@@ -2053,20 +2342,17 @@ public class FormsFragment extends Fragment {
nestedEntries.clear();
updateStatus("");
- // Reset visibility of history on new load
+ // NYTT: Sett tittelen i Toolbaren i stedet for å legge til en TextView
+ if (toolbar != null) {
+ toolbar.setTitle(getCleanTitle(form.title));
+ }
+
if (historyWrapper != null) {
historyWrapper.setVisibility(View.VISIBLE);
if (btnToggleHistory != null) btnToggleHistory.setImageResource(android.R.drawable.arrow_up_float);
}
- TextView title = new TextView(getContext());
- title.setText(getCleanTitle(form.title));
- title.setTextSize(24);
- title.setTypeface(null, Typeface.BOLD);
- title.setTextColor(Color.BLACK);
- title.setPadding(0, 0, 0, 20);
- formContainer.addView(title);
-
+ // Beskrivelse legges fortsatt inn som innhold
if (form.description != null && !form.description.isEmpty()) {
TextView formDesc = new TextView(getContext());
String cleanDesc = form.description.replaceFirst("^\\d+\\.\\s*", "");
@@ -2182,14 +2468,13 @@ public class FormsFragment extends Fragment {
btnAdd.setBackgroundColor(Color.parseColor("#53AFE9"));
btnAdd.setTextColor(Color.WHITE);
btnAdd.setOnClickListener(v -> {
- expandFormModule(); // Trigger expand
+ expandFormModule();
int childFormId = 18;
if (field.gpnfForm != null) {
try {
childFormId = Integer.parseInt(field.gpnfForm);
} catch (NumberFormatException e) { e.printStackTrace(); }
}
- // NYTT: Sender med felt-ID fra hovedskjemaet (f.eks "25")
openChildFormDialog(childFormId, field.id);
});
container.addView(btnAdd);
@@ -2208,7 +2493,6 @@ public class FormsFragment extends Fragment {
container.addView(totalAmountView);
}
- // NY SIGNATUR: Tar imot parentFieldId
private void openChildFormDialog(int childFormId, String parentFieldId) {
if (getActivity() == null) return;
ProgressBar pBar = new ProgressBar(getContext());
@@ -2236,7 +2520,6 @@ public class FormsFragment extends Fragment {
});
}
- // NY SIGNATUR: Tar imot parentFieldId
private void showChildFormDialog(GravityForm childForm, String parentFieldId) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
childInputViews.clear();
@@ -2286,7 +2569,6 @@ public class FormsFragment extends Fragment {
});
}
- // NY SIGNATUR: Tar imot parentFieldId og sender den som meta
private void submitChildForm(int childFormId, AlertDialog dialog, String parentFieldId) {
JSONObject inputValues = new JSONObject();
for (Map.Entry entry : childInputViews.entrySet()) {
@@ -2312,13 +2594,9 @@ public class FormsFragment extends Fragment {
}
}
- // --- HER ER FIKSEN FOR BUGGEN ---
- // Vi legger ved en ekstra parameter som forteller Gravity Forms at dette
- // er en nested entry som hører til et bestemt felt (f.eks "25").
if (parentFieldId != null) {
textParts.put("gpnf_entry_nested_form_field", RequestBody.create(MultipartBody.FORM, parentFieldId));
}
- // ---------------------------------
for (Map.Entry fileEntry : childFileUploads.entrySet()) {
String fieldId = fileEntry.getKey();
@@ -2338,9 +2616,6 @@ public class FormsFragment extends Fragment {
JsonObject json = response.body().getAsJsonObject();
if (json.has("is_valid") && json.get("is_valid").getAsBoolean()) {
String entryId = json.has("entry_id") ? json.get("entry_id").getAsString() : "";
-
- // NB: Tilpass ID-ene her hvis skjema 18 endres.
- // ID 3 = Beskrivelse, ID 4 = Beløp
String desc = getInputValueGeneric(childInputViews.get("3"));
String price = getInputValueGeneric(childInputViews.get("4"));
addNestedEntry(entryId, desc, price);
@@ -2414,10 +2689,9 @@ public class FormsFragment extends Fragment {
Button btnUpload = new Button(getContext());
btnUpload.setText("Velg fil / Ta bilde");
btnUpload.setOnClickListener(v -> {
- // Setter state før vi viser dialog
pendingFileFieldId = field.id;
isSelectingForChild = isChild;
- expandFormModule(); // Trigger expand
+ expandFormModule();
showFileSourceDialog();
});
TextView txtFileName = new TextView(getContext());
@@ -2434,23 +2708,19 @@ public class FormsFragment extends Fragment {
reqMap.put(field.id, field.isRequired);
}
- // Hjelpemetode for å vise dialog
private void showFileSourceDialog() {
String[] options = {"Ta bilde", "Velg fil"};
new AlertDialog.Builder(getContext())
.setTitle("Last opp vedlegg")
.setItems(options, (dialog, which) -> {
if (which == 0) {
- // Ta bilde - SJEKKER PERMISSION FØRST
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED) {
openCamera();
} else {
- // Spør om lov
requestPermissionLauncher.launch(Manifest.permission.CAMERA);
}
} else {
- // Velg fil
if (filePickerLauncher != null) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
@@ -2543,7 +2813,7 @@ public class FormsFragment extends Fragment {
timeInput.setClickable(true);
timeInput.setHint("00:00");
timeInput.setOnClickListener(v -> {
- expandFormModule(); // Trigger expand
+ expandFormModule();
Calendar mcurrentTime = Calendar.getInstance();
int hour = mcurrentTime.get(Calendar.HOUR_OF_DAY);
int minute = mcurrentTime.get(Calendar.MINUTE);
@@ -2584,7 +2854,7 @@ public class FormsFragment extends Fragment {
public void onTextChanged(CharSequence s, int start, int before, int count) {}
public void afterTextChanged(Editable s) { evaluateAllConditionalLogic(); }
});
- attachInteractionListener(input); // Add listener
+ attachInteractionListener(input);
container.addView(input);
views.put(field.id, input);
@@ -2598,7 +2868,7 @@ public class FormsFragment extends Fragment {
input.setMinLines(3);
input.setGravity(android.view.Gravity.TOP | android.view.Gravity.START);
- attachInteractionListener(input); // Add listener
+ attachInteractionListener(input);
container.addView(input);
views.put(field.id, input);
@@ -2612,7 +2882,6 @@ public class FormsFragment extends Fragment {
RadioButton rb = new RadioButton(getContext());
rb.setText(choice.text);
rb.setTag(choice.value);
- // Also trigger expand on RadioButton click
rb.setOnClickListener(v -> {
expandFormModule();
evaluateAllConditionalLogic();
@@ -2620,7 +2889,6 @@ public class FormsFragment extends Fragment {
group.addView(rb);
}
}
- // Fallback listener
group.setOnCheckedChangeListener((g, i) -> {
expandFormModule();
evaluateAllConditionalLogic();
@@ -2632,7 +2900,6 @@ public class FormsFragment extends Fragment {
private void renderSelectField(LinearLayout container, GravityField field, Map views, Map req) {
Spinner spinner = new Spinner(getContext());
- // Spinner touch listener is tricky, usually set onTouchListener works
spinner.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
expandFormModule();
@@ -2675,7 +2942,6 @@ public class FormsFragment extends Fragment {
checkBox.setText(inputDef.label);
String value = "1";
- // Fallback
if (field.choices != null && i < field.choices.size()) {
value = field.choices.get(i).value;
}
@@ -2693,24 +2959,20 @@ public class FormsFragment extends Fragment {
private void renderDateField(LinearLayout container, GravityField field, Map views, Map req) {
EditText dateInput = new EditText(getContext());
- // --- DATO LØSNING (Fix for Read Only / Dagens Dato) ---
if (field.readOnly || (formId == ID_REFUSJON_UTLEGG && "28".equals(field.id))) {
- // Sett dagens dato
SimpleDateFormat df = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault());
dateInput.setText(df.format(new Date()));
- // Gjør den "read-only" men synlig
dateInput.setFocusable(false);
dateInput.setClickable(false);
dateInput.setEnabled(false);
dateInput.setTextColor(Color.BLACK);
} else {
- // Vanlig dato-velger
dateInput.setFocusable(false);
dateInput.setClickable(true);
dateInput.setHint("dd.mm.yyyy");
dateInput.setOnClickListener(v -> {
- expandFormModule(); // Trigger expand
+ expandFormModule();
Calendar c = Calendar.getInstance();
new DatePickerDialog(getContext(), (view, year, month, dayOfMonth) -> {
dateInput.setText(String.format("%02d.%02d.%d", dayOfMonth, month + 1, year));
@@ -3114,20 +3376,17 @@ public class FormsFragment extends Fragment {
}
try {
- // Vis flere oppføringer siden vi nå har scrolle-mulighet øverst
int count = Math.min(entries.length(), 20);
for (int i = 0; i < count; i++) {
JSONObject entry = entries.getJSONObject(i);
String date = entry.optString("date_created");
- // Prøv å finne en bedre tittel enn bare dato (f.eks Prosjektnavn eller Sted)
String titleText = "Innsendt: " + date;
TextView item = new TextView(getContext());
item.setText(titleText);
item.setPadding(10, 20, 10, 20);
item.setBackgroundResource(android.R.drawable.list_selector_background);
item.setTextSize(14);
- // Add click listener to show details
item.setOnClickListener(v -> showEntryDetails(entry));
View line = new View(getContext());
line.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1));
@@ -3140,20 +3399,16 @@ public class FormsFragment extends Fragment {
}
}
- // NY METODE: Vis detaljer i dialog med delingsknapp
private void showEntryDetails(JSONObject entry) {
- // HVIS dette er Form 16 (Refusjon), må vi først hente vedleggene (som ligger i Form 18)
if (formId == ID_REFUSJON_UTLEGG) {
Log.d(TAG, "Form 16 detected. Checking for child entries...");
- String nestedIds = entry.optString("25"); // Felt 25 i Form 16 inneholder ID-ene til vedleggene
+ String nestedIds = entry.optString("25");
if (!nestedIds.isEmpty()) {
Log.d(TAG, "Nested IDs found: " + nestedIds);
List ids = new ArrayList<>();
- // ROBUST PARSING AV ID-LISTE
if (nestedIds.startsWith("[") && nestedIds.endsWith("]")) {
- // Dette er en JSON-array streng (f.eks [101, 102])
try {
JSONArray jsonArray = new JSONArray(nestedIds);
for(int i=0; i").append(field.label).append(":
")
@@ -3244,27 +3488,20 @@ public class FormsFragment extends Fragment {
} catch (Exception e) {}
}
- // Rekursiv metode for å hente barn-oppføringer (Vedlegg)
private void fetchChildEntriesRecursive(List ids, int index, StringBuilder html, StringBuilder text, AlertDialog loader) {
if (index >= ids.size()) {
- // Alle ferdige! Vis dialogen.
loader.dismiss();
showFinalDialog(html, text);
return;
}
String entryId = ids.get(index);
- Log.d(TAG, "Fetching child entry ID: " + entryId);
-
RetrofitClient.getApiService().getSingleEntry(entryId).enqueue(new retrofit2.Callback() {
@Override
public void onResponse(retrofit2.Call call, retrofit2.Response response) {
if (response.isSuccessful() && response.body() != null) {
try {
JsonObject json = response.body().getAsJsonObject();
- // I Form 18: Felt 1 = Fil, Felt 3 = Beskrivelse, Felt 4 = Beløp
-
- // Parse Description and Price
String desc = json.has("3") ? json.get("3").getAsString() : "Uten beskrivelse";
String price = json.has("4") ? json.get("4").getAsString() : "";
@@ -3274,11 +3511,9 @@ public class FormsFragment extends Fragment {
html.append(desc).append(" (").append(price).append(")
");
text.append(desc).append(" (").append(price).append(")\n");
- // Parse File Field (ID 1) - Can be multiple files!
if (json.has("1")) {
JsonElement fileEl = json.get("1");
if (fileEl.isJsonArray()) {
- // It is a real JSON array
JsonArray arr = fileEl.getAsJsonArray();
for (int i = 0; i < arr.size(); i++) {
String url = arr.get(i).getAsString().replace("\\/", "/");
@@ -3286,7 +3521,6 @@ public class FormsFragment extends Fragment {
text.append(url).append("\n");
}
} else {
- // It is a string. Check if it's a JSON string array like "[\"http...\"]"
String rawString = fileEl.getAsString();
if (rawString.startsWith("[") && rawString.endsWith("]")) {
try {
@@ -3297,13 +3531,11 @@ public class FormsFragment extends Fragment {
text.append(url).append("\n");
}
} catch (JSONException ex) {
- // Fallback: simple cleanup
String clean = extractUrl(rawString);
html.append("Åpne fil
");
text.append(clean).append("\n");
}
} else {
- // Just a plain URL string
String clean = extractUrl(rawString);
if(clean.startsWith("http")) {
html.append("Åpne fil
");
@@ -3318,17 +3550,12 @@ public class FormsFragment extends Fragment {
} catch (Exception e) {
Log.e(TAG, "Error parsing child entry", e);
}
- } else {
- Log.e(TAG, "Failed to fetch child entry: " + response.code());
}
- // Gå til neste (uansett om denne feilet eller ei)
fetchChildEntriesRecursive(ids, index + 1, html, text, loader);
}
@Override
public void onFailure(retrofit2.Call call, Throwable t) {
- Log.e(TAG, "Network error fetching child entry", t);
- // Hopp over ved feil
fetchChildEntriesRecursive(ids, index + 1, html, text, loader);
}
});
@@ -3351,7 +3578,6 @@ public class FormsFragment extends Fragment {
.show();
}
- // Hjelpemetode for å rydde opp i URLer fra JSON (f.eks ["http://..."] -> http://...)
private String extractUrl(String rawValue) {
if (rawValue == null) return "";
String clean = rawValue.replace("[", "")
@@ -4517,11 +4743,15 @@ package com.kbs.kbsintranett;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
+import android.widget.ProgressBar;
import android.widget.TextView;
+import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
@@ -4531,6 +4761,7 @@ import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import java.text.SimpleDateFormat;
@@ -4547,6 +4778,11 @@ import retrofit2.Response;
public class HomeFragment extends Fragment {
private ActivityResultLauncher requestPermissionLauncher;
private RecyclerView calendarRecycler;
+ private RecyclerView newsRecycler;
+ private ProgressBar mainProgressBar;
+ private SwipeRefreshLayout swipeRefreshLayout;
+
+ private int activeNetworkCalls = 0;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -4555,7 +4791,7 @@ public class HomeFragment extends Fragment {
new ActivityResultContracts.RequestPermission(),
isGranted -> {
if (calendarRecycler != null) {
- fetchCalendarEvents(calendarRecycler);
+ refreshData();
}
}
);
@@ -4572,15 +4808,19 @@ public class HomeFragment extends Fragment {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
+ mainProgressBar = view.findViewById(R.id.main_loading_spinner);
+ if (mainProgressBar != null) mainProgressBar.setVisibility(View.VISIBLE);
+
+ swipeRefreshLayout = view.findViewById(R.id.swipe_refresh_home);
+ swipeRefreshLayout.setOnRefreshListener(this::refreshData);
+
View profileBtn = view.findViewById(R.id.btn_profile);
if (profileBtn != null) {
profileBtn.setOnClickListener(v -> Navigation.findNavController(view).navigate(R.id.navigation_profile));
}
- // NY LOGIKK: Vis knapp hvis brukeren har tilgang til minst én kalender
Button btnCreateEvent = view.findViewById(R.id.btn_create_event);
List writeable = UserManager.getInstance().getWriteableCalendars();
-
if (writeable != null && !writeable.isEmpty()) {
btnCreateEvent.setVisibility(View.VISIBLE);
btnCreateEvent.setOnClickListener(v -> {
@@ -4599,19 +4839,13 @@ public class HomeFragment extends Fragment {
viewAllCalendar.setOnClickListener(v -> Navigation.findNavController(view).navigate(R.id.action_home_to_calendarFull));
}
- if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED) {
- fetchCalendarEvents(calendarRecycler);
- } else {
- requestPermissionLauncher.launch(Manifest.permission.READ_CALENDAR);
- }
-
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);
}
}
- RecyclerView newsRecycler = view.findViewById(R.id.recycler_news);
+ newsRecycler = view.findViewById(R.id.recycler_news);
newsRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
newsRecycler.setNestedScrollingEnabled(false);
newsRecycler.setAdapter(new NewsAdapter(new ArrayList<>(), item -> {}));
@@ -4623,12 +4857,47 @@ public class HomeFragment extends Fragment {
});
}
+ refreshData();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (activeNetworkCalls == 0) {
+ refreshData();
+ }
+ }
+
+ private void refreshData() {
+ activeNetworkCalls = 2;
+
+ if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED) {
+ fetchCalendarEvents(calendarRecycler);
+ } else {
+ checkLoadingComplete();
+ requestPermissionLauncher.launch(Manifest.permission.READ_CALENDAR);
+ }
+
fetchNewsFromWordpress(newsRecycler);
}
- private void fetchCalendarEvents(RecyclerView recyclerView) {
- List deviceEvents = CalendarManager.getDeviceEvents(getContext());
+ private void checkLoadingComplete() {
+ activeNetworkCalls--;
+ if (activeNetworkCalls <= 0) {
+ activeNetworkCalls = 0;
+ if (mainProgressBar != null) mainProgressBar.setVisibility(View.GONE);
+ if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(false);
+ }
+ }
+ private void fetchCalendarEvents(RecyclerView recyclerView) {
+ new Thread(() -> {
+ List deviceEvents = CalendarManager.getDeviceEvents(getContext(), true);
+ new Handler(Looper.getMainLooper()).post(() -> fetchApiEvents(recyclerView, deviceEvents));
+ }).start();
+ }
+
+ private void fetchApiEvents(RecyclerView recyclerView, List deviceEvents) {
RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback>() {
@Override
public void onResponse(Call> call, Response> response) {
@@ -4637,51 +4906,64 @@ public class HomeFragment extends Fragment {
List apiEvents = new ArrayList<>();
if (response.isSuccessful() && response.body() != null) {
apiEvents = response.body();
+
+ CacheManager.saveCalendarEvents(getContext(), apiEvents);
+
for (CalendarEvent e : apiEvents) {
CalendarManager.formatEventForUI(e);
}
- }
-
- List merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents);
- List upcomingEvents = new ArrayList<>();
- String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
-
- for (CalendarEvent e : merged) {
- if (e.getRawDate() != null && e.getRawDate().compareTo(today) >= 0) {
- upcomingEvents.add(e);
+ } else {
+ apiEvents = CacheManager.getCachedCalendarEvents(getContext());
+ for (CalendarEvent e : apiEvents) CalendarManager.formatEventForUI(e);
+ if (!apiEvents.isEmpty()) {
+ Toast.makeText(getContext(), "Server utilgjengelig. Viser lagret kalender.", Toast.LENGTH_SHORT).show();
}
}
- List top5 = new ArrayList<>();
- for(int i=0; i {
- CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
- sheet.show(getParentFragmentManager(), "CalendarDetails");
- }));
+ updateCalendarUI(recyclerView, apiEvents, deviceEvents);
+ checkLoadingComplete();
}
@Override
public void onFailure(Call> call, Throwable t) {
if (!isAdded()) return;
- if (!deviceEvents.isEmpty()) {
- List top5 = new ArrayList<>();
- for(int i=0; i {
- CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
- sheet.show(getParentFragmentManager(), "CalendarDetails");
- }));
- } else {
- List errorList = new ArrayList<>();
- errorList.add(new CalendarEvent("Kunne ikke laste kalender", "Sjekk nettverk", "!", "OBS", ""));
- recyclerView.setAdapter(new CalendarAdapter(errorList, null));
+
+ List cachedApiEvents = CacheManager.getCachedCalendarEvents(getContext());
+ for (CalendarEvent e : cachedApiEvents) CalendarManager.formatEventForUI(e);
+
+ if (!cachedApiEvents.isEmpty()) {
+ Toast.makeText(getContext(), "Ingen nettverk. Viser lagret kalender.", Toast.LENGTH_SHORT).show();
}
+
+ updateCalendarUI(recyclerView, cachedApiEvents, deviceEvents);
+ checkLoadingComplete();
}
});
}
+ private void updateCalendarUI(RecyclerView recyclerView, List apiEvents, List deviceEvents) {
+ List merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents);
+ List upcomingEvents = new ArrayList<>();
+ String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
+
+ for (CalendarEvent e : merged) {
+ if (e.getRawDate() != null && e.getRawDate().compareTo(today) >= 0) {
+ upcomingEvents.add(e);
+ }
+ }
+
+ List topEvents = new ArrayList<>();
+ for(int i=0; i {
+ CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
+ sheet.setOnEventChangeListener(HomeFragment.this::refreshData);
+ sheet.show(getParentFragmentManager(), "CalendarDetails");
+ }));
+ }
+
private void fetchNewsFromWordpress(RecyclerView recyclerView) {
WordPressApiService apiService = RetrofitClient.getApiService();
apiService.getPosts().enqueue(new Callback>() {
@@ -4689,37 +4971,58 @@ public class HomeFragment extends Fragment {
public void onResponse(Call> call, Response> response) {
if (getContext() == null) return;
+ List postsToShow = new ArrayList<>();
+
if (response.isSuccessful() && response.body() != null) {
- List wpPosts = response.body();
- 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());
- targetFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
-
- for (WpPost post : wpPosts) {
- try {
- Date date = rawFormat.parse(post.date);
- post.date = targetFormat.format(date);
- } catch (Exception e) {}
+ postsToShow = response.body();
+ CacheManager.saveNewsPosts(getContext(), postsToShow);
+ } else {
+ postsToShow = CacheManager.getCachedNewsPosts(getContext());
+ if (!postsToShow.isEmpty()) {
+ Toast.makeText(getContext(), "Server utilgjengelig. Viser lagrede nyheter.", Toast.LENGTH_SHORT).show();
}
-
- NewsAdapter adapter = new NewsAdapter(wpPosts, post -> {
- Bundle bundle = new Bundle();
- bundle.putSerializable("post_data", post);
- Navigation.findNavController(getView()).navigate(R.id.action_home_to_newsDetail, bundle);
- });
- recyclerView.setAdapter(adapter);
}
+
+ updateNewsUI(recyclerView, postsToShow);
+ checkLoadingComplete();
}
@Override
public void onFailure(Call> call, Throwable t) {
if (getContext() == null) return;
- recyclerView.setAdapter(new NewsAdapter(new ArrayList<>(), null));
+
+ List cachedPosts = CacheManager.getCachedNewsPosts(getContext());
+ if (!cachedPosts.isEmpty()) {
+ Toast.makeText(getContext(), "Ingen nettverk. Viser lagrede nyheter.", Toast.LENGTH_SHORT).show();
+ }
+
+ updateNewsUI(recyclerView, cachedPosts);
+ checkLoadingComplete();
}
});
}
+ private void updateNewsUI(RecyclerView recyclerView, List posts) {
+ 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());
+ targetFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
+
+ for (WpPost post : posts) {
+ try {
+ Date date = rawFormat.parse(post.date);
+ post.date = targetFormat.format(date);
+ } catch (Exception e) {}
+ }
+
+ NewsAdapter adapter = new NewsAdapter(posts, post -> {
+ Bundle bundle = new Bundle();
+ bundle.putSerializable("post_data", post);
+ Navigation.findNavController(getView()).navigate(R.id.action_home_to_newsDetail, bundle);
+ });
+ recyclerView.setAdapter(adapter);
+ }
+
private void startNotificationWorker() {
PeriodicWorkRequest notifRequest =
new PeriodicWorkRequest.Builder(NotificationWorker.class, 15, TimeUnit.MINUTES)
@@ -4728,6 +5031,100 @@ public class HomeFragment extends Fragment {
}
}
+============================================================
+FILSTI: app\src\main\java\com\kbs\kbsintranett\ImageDialogFragment.java
+============================================================
+package com.kbs.kbsintranett;
+
+import android.app.Dialog;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
+
+public class ImageDialogFragment extends DialogFragment {
+
+ private static final String ARG_URL = "image_url";
+
+ public static ImageDialogFragment newInstance(String imageUrl) {
+ ImageDialogFragment fragment = new ImageDialogFragment();
+ Bundle args = new Bundle();
+ args.putString(ARG_URL, imageUrl);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ // Gjør dialogen fullskjerm
+ Dialog dialog = getDialog();
+ if (dialog != null) {
+ int width = ViewGroup.LayoutParams.MATCH_PARENT;
+ int height = ViewGroup.LayoutParams.MATCH_PARENT;
+ dialog.getWindow().setLayout(width, height);
+ dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK));
+ }
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_image_dialog, container, false);
+
+ ImageView imageView = view.findViewById(R.id.full_screen_image);
+ ImageButton closeBtn = view.findViewById(R.id.btn_close_image);
+ ProgressBar progressBar = view.findViewById(R.id.loading_image);
+
+ String url = getArguments() != null ? getArguments().getString(ARG_URL) : null;
+
+ if (url != null) {
+ Glide.with(this)
+ .load(url)
+ .transition(DrawableTransitionOptions.withCrossFade())
+ .listener(new com.bumptech.glide.request.RequestListener() {
+ @Override
+ public boolean onLoadFailed(@Nullable com.bumptech.glide.load.engine.GlideException e, Object model, com.bumptech.glide.request.target.Target target, boolean isFirstResource) {
+ progressBar.setVisibility(View.GONE);
+ return false;
+ }
+
+ @Override
+ public boolean onResourceReady(android.graphics.drawable.Drawable resource, Object model, com.bumptech.glide.request.target.Target target, com.bumptech.glide.load.DataSource dataSource, boolean isFirstResource) {
+ progressBar.setVisibility(View.GONE);
+ return false;
+ }
+ })
+ .into(imageView);
+ }
+
+ closeBtn.setOnClickListener(v -> dismiss());
+ // Lukk også hvis man trykker på selve bildet
+ imageView.setOnClickListener(v -> dismiss());
+
+ return view;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+ Dialog dialog = super.onCreateDialog(savedInstanceState);
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ return dialog;
+ }
+}
+
============================================================
FILSTI: app\src\main\java\com\kbs\kbsintranett\InputsAdapter.java
============================================================
@@ -5155,14 +5552,12 @@ import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity {
- // VIKTIG: Sørg for at denne matcher den du har i Google Cloud Console
- public static final String GOOGLE_WEB_CLIENT_ID = "SECRET.apps.googleusercontent.com";
+ public static final String GOOGLE_WEB_CLIENT_ID = "738325360287-cidl3plnqv9ei74vm9vm5muustj6eenb.apps.googleusercontent.com"; // Bytt med din egen hvis denne er feil
private static final String TAG = "MainActivity";
private NavController navController;
private BottomNavigationView bottomNav;
- // Launcher for å spørre om varslingstillatelse (Android 13+)
private ActivityResultLauncher requestPermissionLauncher;
@Override
@@ -5171,7 +5566,6 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
// --- 1. SETUP UI & NAVIGASJON ---
- // Sjekket activity_main.xml: ID er "bottom_nav_view"
bottomNav = findViewById(R.id.bottom_nav_view);
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
@@ -5181,6 +5575,13 @@ public class MainActivity extends AppCompatActivity {
navController = navHostFragment.getNavController();
if (bottomNav != null) {
NavigationUI.setupWithNavController(bottomNav, navController);
+
+ // --- NYTT: Håndter "Reselection" (Klikk på fanen man allerede er i) ---
+ bottomNav.setOnItemReselectedListener(item -> {
+ // Dette fjerner alt som ligger "oppå" hovedsiden i stabelen.
+ // F.eks: Hjem -> Kalender Full -> (Klikk Hjem) -> Hjem
+ navController.popBackStack(item.getItemId(), false);
+ });
}
// Skjul meny på login-skjerm
@@ -5195,32 +5596,26 @@ public class MainActivity extends AppCompatActivity {
});
}
- // --- 2. VARSLINGSOPPSETT (NYTT) ---
+ // --- 2. VARSLINGSOPPSETT ---
createNotificationChannel();
- // Initialiser permission launcher for varsler
requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
if (isGranted) {
Log.d(TAG, "Varslingstillatelse gitt!");
} else {
- Log.e(TAG, "Varslingstillatelse avslått. Bruker får ikke kalendervarsler.");
+ Log.e(TAG, "Varslingstillatelse avslått.");
}
});
- // Sjekk tillatelser (både Varsler og Alarmer)
checkNotificationPermission();
checkExactAlarmPermission();
- // Start bakgrunnsjobben for kalenderen
scheduleCalendarWork();
- // --- 3. AUTENTISERING (GAMMELT) ---
+ // --- 3. AUTENTISERING ---
checkLoginState();
}
- /**
- * Sjekker om brukeren er logget inn med Google, og gjør en silent refresh mot WP.
- */
private void checkLoginState() {
GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
if (account == null) {
@@ -5279,8 +5674,6 @@ public class MainActivity extends AppCompatActivity {
}
}
- // --- NYE HJELPEMETODER FOR VARSLING ---
-
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "KBS Kalendervarsler";
@@ -5303,10 +5696,6 @@ public class MainActivity extends AppCompatActivity {
}
}
- /**
- * 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);
@@ -5325,9 +5714,6 @@ public class MainActivity extends AppCompatActivity {
}
}
- /**
- * Starter WorkManager som sjekker kalenderen hvert 15. minutt.
- */
private void scheduleCalendarWork() {
PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(NotificationWorker.class, 15, TimeUnit.MINUTES)
.build();
@@ -5452,12 +5838,15 @@ FILSTI: app\src\main\java\com\kbs\kbsintranett\NewsDetailFragment.java
============================================================
package com.kbs.kbsintranett;
+import android.content.Context;
import android.os.Bundle;
-import android.text.Html;
-import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -5469,6 +5858,41 @@ import com.bumptech.glide.Glide;
public class NewsDetailFragment extends Fragment {
+ // CSS Styling (Samme stil som håndboken, pluss bildehåndtering)
+ private static final String CSS_STYLE =
+ "";
+
+ // JavaScript for å fange opp bildeklikk
+ private static final String JS_SCRIPT =
+ "";
+
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@@ -5479,7 +5903,6 @@ public class NewsDetailFragment extends Fragment {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- // Hent data fra argumentene (sendt fra HomeFragment/NewsFullFragment)
if (getArguments() != null) {
WpPost post = (WpPost) getArguments().getSerializable("post_data");
if (post != null) {
@@ -5496,9 +5919,10 @@ public class NewsDetailFragment extends Fragment {
TextView title = view.findViewById(R.id.detail_title);
TextView category = view.findViewById(R.id.detail_category);
TextView date = view.findViewById(R.id.detail_date);
- TextView author = view.findViewById(R.id.detail_author); // NY
- TextView content = view.findViewById(R.id.detail_content);
+ TextView author = view.findViewById(R.id.detail_author);
+ WebView webView = view.findViewById(R.id.detail_webview);
+ // Header bilde
String imgUrl = post.getFeaturedImageUrl();
if (imgUrl != null) {
Glide.with(this).load(imgUrl).centerCrop().into(image);
@@ -5509,12 +5933,60 @@ public class NewsDetailFragment extends Fragment {
title.setText(post.getTitleStr());
category.setText(post.getCategoryName());
date.setText("Publisert: " + post.date);
-
- // NYTT: Sett forfatter
author.setText("Av: " + post.getAuthorName());
- content.setText(Html.fromHtml(post.getContentStr(), Html.FROM_HTML_MODE_COMPACT));
- content.setMovementMethod(LinkMovementMethod.getInstance());
+ // Konfigurer WebView
+ WebSettings settings = webView.getSettings();
+ settings.setJavaScriptEnabled(true);
+ settings.setDomStorageEnabled(true);
+
+ // Legg til Interface for å snakke med Java
+ webView.addJavascriptInterface(new WebAppInterface(getContext()), "Android");
+
+ // Håndter linker internt (som i Håndboken)
+ webView.setWebViewClient(new WebViewClient() {
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ // Bruk samme link-logikk som i Håndboken hvis nødvendig,
+ // men her lar vi linker åpnes i nettleser for enkelhets skyld foreløpig
+ return false;
+ }
+ });
+
+ // Bygg HTML
+ String rawContent = post.getContentStr();
+
+ // Vask innholdet litt hvis nødvendig (f.eks fjerne inline styles som ødelegger)
+ // Her legger vi bare til vår CSS og JS
+ String htmlData = "" +
+ "" +
+ CSS_STYLE +
+ JS_SCRIPT +
+ "" +
+ rawContent +
+ "";
+
+ webView.loadDataWithBaseURL("https://intranet.kbs.no", htmlData, "text/html", "UTF-8", null);
+ }
+
+ // Bridge-klasse for å ta imot klikk fra JavaScript
+ public class WebAppInterface {
+ Context mContext;
+
+ WebAppInterface(Context c) {
+ mContext = c;
+ }
+
+ @JavascriptInterface
+ public void showImage(String url) {
+ // Må kjøres på UI-tråden
+ if (getActivity() != null) {
+ getActivity().runOnUiThread(() -> {
+ ImageDialogFragment dialog = ImageDialogFragment.newInstance(url);
+ dialog.show(getParentFragmentManager(), "image_lightbox");
+ });
+ }
+ }
}
}
@@ -5841,22 +6313,24 @@ public class ProfileFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_profile, container, false);
- // 1. Finn Views
ImageView closeBtn = view.findViewById(R.id.btn_close_profile);
ImageView profileImage = view.findViewById(R.id.profile_image);
TextView nameText = view.findViewById(R.id.profile_name);
TextView emailText = view.findViewById(R.id.profile_email);
TextView roleText = view.findViewById(R.id.profile_role);
Button logoutBtn = view.findViewById(R.id.btn_logout);
- Button updateInfoBtn = view.findViewById(R.id.btn_update_info); // NY
+ Button updateInfoBtn = view.findViewById(R.id.btn_update_info);
+ TextView versionText = view.findViewById(R.id.tv_version_info); // NYTT
- // 2. Hent data fra UserManager
UserManager user = UserManager.getInstance();
nameText.setText(user.getUserDisplayName());
emailText.setText(user.getUserEmail());
roleText.setText("Rolle: " + user.getUserRole());
- // 3. Last bilde med Glide
+ // NYTT: Sett versjonstekst
+ String versionInfo = "Versjon " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")";
+ versionText.setText(versionInfo);
+
if (user.getPhotoUrl() != null) {
Glide.with(this)
.load(user.getPhotoUrl())
@@ -5864,44 +6338,33 @@ public class ProfileFragment extends Fragment {
.into(profileImage);
}
- // 4. Håndter "Lukk" (X) knapp - Gå tilbake til forrige skjerm
closeBtn.setOnClickListener(v -> {
Navigation.findNavController(view).navigateUp();
});
- // 5. Håndter "Oppdater opplysninger" (Skjema ID 1)
updateInfoBtn.setOnClickListener(v -> {
Bundle bundle = new Bundle();
- bundle.putInt("formId", 1); // ID 1 er Ansatteopplysninger
+ bundle.putInt("formId", 1);
Navigation.findNavController(view).navigate(R.id.action_profile_to_form, bundle);
});
- // 6. Håndter utlogging
logoutBtn.setOnClickListener(v -> performLogout());
return view;
}
private void performLogout() {
- // A. Konfigurer Google Client
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(MainActivity.GOOGLE_WEB_CLIENT_ID)
.requestEmail()
.build();
GoogleSignInClient client = GoogleSignIn.getClient(requireActivity(), gso);
- // B. Logg ut fra Google
client.signOut().addOnCompleteListener(task -> {
-
- // C. Tøm interne data
UserManager.getInstance().logout();
RetrofitClient.clearClient();
-
- // D. Naviger tilbake til Login-skjermen
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
- // Denne aksjonen finnes i mobile_navigation.xml
navController.navigate(R.id.action_profile_to_login);
-
Toast.makeText(getContext(), "Du er nå logget ut", Toast.LENGTH_SHORT).show();
});
}
@@ -6363,6 +6826,15 @@ FILSTI: app\src\main\res\drawable\bg_category_unselected.xml
+============================================================
+FILSTI: app\src\main\res\drawable\bg_date_box.xml
+============================================================
+
+
+
+
+
+
============================================================
FILSTI: app\src\main\res\drawable\ic_book.xml
============================================================
@@ -6667,6 +7139,21 @@ FILSTI: app\src\main\res\layout\bottom_sheet_calendar_details.xml
android:orientation="vertical"
android:padding="24dp"
android:background="@android:color/white">
+
+
+
+
-