diff --git a/app/src/main/java/com/kbs/kbsintranett/CalendarDetailsBottomSheet.java b/app/src/main/java/com/kbs/kbsintranett/CalendarDetailsBottomSheet.java
index b5b01c5..768775d 100644
--- a/app/src/main/java/com/kbs/kbsintranett/CalendarDetailsBottomSheet.java
+++ b/app/src/main/java/com/kbs/kbsintranett/CalendarDetailsBottomSheet.java
@@ -1,11 +1,14 @@
package com.kbs.kbsintranett;
import android.app.AlertDialog;
+import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Color;
+import android.net.Uri;
import android.os.Bundle;
import android.text.Html;
import android.text.method.LinkMovementMethod;
+import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -64,18 +67,50 @@ public class CalendarDetailsBottomSheet extends BottomSheetDialogFragment {
calName.setBackgroundTintList(ColorStateList.valueOf(color));
} catch (Exception e) {}
+ // --- BESKRIVELSE OG LENKER ---
if (!event.getDescription().isEmpty()) {
String cleanDesc = event.getDescription().replaceAll("#varsel:[\\d,]+", "").trim();
+
+ // Konverter HTML til Spannable
desc.setText(Html.fromHtml(cleanDesc, Html.FROM_HTML_MODE_COMPACT));
- desc.setVisibility(View.VISIBLE);
+
+ // Sørg for at rene tekst-lenker (uten ) også blir klikkbare
+ Linkify.addLinks(desc, Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS);
+
+ // Gjør at man kan klikke på lenkene
desc.setMovementMethod(LinkMovementMethod.getInstance());
+
+ desc.setVisibility(View.VISIBLE);
} else {
desc.setVisibility(View.GONE);
}
+ // --- ADRESSE OG KART ---
if (!event.getLocation().isEmpty()) {
- loc.setText("Sted: " + event.getLocation());
+ loc.setText(event.getLocation()); // Viser kun teksten, ikonet ligger i XML
loc.setVisibility(View.VISIBLE);
+
+ // Gjør adressen klikkbar for å åpne kart
+ loc.setOnClickListener(v -> {
+ String location = event.getLocation();
+ // Opprett en geo-URI. "q=" søker etter stedet.
+ Uri gmmIntentUri = Uri.parse("geo:0,0?q=" + Uri.encode(location));
+ Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
+ // Sett pakke til Google Maps for å foretrekke det, men la systemet velge hvis ikke installert
+ mapIntent.setPackage("com.google.android.apps.maps");
+
+ try {
+ startActivity(mapIntent);
+ } catch (Exception e) {
+ // Fallback: Hvis Google Maps ikke finnes, prøv uten pakkenavn (hvilken som helst kart-app)
+ mapIntent.setPackage(null);
+ try {
+ startActivity(mapIntent);
+ } catch (Exception ex) {
+ Toast.makeText(getContext(), "Fant ingen kart-app", Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
} else {
loc.setVisibility(View.GONE);
}
diff --git a/app/src/main/res/drawable-hdpi/ic_stat_kbs.png b/app/src/main/res/drawable-hdpi/ic_stat_kbs.png
index 0aa6148..e92dad0 100644
Binary files a/app/src/main/res/drawable-hdpi/ic_stat_kbs.png 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
index 789bb4b..c904ecc 100644
Binary files a/app/src/main/res/drawable-mdpi/ic_stat_kbs.png 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
index 7143739..6313090 100644
Binary files a/app/src/main/res/drawable-xhdpi/ic_stat_kbs.png 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
index e05de22..c27952e 100644
Binary files a/app/src/main/res/drawable-xxhdpi/ic_stat_kbs.png 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
index 819488a..126084b 100644
Binary files a/app/src/main/res/drawable-xxxhdpi/ic_stat_kbs.png and b/app/src/main/res/drawable-xxxhdpi/ic_stat_kbs.png differ
diff --git a/app/src/main/res/layout/bottom_sheet_calendar_details.xml b/app/src/main/res/layout/bottom_sheet_calendar_details.xml
index 8dd1950..0acc9d3 100644
--- a/app/src/main/res/layout/bottom_sheet_calendar_details.xml
+++ b/app/src/main/res/layout/bottom_sheet_calendar_details.xml
@@ -7,7 +7,7 @@
android:padding="24dp"
android:background="@android:color/white">
-
+
+
+ android:visibility="gone"
+ android:drawablePadding="8dp"
+ android:gravity="center_vertical"
+ android:background="?attr/selectableItemBackground"
+ android:paddingVertical="8dp"
+ app:drawableStartCompat="@android:drawable/ic_dialog_map"/>
+
+ android:layout_marginBottom="24dp"
+ android:autoLink="web|email|phone"
+ android:linksClickable="true"/>
) også blir klikkbare
+ Linkify.addLinks(desc, Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS);
+
+ // Gjør at man kan klikke på lenkene
desc.setMovementMethod(LinkMovementMethod.getInstance());
+
+ desc.setVisibility(View.VISIBLE);
} else {
desc.setVisibility(View.GONE);
}
+ // --- ADRESSE OG KART ---
if (!event.getLocation().isEmpty()) {
- loc.setText("Sted: " + event.getLocation());
+ loc.setText(event.getLocation()); // Viser kun teksten, ikonet ligger i XML
loc.setVisibility(View.VISIBLE);
+
+ // Gjør adressen klikkbar for å åpne kart
+ loc.setOnClickListener(v -> {
+ String location = event.getLocation();
+ // Opprett en geo-URI. "q=" søker etter stedet.
+ Uri gmmIntentUri = Uri.parse("geo:0,0?q=" + Uri.encode(location));
+ Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
+ // Sett pakke til Google Maps for å foretrekke det, men la systemet velge hvis ikke installert
+ mapIntent.setPackage("com.google.android.apps.maps");
+
+ try {
+ startActivity(mapIntent);
+ } catch (Exception e) {
+ // Fallback: Hvis Google Maps ikke finnes, prøv uten pakkenavn (hvilken som helst kart-app)
+ mapIntent.setPackage(null);
+ try {
+ startActivity(mapIntent);
+ } catch (Exception ex) {
+ Toast.makeText(getContext(), "Fant ingen kart-app", Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
} else {
loc.setVisibility(View.GONE);
}
@@ -5552,7 +5587,7 @@ import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity {
- public static final String GOOGLE_WEB_CLIENT_ID = "738325360287-cidl3plnqv9ei74vm9vm5muustj6eenb.apps.googleusercontent.com"; // Bytt med din egen hvis denne er feil
+ public static final String GOOGLE_WEB_CLIENT_ID = "SECRET.apps.googleusercontent.com"; // Bytt med din egen hvis denne er feil
private static final String TAG = "MainActivity";
private NavController navController;
@@ -7140,7 +7175,7 @@ FILSTI: app\src\main\res\layout\bottom_sheet_calendar_details.xml
android:padding="24dp"
android:background="@android:color/white">
-
+
+
+ android:visibility="gone"
+ android:drawablePadding="8dp"
+ android:gravity="center_vertical"
+ android:background="?attr/selectableItemBackground"
+ android:paddingVertical="8dp"
+ app:drawableStartCompat="@android:drawable/ic_dialog_map"/>
+
+ android:layout_marginBottom="24dp"
+ android:autoLink="web|email|phone"
+ android:linksClickable="true"/>
Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
- assertEquals("com.kbs.kbsintranett", appContext.getPackageName());
- }
-}
-
-============================================================
-FILSTI: app\src\main\AndroidManifest.xml
-============================================================
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-============================================================
-FILSTI: app\src\main\java\com\kbs\kbsintranett\AuthRepository.java
-============================================================
-// FILSTI: app\src\main\java\com\kbs\kbsintranett\AuthRepository.java
-package com.kbs.kbsintranett;
-
-import android.util.Log;
-import retrofit2.Call;
-import retrofit2.Callback;
-import retrofit2.Response;
-
-public class AuthRepository {
-
- private static final String TAG = "AuthRepository";
-
- // Interface for å gi beskjed tilbake til Activity/Fragment
- public interface AuthCallback {
- void onSuccess(String role);
- void onError(String message);
- }
-
- /**
- * Utfører selve API-kallet mot WordPress.
- * Denne brukes nå av både MainActivity (Silent Sign-In) og LoginFragment (Manuell).
- */
- public static void loginToWordPress(String googleIdToken, String displayName, String email, String photoUrl, AuthCallback callback) {
-
- // 1. Lagre Google-info midlertidig
- UserManager.getInstance().setUserData(displayName, email, googleIdToken, photoUrl);
-
- // 2. Gjør klar request
- LoginRequest request = new LoginRequest(googleIdToken);
-
- // 3. Send til WordPress
- RetrofitClient.getApiService().googleLogin(request).enqueue(new Callback() {
- @Override
- public void onResponse(Call call, Response response) {
- if (response.isSuccessful() && response.body() != null && response.body().success) {
- // SUKSESS!
- String cookie = response.body().fullCookie;
- String role = response.body().role;
-
- // NYTT: Hent utvidet info fra responsen
- int userId = response.body().userId;
- String fName = response.body().firstName;
- String lName = response.body().lastName;
- String stilling = response.body().stilling;
- String mobil = response.body().mobiltelefon;
-
- Log.d(TAG, "WordPress Login suksess! Rolle: " + role + ", UserID: " + userId);
-
- // Lagre cookie, rolle og ID
- UserManager.getInstance().setCookie(cookie);
- UserManager.getInstance().setUserRole(role);
- UserManager.getInstance().setUserId(userId);
-
- // Lagre utvidet info i UserManager
- UserManager.getInstance().setExtendedUserInfo(fName, lName, stilling, mobil);
-
- callback.onSuccess(role);
-
- } else {
- Log.e(TAG, "WordPress Login nektet. Kode: " + response.code());
- callback.onError("Kunne ikke logge inn på Intranettet (Kode: " + response.code() + ")");
- }
- }
-
- @Override
- public void onFailure(Call call, Throwable t) {
- Log.e(TAG, "Nettverksfeil mot WP", t);
- callback.onError("Nettverksfeil: " + t.getMessage());
- }
- });
- }
-}
-
-============================================================
-FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarAdapter.java
-============================================================
-package com.kbs.kbsintranett;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-import java.util.List;
-
-public class CalendarAdapter extends RecyclerView.Adapter {
-
- private List events;
- private final OnItemClickListener listener;
-
- public interface OnItemClickListener {
- void onItemClick(CalendarEvent event);
- }
-
- // Oppdatert konstruktør som tar imot en listener
- public CalendarAdapter(List events, OnItemClickListener listener) {
- this.events = events;
- this.listener = listener;
- }
-
- @NonNull
- @Override
- public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_calendar, parent, false);
- return new ViewHolder(view);
- }
-
- @Override
- public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
- CalendarEvent event = events.get(position);
- holder.day.setText(event.getDay());
- holder.month.setText(event.getMonth());
- holder.time.setText(event.getTime());
- holder.title.setText(event.getTitle());
-
- holder.itemView.setOnClickListener(v -> {
- if (listener != null) {
- listener.onItemClick(event);
- }
- });
- }
-
- @Override
- public int getItemCount() {
- return events.size();
- }
-
- public static class ViewHolder extends RecyclerView.ViewHolder {
- TextView day, month, title, time;
-
- public ViewHolder(View view) {
- super(view);
- day = view.findViewById(R.id.cal_day);
- month = view.findViewById(R.id.cal_month);
- title = view.findViewById(R.id.cal_title);
- time = view.findViewById(R.id.cal_time);
- }
- }
-}
-
-============================================================
-FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarDetailsBottomSheet.java
-============================================================
-package com.kbs.kbsintranett;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.provider.CalendarContract;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.TextView;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.TimeZone;
-
-public class CalendarDetailsBottomSheet extends BottomSheetDialogFragment {
-
- private CalendarEvent event;
-
- public CalendarDetailsBottomSheet(CalendarEvent event) {
- this.event = event;
- }
-
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.bottom_sheet_calendar_details, container, false);
-
- TextView title = view.findViewById(R.id.sheet_title);
- TextView time = view.findViewById(R.id.sheet_time);
- TextView desc = view.findViewById(R.id.sheet_desc);
- TextView loc = view.findViewById(R.id.sheet_location);
- Button btnAdd = view.findViewById(R.id.btn_add_to_calendar);
-
- // Skjul knapp siden appen nå varsler automatisk (iht krav)
- btnAdd.setVisibility(View.GONE);
-
- title.setText(event.getTitle());
- time.setText(event.getTime() + " (" + event.getDay() + ". " + event.getMonth() + ")");
-
- if (!event.getDescription().isEmpty()) {
- // HER ER FIKSEN FOR HTML:
- desc.setText(android.text.Html.fromHtml(event.getDescription(), android.text.Html.FROM_HTML_MODE_COMPACT));
- desc.setVisibility(View.VISIBLE);
- // Gjør linker klikkbare
- desc.setMovementMethod(android.text.method.LinkMovementMethod.getInstance());
- } else {
- desc.setVisibility(View.GONE);
- }
-
- if (!event.getLocation().isEmpty()) {
- loc.setText("Sted: " + event.getLocation());
- loc.setVisibility(View.VISIBLE);
- } else {
- loc.setVisibility(View.GONE);
- }
-
- return view;
- }
-
- private void addToSystemCalendar() {
- try {
- SimpleDateFormat apiFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
- apiFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
-
- Date startDate = apiFormat.parse(event.getRawDate());
- long startMillis = startDate.getTime();
- long endMillis = startMillis + (60 * 60 * 1000); // Default 1 time hvis slutt mangler
-
- if (event.getRawEndDate() != null && !event.getRawEndDate().isEmpty()) {
- Date endDate = apiFormat.parse(event.getRawEndDate());
- endMillis = endDate.getTime();
- }
-
- Intent intent = new Intent(Intent.ACTION_INSERT)
- .setData(CalendarContract.Events.CONTENT_URI)
- .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startMillis)
- .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endMillis)
- .putExtra(CalendarContract.Events.TITLE, event.getTitle())
- .putExtra(CalendarContract.Events.DESCRIPTION, event.getDescription())
- .putExtra(CalendarContract.Events.EVENT_LOCATION, event.getLocation())
- .putExtra(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_BUSY);
-
- startActivity(intent);
-
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-}
-
-============================================================
-FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarEvent.java
-============================================================
-package com.kbs.kbsintranett;
-
-import com.google.gson.annotations.SerializedName;
-
-public class CalendarEvent {
- @SerializedName("title")
- private String title;
-
- @SerializedName("start_date") // Juster denne nøkkelen til hva APIet faktisk returnerer (f.eks "start")
- private String rawDate;
-
- @SerializedName("end_date") // Juster nøkkel (f.eks "end")
- private String rawEndDate;
-
- @SerializedName("description")
- private String description;
-
- @SerializedName("location")
- private String location;
-
- // --- UI-hjelpefelter (settes manuelt i appen etter parsing) ---
- private String day; // F.eks "12"
- private String month; // F.eks "DES"
- private String time; // F.eks "10:00 - 11:30"
-
- // Konstruktør for Retrofit (Gson)
- public CalendarEvent(String title, String rawDate, String rawEndDate, String description, String location) {
- this.title = title;
- this.rawDate = rawDate;
- this.rawEndDate = rawEndDate;
- this.description = description;
- this.location = location;
- }
-
- // Konstruktør for manuell opprettelse (f.eks ved feil)
- public CalendarEvent(String title, String time, String day, String month) {
- this.title = title;
- this.time = time;
- this.day = day;
- this.month = month;
- }
-
- public String getTitle() { return title; }
- public String getRawDate() { return rawDate; }
- public String getRawEndDate() { return rawEndDate; }
- public String getDescription() { return description != null ? description : ""; }
- public String getLocation() { return location != null ? location : ""; }
-
- // Getters og Setters for UI-felter
- public String getDay() { return day; }
- public void setDay(String day) { this.day = day; }
-
- public String getMonth() { return month; }
- public void setMonth(String month) { this.month = month; }
-
- public String getTime() { return time; }
- public void setTime(String time) { this.time = time; }
-}
-
-============================================================
-FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarFullFragment.java
-============================================================
-package com.kbs.kbsintranett;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import androidx.navigation.Navigation;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Locale;
-import retrofit2.Call;
-import retrofit2.Callback;
-import retrofit2.Response;
-
-public class CalendarFullFragment extends Fragment {
-
- private RecyclerView recyclerView;
- private ProgressBar progressBar;
- private TextView emptyView;
- private LinearLayoutManager layoutManager; // Trenger denne for å scrolle
-
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- return inflater.inflate(R.layout.fragment_calendar_full, container, false);
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- recyclerView = view.findViewById(R.id.recycler_full_calendar);
- progressBar = view.findViewById(R.id.loading_full_calendar);
- emptyView = view.findViewById(R.id.empty_view_calendar);
- ImageView backBtn = view.findViewById(R.id.btn_back_calendar);
-
- layoutManager = new LinearLayoutManager(getContext());
- recyclerView.setLayoutManager(layoutManager);
-
- backBtn.setOnClickListener(v -> Navigation.findNavController(view).navigateUp());
-
- fetchAllEvents();
- }
-
- private void fetchAllEvents() {
- progressBar.setVisibility(View.VISIBLE);
-
- // Hent personlige hendelser (Nå med historikk)
- List deviceEvents = CalendarManager.getDeviceEvents(getContext());
-
- RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback>() {
- @Override
- public void onResponse(Call> call, Response> response) {
- if (!isAdded()) return;
- progressBar.setVisibility(View.GONE);
-
- List apiEvents = new ArrayList<>();
- if (response.isSuccessful() && response.body() != null) {
- for (CalendarEvent e : response.body()) {
- CalendarManager.formatEventForUI(e);
- apiEvents.add(e);
- }
- }
-
- // Flett og vis
- List allEvents = CalendarManager.mergeAndSort(apiEvents, deviceEvents);
-
- if (allEvents.isEmpty()) {
- emptyView.setVisibility(View.VISIBLE);
- recyclerView.setVisibility(View.GONE);
- } else {
- emptyView.setVisibility(View.GONE);
- recyclerView.setVisibility(View.VISIBLE);
-
- CalendarAdapter adapter = new CalendarAdapter(allEvents, event -> {
- CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
- sheet.show(getParentFragmentManager(), "CalendarDetails");
- });
- recyclerView.setAdapter(adapter);
-
- // --- SCROLL TIL I DAG ---
- scrollToToday(allEvents);
- }
- }
-
- @Override
- public void onFailure(Call> call, Throwable t) {
- if (!isAdded()) return;
- progressBar.setVisibility(View.GONE);
-
- if (!deviceEvents.isEmpty()) {
- CalendarAdapter adapter = new CalendarAdapter(deviceEvents, event -> {
- CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
- sheet.show(getParentFragmentManager(), "CalendarDetails");
- });
- recyclerView.setAdapter(adapter);
- scrollToToday(deviceEvents);
- } else {
- emptyView.setText("Ingen hendelser funnet.");
- emptyView.setVisibility(View.VISIBLE);
- }
- }
- });
- }
-
- private void scrollToToday(List events) {
- 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) {
- scrollIndex = i;
- break;
- }
- }
-
- // 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);
- }
- }
-}
-
-============================================================
-FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarManager.java
-============================================================
-package com.kbs.kbsintranett;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.provider.CalendarContract;
-import androidx.core.content.ContextCompat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-import java.util.Locale;
-import java.util.TimeZone;
-
-public class CalendarManager {
-
- // NY HJELPEMETODE: Finner ID-ene til kalendere som tilhører @kbs.no
- private static List getKbsCalendarIds(Context context) {
- List ids = new ArrayList<>();
-
- String[] projection = new String[] {
- CalendarContract.Calendars._ID,
- CalendarContract.Calendars.ACCOUNT_NAME
- };
-
- // Vi ser etter kontoer som slutter på @kbs.no
- // (SQL: account_name LIKE '%@kbs.no')
- String selection = CalendarContract.Calendars.ACCOUNT_NAME + " LIKE ?";
- String[] selectionArgs = new String[] {"%@kbs.no"};
-
- try (Cursor cursor = context.getContentResolver().query(
- CalendarContract.Calendars.CONTENT_URI,
- projection,
- selection,
- selectionArgs,
- null
- )) {
- if (cursor != null) {
- while (cursor.moveToNext()) {
- ids.add(cursor.getString(0)); // Legg til kalender-ID
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- return ids;
- }
-
- public static List getDeviceEvents(Context context) {
- List deviceEvents = new ArrayList<>();
-
- if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR)
- != PackageManager.PERMISSION_GRANTED) {
- return deviceEvents;
- }
-
- // 1. Finn først ID-ene til KBS-kalenderne
- List kbsCalendarIds = getKbsCalendarIds(context);
-
- // Hvis ingen kbs-kalendere finnes på telefonen, returner tom liste
- if (kbsCalendarIds.isEmpty()) {
- return deviceEvents;
- }
-
- // Hent events fra 1 år tilbake og 1 år frem
- long now = System.currentTimeMillis();
- long startMillis = now - (365L * 24 * 60 * 60 * 1000);
- long endMillis = now + (365L * 24 * 60 * 60 * 1000);
-
- String[] projection = new String[]{
- CalendarContract.Events.TITLE,
- CalendarContract.Events.DTSTART,
- CalendarContract.Events.DTEND,
- CalendarContract.Events.DESCRIPTION,
- CalendarContract.Events.EVENT_LOCATION,
- CalendarContract.Events.ALL_DAY
- };
-
- // 2. Bygg opp spørringen for å filtrere på disse ID-ene
- // Resultatet blir noe sånt som: "((calendar_id = ?) OR (calendar_id = ?)) AND dtstart >= ? AND dtstart <= ?"
- StringBuilder selection = new StringBuilder("(");
- List selectionArgsList = new ArrayList<>();
-
- for (int i = 0; i < kbsCalendarIds.size(); i++) {
- selection.append(CalendarContract.Events.CALENDAR_ID).append(" = ?");
- selectionArgsList.add(kbsCalendarIds.get(i));
- if (i < kbsCalendarIds.size() - 1) {
- selection.append(" OR ");
- }
- }
- selection.append(") AND ");
-
- selection.append(CalendarContract.Events.DTSTART).append(" >= ? AND ");
- selection.append(CalendarContract.Events.DTSTART).append(" <= ?");
-
- selectionArgsList.add(String.valueOf(startMillis));
- selectionArgsList.add(String.valueOf(endMillis));
-
- String[] selectionArgs = selectionArgsList.toArray(new String[0]);
-
- try (Cursor cursor = context.getContentResolver().query(
- CalendarContract.Events.CONTENT_URI,
- projection,
- selection.toString(),
- selectionArgs,
- CalendarContract.Events.DTSTART + " ASC"
- )) {
- if (cursor != null) {
- SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
-
- while (cursor.moveToNext()) {
- String title = cursor.getString(0);
- long dtStart = cursor.getLong(1);
- long dtEnd = cursor.getLong(2);
- String desc = cursor.getString(3);
- String loc = cursor.getString(4);
- int allDay = cursor.getInt(5);
-
- String rawStart;
- String rawEnd;
-
- if (allDay == 1) {
- SimpleDateFormat shortFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
- rawStart = shortFormat.format(new Date(dtStart));
- rawEnd = shortFormat.format(new Date(dtEnd));
- } else {
- rawStart = isoFormat.format(new Date(dtStart));
- rawEnd = isoFormat.format(new Date(dtEnd));
- }
-
- CalendarEvent event = new CalendarEvent(title, rawStart, rawEnd, desc, loc);
- formatEventForUI(event);
- deviceEvents.add(event);
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- return deviceEvents;
- }
-
- // --- (formatEventForUI og mergeAndSort er uendret fra forrige versjon) ---
- public static void formatEventForUI(CalendarEvent event) {
- if (event.getRawDate() == null) return;
-
- SimpleDateFormat outputDay = new SimpleDateFormat("dd", Locale.getDefault());
- SimpleDateFormat outputMonth = new SimpleDateFormat("MMM", new Locale("no", "NO"));
- SimpleDateFormat outputTime = new SimpleDateFormat("HH:mm", Locale.getDefault());
-
- outputMonth.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
- outputTime.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
-
- try {
- Date date = null;
- Date endDate = null;
- boolean isAllDay = false;
-
- String raw = event.getRawDate();
-
- if (raw.length() == 10 && !raw.contains("T") && !raw.contains(" ")) {
- SimpleDateFormat shortFmt = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
- date = shortFmt.parse(raw);
- isAllDay = true;
- if (event.getRawEndDate() != null && event.getRawEndDate().length() == 10) {
- endDate = shortFmt.parse(event.getRawEndDate());
- }
- }
- else if (raw.contains("T")) {
- SimpleDateFormat isoFmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
- isoFmt.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
- date = isoFmt.parse(raw);
- if (event.getRawEndDate() != null && event.getRawEndDate().contains("T")) {
- endDate = isoFmt.parse(event.getRawEndDate());
- }
- }
- else {
- SimpleDateFormat sqlFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
- sqlFmt.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
- date = sqlFmt.parse(raw);
- if (event.getRawEndDate() != null) {
- endDate = sqlFmt.parse(event.getRawEndDate());
- }
- }
-
- if (date != null) {
- event.setDay(outputDay.format(date));
- event.setMonth(outputMonth.format(date).toUpperCase());
-
- if (isAllDay) {
- event.setTime("Hele dagen");
- } else {
- String timeStr = outputTime.format(date);
- if (endDate != null) {
- timeStr += " - " + outputTime.format(endDate);
- }
- event.setTime("Kl. " + timeStr);
- }
- }
-
- } catch (Exception e) {
- e.printStackTrace();
- event.setDay("??");
- event.setMonth("???");
- event.setTime("Feil dato");
- }
- }
-
- public static List mergeAndSort(List apiEvents, List deviceEvents) {
- List all = new ArrayList<>(apiEvents);
- all.addAll(deviceEvents);
-
- Collections.sort(all, (e1, e2) -> {
- String d1 = e1.getRawDate() != null ? e1.getRawDate() : "";
- String d2 = e2.getRawDate() != null ? e2.getRawDate() : "";
- return d1.compareTo(d2);
- });
-
- return all;
- }
-}
-
-============================================================
-FILSTI: app\src\main\java\com\kbs\kbsintranett\CategoryAdapter.java
-============================================================
-package com.kbs.kbsintranett;
-
-import android.graphics.Color;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-import androidx.annotation.NonNull;
-import androidx.core.content.ContextCompat;
-import androidx.recyclerview.widget.RecyclerView;
-import java.util.List;
-
-public class CategoryAdapter extends RecyclerView.Adapter {
-
- private List categories;
- private String selectedCategory = "Alle"; // Standardvalg
- private OnCategoryClickListener listener;
-
- public interface OnCategoryClickListener {
- void onCategoryClick(String category);
- }
-
- public CategoryAdapter(List categories, OnCategoryClickListener listener) {
- this.categories = categories;
- this.listener = listener;
- }
-
- @NonNull
- @Override
- public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_category, parent, false);
- return new ViewHolder(view);
- }
-
- @Override
- public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
- String category = categories.get(position);
- holder.name.setText(category);
-
- if (category.equals(selectedCategory)) {
- // Valgt stil (Blå bakgrunn, hvit tekst)
- holder.name.setBackgroundResource(R.drawable.bg_category_selected);
- holder.name.setTextColor(Color.WHITE);
- } else {
- // Ikke valgt stil (Hvit bakgrunn, mørk tekst)
- holder.name.setBackgroundResource(R.drawable.bg_category_unselected);
- holder.name.setTextColor(Color.parseColor("#333333"));
- }
-
- holder.itemView.setOnClickListener(v -> {
- selectedCategory = category;
- notifyDataSetChanged(); // Oppdater alle for å flytte markering
- listener.onCategoryClick(category);
- });
- }
-
- @Override
- public int getItemCount() {
- return categories.size();
- }
-
- public static class ViewHolder extends RecyclerView.ViewHolder {
- TextView name;
- public ViewHolder(View view) {
- super(view);
- name = view.findViewById(R.id.category_name);
- }
- }
-}
-
-============================================================
-FILSTI: app\src\main\java\com\kbs\kbsintranett\ChoicesAdapter.java
-============================================================
-package com.kbs.kbsintranett;
-
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonParseException;
-import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.List;
-
-public class ChoicesAdapter implements JsonDeserializer> {
- @Override
- public List deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
- // Hvis feltet er "null" eller en tom tekststreng, returner en tom liste
- if (json.isJsonNull() || (json.isJsonPrimitive() && json.getAsString().isEmpty())) {
- return new ArrayList<>();
- }
-
- // Hvis det faktisk er en liste (Array), les den som vanlig
- if (json.isJsonArray()) {
- List list = new ArrayList<>();
- for (JsonElement e : json.getAsJsonArray()) {
- list.add(context.deserialize(e, GravityField.Choice.class));
- }
- return list;
- }
-
- // Hvis vi får noe annet rart (f.eks. en tekst som ikke er tom), ignorer det for å unngå krasj
- return new ArrayList<>();
- }
-}
-
-============================================================
-FILSTI: app\src\main\java\com\kbs\kbsintranett\ConditionalLogicAdapter.java
-============================================================
-package com.kbs.kbsintranett;
-
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonParseException;
-import java.lang.reflect.Type;
-
-public class ConditionalLogicAdapter implements JsonDeserializer {
- @Override
- public GravityField.ConditionalLogic deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
- // Hvis feltet er en streng (f.eks tom streng ""), returner null
- if (json.isJsonPrimitive() && json.getAsJsonPrimitive().isString()) {
- return null;
- }
-
- // Hvis det er et objekt, bruk standard deserialisering
- if (json.isJsonObject()) {
- // Vi må manuelt deserialisere for å unngå uendelig løkke hvis vi bare kaller context.deserialize på samme type
- // Enkleste måte er å la Gson gjøre jobben på innholdet
- return new com.google.gson.Gson().fromJson(json, GravityField.ConditionalLogic.class);
- }
-
- return null;
- }
-}
-
-============================================================
-FILSTI: app\src\main\java\com\kbs\kbsintranett\FormsFragment.java
-============================================================
-package com.kbs.kbsintranett;
-
-import android.Manifest;
-import android.animation.LayoutTransition;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.DatePickerDialog;
-import android.app.TimePickerDialog;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.graphics.Color;
-import android.graphics.Typeface;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Environment;
-import android.provider.OpenableColumns;
-import android.text.Editable;
-import android.text.Html;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ProgressBar;
-import android.widget.RadioButton;
-import android.widget.RadioGroup;
-import android.widget.ScrollView;
-import android.widget.Spinner;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
-import androidx.core.content.FileProvider;
-import androidx.fragment.app.Fragment;
-
-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;
-import org.json.JSONObject;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import okhttp3.MediaType;
-import okhttp3.MultipartBody;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
-import okio.BufferedSink;
-import okio.Okio;
-import okio.Source;
-
-public class FormsFragment extends Fragment {
-
- private static final String TAG = "FormsFragment";
- private static final String BASE_URL_GF = "https://intranet.kbs.no/wp-json/gf/v2";
-
- // SKJEMA ID-er
- private static final int ID_ANSATTEOPPLYSNINGER = 1;
- private static final int ID_RUH = 4;
- private static final int ID_SIKKERHETSKURS = 9;
- private static final int ID_HMS_BEKREFTELSE = 10;
- private static final int ID_EGENMELDING = 11;
- private static final int ID_SJA = 14;
- private static final int ID_FRAVARSVARSEL = 15;
- private static final int ID_REFUSJON_UTLEGG = 16;
-
- private int formId = 1;
-
- private LinearLayout formContainer;
- private LinearLayout historyContainer;
- private View historyWrapper; // Wrapper for historikk-modulen
- private TextView txtStatus;
- private TextView lblHistory;
- private ProgressBar loadingSpinner;
- private ImageView btnToggleHistory;
-
- // --- HOVEDSKJEMA STATE ---
- private Map fieldWrappers = new HashMap<>();
- private Map inputViews = new HashMap<>();
- private Map requiredFieldsMap = new HashMap<>();
- private Map fileUploads = new HashMap<>();
-
- // --- NESTED FORM (BARN) STATE ---
- 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;
-
- // --- FILOPPLASTING & KAMERA ---
- private String pendingFileFieldId = null;
- private boolean isSelectingForChild = false;
- private Uri currentPhotoUri = null;
-
- private ActivityResultLauncher filePickerLauncher;
- private ActivityResultLauncher takePictureLauncher;
- private ActivityResultLauncher requestPermissionLauncher;
-
- private GravityForm currentForm;
- private final OkHttpClient client = new OkHttpClient();
-
- private static final Pattern TITLE_PATTERN = Pattern.compile("^(\\d+)[.\\s-]+\\s*(.*)");
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- filePickerLauncher = registerForActivityResult(
- new ActivityResultContracts.StartActivityForResult(),
- result -> {
- if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
- Uri uri = result.getData().getData();
- if (uri != null && pendingFileFieldId != null) {
- handleFileSelection(pendingFileFieldId, uri, isSelectingForChild);
- }
- }
- }
- );
- takePictureLauncher = registerForActivityResult(
- new ActivityResultContracts.TakePicture(),
- success -> {
- if (success && currentPhotoUri != null && pendingFileFieldId != null) {
- handleFileSelection(pendingFileFieldId, currentPhotoUri, isSelectingForChild);
- } else if (!success) {
- currentPhotoUri = null;
- }
- }
- );
- requestPermissionLauncher = registerForActivityResult(
- new ActivityResultContracts.RequestPermission(),
- isGranted -> {
- if (isGranted) {
- openCamera();
- } else {
- Toast.makeText(getContext(), "Kameratillatelse er påkrevd for å ta bilde.", Toast.LENGTH_LONG).show();
- }
- }
- );
- }
-
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_forms, container, false);
- formContainer = view.findViewById(R.id.form_container);
- historyContainer = view.findViewById(R.id.historyContainer);
- historyWrapper = view.findViewById(R.id.history_wrapper);
- txtStatus = view.findViewById(R.id.txt_status);
- lblHistory = view.findViewById(R.id.lbl_history);
- loadingSpinner = view.findViewById(R.id.loading_spinner);
-
- 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) {
- transition = new LayoutTransition();
- ((ViewGroup) view).setLayoutTransition(transition);
- }
- transition.enableTransitionType(LayoutTransition.CHANGING);
- }
- // ----------------------------------------------------------
-
- if (formContainer == null) {
- formContainer = new LinearLayout(getContext());
- }
-
- if (getArguments() != null) {
- int argId = getArguments().getInt("formId", 0);
- if (argId != 0) formId = argId;
- }
-
- fetchFormStructure();
-
- return view;
- }
-
- // --- UI LOGIKK FOR DELT SKJERM ---
-
- private void expandFormModule() {
- if (historyWrapper != null && historyWrapper.getVisibility() == View.VISIBLE) {
- historyWrapper.setVisibility(View.GONE);
- if (btnToggleHistory != null) {
- btnToggleHistory.setImageResource(android.R.drawable.arrow_down_float);
- }
- }
- }
-
- private void toggleHistoryVisibility() {
- 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);
- }
- }
-
- private void attachInteractionListener(View view) {
- if (view == null) return;
- view.setOnTouchListener((v, event) -> {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- expandFormModule();
- }
- return false;
- });
- view.setOnFocusChangeListener((v, hasFocus) -> {
- if (hasFocus) {
- expandFormModule();
- }
- });
- if (view.isClickable()) {
- view.setOnClickListener(v -> expandFormModule());
- }
- }
-
- // ----------------------------------
-
- private void fetchFormStructure() {
- if (loadingSpinner != null) loadingSpinner.setVisibility(View.VISIBLE);
- updateStatus("Laster skjema...");
-
- WordPressApiService api = RetrofitClient.getApiService();
- api.getForm(formId).enqueue(new retrofit2.Callback() {
- @Override
- public void onResponse(retrofit2.Call call, retrofit2.Response response) {
- if (response.isSuccessful() && response.body() != null) {
- currentForm = response.body();
- if (getActivity() != null) {
- getActivity().runOnUiThread(() -> {
- if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE);
- renderDynamicForm(currentForm);
- fetchFormEntries();
- });
- }
- } else {
- updateStatus("Feil ved lasting av skjema.");
- if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void onFailure(retrofit2.Call call, Throwable t) {
- updateStatus("Nettverksfeil (Skjema): " + t.getMessage());
- if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE);
- }
- });
- }
-
- private void renderDynamicForm(GravityForm form) {
- if (formContainer == null) return;
- formContainer.removeAllViews();
- fieldWrappers.clear();
- inputViews.clear();
- requiredFieldsMap.clear();
- fileUploads.clear();
- nestedEntries.clear();
- updateStatus("");
-
- // Reset visibility of history on new load
- 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);
-
- if (form.description != null && !form.description.isEmpty()) {
- TextView formDesc = new TextView(getContext());
- String cleanDesc = form.description.replaceFirst("^\\d+\\.\\s*", "");
- formDesc.setText(cleanDesc);
- formDesc.setPadding(0, 0, 0, 40);
- formContainer.addView(formDesc);
- }
-
- if (form.fields == null) return;
- for (GravityField field : form.fields) {
- if ("hidden".equals(field.type) || field.isHidden || "hidden".equals(field.visibility)) {
- continue;
- }
-
- LinearLayout fieldWrapper = new LinearLayout(getContext());
- fieldWrapper.setOrientation(LinearLayout.VERTICAL);
- fieldWrapper.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
- fieldWrapper.setPadding(0, 10, 0, 20);
-
- fieldWrappers.put(String.valueOf(field.id), fieldWrapper);
-
- if ("section".equals(field.type)) {
- addSectionHeader(fieldWrapper, field.label, field.description);
- formContainer.addView(fieldWrapper);
- continue;
- }
-
- if ("html".equals(field.type)) {
- if (field.content != null && !field.content.isEmpty()) {
- TextView htmlView = new TextView(getContext());
- htmlView.setText(Html.fromHtml(field.content, Html.FROM_HTML_MODE_COMPACT));
- fieldWrapper.addView(htmlView);
- }
- formContainer.addView(fieldWrapper);
- continue;
- }
-
- TextView label = new TextView(getContext());
- String labelText = field.label;
- if (field.isRequired) labelText += " *";
- label.setText(labelText);
- label.setTextColor(Color.DKGRAY);
- label.setTypeface(null, Typeface.BOLD);
- label.setPadding(0, 10, 0, 5);
- fieldWrapper.addView(label);
-
- if ("form".equals(field.type)) {
- renderNestedFormField(fieldWrapper, field);
- }
- else if ("product".equals(field.type) && "calculation".equals(field.inputType)) {
- renderTotalSumField(fieldWrapper, field);
- }
- else if ("time".equals(field.type)) {
- renderTimeField(fieldWrapper, field, inputViews, requiredFieldsMap);
- } else if ("fileupload".equals(field.type)) {
- renderFileUploadField(fieldWrapper, field, inputViews, requiredFieldsMap, false);
- } else if (field.inputs != null && !field.inputs.isEmpty()) {
- if ("consent".equals(field.type)) {
- renderConsentField(fieldWrapper, field, inputViews, requiredFieldsMap);
- } else if ("checkbox".equals(field.type) || "multi_choice".equals(field.type)) {
- renderCheckboxField(fieldWrapper, field, inputViews, requiredFieldsMap);
- } else {
- renderCompositeField(fieldWrapper, field, inputViews, requiredFieldsMap);
- }
- } else if ("radio".equals(field.type)) {
- renderRadioField(fieldWrapper, field, inputViews, requiredFieldsMap);
- } else if ("select".equals(field.type)) {
- renderSelectField(fieldWrapper, field, inputViews, requiredFieldsMap);
- } else if ("textarea".equals(field.type)) {
- renderTextAreaField(fieldWrapper, field, inputViews, requiredFieldsMap);
- } else if ("date".equals(field.type)) {
- renderDateField(fieldWrapper, field, inputViews, requiredFieldsMap);
- } else if ("consent".equals(field.type)) {
- renderConsentField(fieldWrapper, field, inputViews, requiredFieldsMap);
- } else {
- renderTextField(fieldWrapper, field, inputViews, requiredFieldsMap);
- }
-
- if (field.description != null && !field.description.isEmpty()) {
- TextView desc = new TextView(getContext());
- desc.setText(Html.fromHtml(field.description, Html.FROM_HTML_MODE_COMPACT));
- desc.setTextSize(12);
- desc.setTextColor(Color.GRAY);
- fieldWrapper.addView(desc);
- }
-
- formContainer.addView(fieldWrapper);
- }
-
- Button dynamicSubmit = new Button(getContext());
- dynamicSubmit.setText("Send inn skjema");
- dynamicSubmit.setTextColor(Color.WHITE);
- dynamicSubmit.setBackgroundColor(Color.parseColor("#0069B3")); // KBS Blå
- LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- params.setMargins(0, 60, 0, 20);
- dynamicSubmit.setLayoutParams(params);
- dynamicSubmit.setOnClickListener(v -> submitDynamicForm());
- formContainer.addView(dynamicSubmit);
-
- evaluateAllConditionalLogic();
- }
-
- // --- NESTED FORM LOGIKK ---
-
- private void renderNestedFormField(LinearLayout container, GravityField field) {
- nestedEntriesContainer = new LinearLayout(getContext());
- nestedEntriesContainer.setOrientation(LinearLayout.VERTICAL);
- nestedEntriesContainer.setPadding(0, 10, 0, 10);
- container.addView(nestedEntriesContainer);
-
- Button btnAdd = new Button(getContext());
- btnAdd.setText("Legg til vedlegg");
- btnAdd.setBackgroundColor(Color.parseColor("#53AFE9"));
- btnAdd.setTextColor(Color.WHITE);
- btnAdd.setOnClickListener(v -> {
- expandFormModule(); // Trigger expand
- 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);
-
- EditText hiddenIds = new EditText(getContext());
- hiddenIds.setVisibility(View.GONE);
- inputViews.put(field.id, hiddenIds);
- }
-
- private void renderTotalSumField(LinearLayout container, GravityField field) {
- totalAmountView = new TextView(getContext());
- totalAmountView.setText("Kr 0,00");
- totalAmountView.setTextSize(18);
- totalAmountView.setTypeface(null, Typeface.BOLD);
- totalAmountView.setPadding(10, 10, 10, 10);
- container.addView(totalAmountView);
- }
-
- // NY SIGNATUR: Tar imot parentFieldId
- private void openChildFormDialog(int childFormId, String parentFieldId) {
- if (getActivity() == null) return;
- ProgressBar pBar = new ProgressBar(getContext());
- AlertDialog loadingDialog = new AlertDialog.Builder(getContext())
- .setView(pBar)
- .setMessage("Laster skjema...")
- .setCancelable(false)
- .show();
- RetrofitClient.getApiService().getForm(childFormId).enqueue(new retrofit2.Callback() {
- @Override
- public void onResponse(retrofit2.Call call, retrofit2.Response response) {
- loadingDialog.dismiss();
- if (response.isSuccessful() && response.body() != null) {
- showChildFormDialog(response.body(), parentFieldId);
- } else {
- Toast.makeText(getContext(), "Kunne ikke hente underskjema", Toast.LENGTH_SHORT).show();
- }
- }
-
- @Override
- public void onFailure(retrofit2.Call call, Throwable t) {
- loadingDialog.dismiss();
- Toast.makeText(getContext(), "Feil: " + t.getMessage(), Toast.LENGTH_SHORT).show();
- }
- });
- }
-
- // NY SIGNATUR: Tar imot parentFieldId
- private void showChildFormDialog(GravityForm childForm, String parentFieldId) {
- AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
- childInputViews.clear();
- childRequiredFieldsMap.clear();
- childFileUploads.clear();
-
- ScrollView scrollView = new ScrollView(getContext());
- LinearLayout layout = new LinearLayout(getContext());
- layout.setOrientation(LinearLayout.VERTICAL);
- layout.setPadding(30, 30, 30, 30);
- scrollView.addView(layout);
- for (GravityField field : childForm.fields) {
- if ("hidden".equals(field.type) || field.isHidden) continue;
- LinearLayout wrapper = new LinearLayout(getContext());
- wrapper.setOrientation(LinearLayout.VERTICAL);
- wrapper.setPadding(0, 10, 0, 20);
-
- TextView label = new TextView(getContext());
- String lText = field.label;
- if (field.isRequired) lText += " *";
- label.setText(lText);
- label.setTypeface(null, Typeface.BOLD);
- wrapper.addView(label);
- if ("fileupload".equals(field.type)) {
- renderFileUploadField(wrapper, field, childInputViews, childRequiredFieldsMap, true);
- } else if ("product".equals(field.type)) {
- EditText input = new EditText(getContext());
- input.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
- input.setHint("Kr 0.00");
- wrapper.addView(input);
- childInputViews.put(field.id, input);
- childRequiredFieldsMap.put(field.id, field.isRequired);
- } else {
- renderTextField(wrapper, field, childInputViews, childRequiredFieldsMap);
- }
- layout.addView(wrapper);
- }
-
- builder.setView(scrollView);
- builder.setPositiveButton("Legg til vedlegg", null);
- builder.setNegativeButton("Avbryt", (d, w) -> d.dismiss());
- AlertDialog dialog = builder.create();
- dialog.show();
-
- dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
- submitChildForm(childForm.id, dialog, parentFieldId);
- });
- }
-
- // 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()) {
- String val = getInputValueGeneric(entry.getValue());
- if (!val.isEmpty()) {
- try {
- inputValues.put("input_" + entry.getKey(), val);
- } catch (JSONException e) { e.printStackTrace(); }
- }
- }
-
- if (!childFileUploads.isEmpty()) {
- List fileParts = new ArrayList<>();
- Map textParts = new HashMap<>();
-
- try {
- JSONArray names = inputValues.names();
- if (names != null) {
- for (int i = 0; i < names.length(); i++) {
- String key = names.getString(i);
- String val = inputValues.getString(key);
- textParts.put(key, RequestBody.create(MultipartBody.FORM, val));
- }
- }
-
- // --- 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();
- Uri uri = fileEntry.getValue();
- if (uri != null) {
- MultipartBody.Part part = getFilePart("input_" + fieldId, uri);
- if (part != null) fileParts.add(part);
- }
- }
-
- Toast.makeText(getContext(), "Laster opp vedlegg...", Toast.LENGTH_SHORT).show();
- RetrofitClient.getApiService().submitMultipartForm(childFormId, textParts, fileParts).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();
- 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);
- dialog.dismiss();
- } else {
- Toast.makeText(getContext(), "Ugyldig respons fra server", Toast.LENGTH_SHORT).show();
- }
- } catch (Exception e) {
- e.printStackTrace();
- Toast.makeText(getContext(), "Feil ved parsing av svar", Toast.LENGTH_SHORT).show();
- }
- } else {
- Toast.makeText(getContext(), "Feil ved opplasting", Toast.LENGTH_SHORT).show();
- }
- }
- @Override
- public void onFailure(retrofit2.Call call, Throwable t) {
- Toast.makeText(getContext(), "Nettverksfeil", Toast.LENGTH_SHORT).show();
- }
- });
- } catch (Exception e) { e.printStackTrace(); }
- }
- }
-
- private void addNestedEntry(String entryId, String description, String price) {
- nestedEntries.add(new NestedEntry(entryId, description, price));
- refreshNestedList();
- }
-
- private void refreshNestedList() {
- if (nestedEntriesContainer == null) return;
- nestedEntriesContainer.removeAllViews();
-
- double total = 0;
- List ids = new ArrayList<>();
- for (NestedEntry entry : nestedEntries) {
- ids.add(entry.id);
- String cleanPrice = entry.price.replaceAll("[^0-9,.]", "").replace(",", ".");
- try {
- if (!cleanPrice.isEmpty()) total += Double.parseDouble(cleanPrice);
- } catch (NumberFormatException e) { }
-
- LinearLayout row = new LinearLayout(getContext());
- row.setOrientation(LinearLayout.HORIZONTAL);
- row.setPadding(10, 10, 10, 10);
-
- TextView txt = new TextView(getContext());
- txt.setText(entry.description + " (" + entry.price + ")");
- txt.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1));
-
- row.addView(txt);
- nestedEntriesContainer.addView(row);
- }
-
- if (totalAmountView != null) {
- totalAmountView.setText("Totalt: Kr " + String.format("%.2f", total));
- }
-
- View hiddenField = inputViews.get("25");
- if (hiddenField instanceof EditText) {
- ((EditText)hiddenField).setText(TextUtils.join(",", ids));
- }
- }
-
- // --- FELLES METODER (FILE UPLOAD M/ CAMERA STØTTE) ---
-
- private void renderFileUploadField(LinearLayout container, GravityField field, Map viewsMap, Map reqMap, boolean isChild) {
- LinearLayout fileLayout = new LinearLayout(getContext());
- fileLayout.setOrientation(LinearLayout.HORIZONTAL);
-
- 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
- showFileSourceDialog();
- });
- TextView txtFileName = new TextView(getContext());
- txtFileName.setText("Ingen fil valgt");
- txtFileName.setPadding(20, 0, 0, 0);
- txtFileName.setTextColor(Color.GRAY);
- btnUpload.setTag(txtFileName);
-
- fileLayout.addView(btnUpload);
- fileLayout.addView(txtFileName);
- container.addView(fileLayout);
-
- viewsMap.put(field.id, btnUpload);
- 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("*/*");
- String[] mimeTypes = {"image/jpeg", "image/png", "application/pdf"};
- intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
- filePickerLauncher.launch(intent);
- }
- }
- })
- .show();
- }
-
- private void openCamera() {
- currentPhotoUri = createImageUri();
- if (currentPhotoUri != null && takePictureLauncher != null) {
- try {
- takePictureLauncher.launch(currentPhotoUri);
- } catch (Exception e) {
- Toast.makeText(getContext(), "Kunne ikke starte kamera: " + e.getMessage(), Toast.LENGTH_SHORT).show();
- Log.e(TAG, "Camera launch failed", e);
- }
- } else {
- Toast.makeText(getContext(), "Kunne ikke opprette bildefil", Toast.LENGTH_SHORT).show();
- }
- }
-
- private Uri createImageUri() {
- try {
- String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
- String imageFileName = "JPEG_" + timeStamp + "_";
- File storageDir = requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
- File image = File.createTempFile(imageFileName, ".jpg", storageDir);
- return FileProvider.getUriForFile(requireContext(), "com.kbs.kbsintranett.fileprovider", image);
- } catch (IOException e) {
- e.printStackTrace();
- return null;
- }
- }
-
- private void handleFileSelection(String fieldId, Uri uri, boolean isChild) {
- if (isChild) {
- childFileUploads.put(fieldId, uri);
- } else {
- fileUploads.put(fieldId, uri);
- }
-
- Map targetMap = isChild ? childInputViews : inputViews;
- View view = targetMap.get(fieldId);
- if (view instanceof Button) {
- TextView txtView = (TextView) view.getTag();
- if (txtView != null) {
- txtView.setText(getFileName(uri));
- txtView.setTextColor(Color.BLACK);
- }
- }
- }
-
- private String getFileName(Uri uri) {
- String result = null;
- if (uri.getScheme().equals("content")) {
- try (Cursor cursor = getContext().getContentResolver().query(uri, null, null, null, null)) {
- if (cursor != null && cursor.moveToFirst()) {
- int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
- if(index >= 0) result = cursor.getString(index);
- }
- } catch (Exception e) {}
- }
- if (result == null) {
- result = uri.getPath();
- int cut = result.lastIndexOf('/');
- if (cut != -1) result = result.substring(cut + 1);
- }
- return result;
- }
-
- private String getCleanTitle(String title) {
- if (title == null) return "";
- Matcher m = TITLE_PATTERN.matcher(title.trim());
- if (m.find()) {
- return m.group(2);
- }
- return title;
- }
-
- // --- STANDARD RENDER METODER ---
-
- private void renderTimeField(LinearLayout container, GravityField field, Map views, Map req) {
- EditText timeInput = new EditText(getContext());
- timeInput.setFocusable(false);
- timeInput.setClickable(true);
- timeInput.setHint("00:00");
- timeInput.setOnClickListener(v -> {
- expandFormModule(); // Trigger expand
- Calendar mcurrentTime = Calendar.getInstance();
- int hour = mcurrentTime.get(Calendar.HOUR_OF_DAY);
- int minute = mcurrentTime.get(Calendar.MINUTE);
- new TimePickerDialog(getContext(), (timePicker, selectedHour, selectedMinute) -> {
- timeInput.setText(String.format("%02d:%02d", selectedHour, selectedMinute));
- evaluateAllConditionalLogic();
- }, hour, minute, true).show();
- });
- container.addView(timeInput);
- views.put(field.id, timeInput);
- req.put(field.id, field.isRequired);
- }
-
- private void renderTextField(LinearLayout container, GravityField field, Map views, Map req) {
- EditText input = new EditText(getContext());
- input.setPadding(30, 30, 30, 30);
- input.setBackgroundResource(android.R.drawable.edit_text);
-
- if ("number".equals(field.type) || "phone".equals(field.type)) {
- input.setInputType(InputType.TYPE_CLASS_PHONE);
- } else if ("email".equals(field.type)) {
- input.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
- } else {
- input.setInputType(InputType.TYPE_CLASS_TEXT);
- }
-
- if (views == inputViews) {
- UserManager user = UserManager.getInstance();
- String lowerLabel = field.label.toLowerCase();
- if (lowerLabel.contains("e-post")) input.setText(user.getUserEmail());
- if (lowerLabel.contains("navn") || lowerLabel.contains("melder")) input.setText(user.getUserDisplayName());
- if (lowerLabel.contains("stilling")) input.setText(user.getStilling());
- if (lowerLabel.contains("mobil")) input.setText(user.getMobiltelefon());
- }
-
- input.addTextChangedListener(new TextWatcher() {
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
- public void onTextChanged(CharSequence s, int start, int before, int count) {}
- public void afterTextChanged(Editable s) { evaluateAllConditionalLogic(); }
- });
- attachInteractionListener(input); // Add listener
-
- container.addView(input);
- views.put(field.id, input);
- req.put(field.id, field.isRequired);
- }
-
- private void renderTextAreaField(LinearLayout container, GravityField field, Map views, Map req) {
- EditText input = new EditText(getContext());
- input.setBackgroundResource(android.R.drawable.edit_text);
- input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
- input.setMinLines(3);
- input.setGravity(android.view.Gravity.TOP | android.view.Gravity.START);
-
- attachInteractionListener(input); // Add listener
-
- container.addView(input);
- views.put(field.id, input);
- req.put(field.id, field.isRequired);
- }
-
- private void renderRadioField(LinearLayout container, GravityField field, Map views, Map req) {
- RadioGroup group = new RadioGroup(getContext());
- if (field.choices != null) {
- for (GravityField.Choice choice : field.choices) {
- RadioButton rb = new RadioButton(getContext());
- rb.setText(choice.text);
- rb.setTag(choice.value);
- // Also trigger expand on RadioButton click
- rb.setOnClickListener(v -> {
- expandFormModule();
- evaluateAllConditionalLogic();
- });
- group.addView(rb);
- }
- }
- // Fallback listener
- group.setOnCheckedChangeListener((g, i) -> {
- expandFormModule();
- evaluateAllConditionalLogic();
- });
- container.addView(group);
- views.put(field.id, group);
- req.put(field.id, field.isRequired);
- }
-
- 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();
- }
- return false;
- });
- List labels = new ArrayList<>();
- labels.add("- Velg -");
- if (field.choices != null) {
- for (GravityField.Choice c : field.choices) labels.add(c.text);
- }
- ArrayAdapter adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, labels);
- spinner.setAdapter(adapter);
- container.addView(spinner);
- views.put(field.id, spinner);
- req.put(field.id, field.isRequired);
- }
-
- private void renderConsentField(LinearLayout container, GravityField field, Map views, Map req) {
- CheckBox checkBox = new CheckBox(getContext());
- String cbText = (field.checkboxLabel != null && !field.checkboxLabel.isEmpty()) ? field.checkboxLabel : field.label;
- checkBox.setText(cbText);
- String inputId = (field.inputs != null && !field.inputs.isEmpty()) ? field.inputs.get(0).id : field.id;
-
- checkBox.setTag("1");
- checkBox.setOnCheckedChangeListener((b, c) -> {
- expandFormModule();
- evaluateAllConditionalLogic();
- });
- container.addView(checkBox);
- views.put(inputId, checkBox);
- req.put(inputId, field.isRequired);
- }
-
- private void renderCheckboxField(LinearLayout container, GravityField field, Map views, Map req) {
- if (field.inputs != null) {
- for (int i = 0; i < field.inputs.size(); i++) {
- GravityField inputDef = field.inputs.get(i);
- CheckBox checkBox = new CheckBox(getContext());
- checkBox.setText(inputDef.label);
-
- String value = "1";
- // Fallback
- if (field.choices != null && i < field.choices.size()) {
- value = field.choices.get(i).value;
- }
- checkBox.setTag(value);
- checkBox.setOnCheckedChangeListener((b, c) -> {
- expandFormModule();
- evaluateAllConditionalLogic();
- });
- container.addView(checkBox);
- views.put(inputDef.id, checkBox);
- req.put(inputDef.id, false);
- }
- }
- }
-
- 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
- Calendar c = Calendar.getInstance();
- new DatePickerDialog(getContext(), (view, year, month, dayOfMonth) -> {
- dateInput.setText(String.format("%02d.%02d.%d", dayOfMonth, month + 1, year));
- evaluateAllConditionalLogic();
- }, c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH)).show();
- });
- }
-
- dateInput.setPadding(30, 30, 30, 30);
- dateInput.setBackgroundResource(android.R.drawable.edit_text);
-
- container.addView(dateInput);
- views.put(field.id, dateInput);
- req.put(field.id, field.isRequired);
- }
-
- private void renderCompositeField(LinearLayout container, GravityField parentField, Map views, Map req) {
- UserManager user = UserManager.getInstance();
- boolean isPersonalia = (formId == ID_ANSATTEOPPLYSNINGER);
-
- List inputs = new ArrayList<>(parentField.inputs);
- if ("address".equals(parentField.type)) {
- Collections.sort(inputs, (f1, f2) -> Integer.compare(getAddressScore(f1.label), getAddressScore(f2.label)));
- }
-
- for (GravityField subField : inputs) {
- if (subField.isHidden || "hidden".equals(subField.visibility)) continue;
- TextView subLabel = new TextView(getContext());
- String subLabelText = subField.label;
- boolean isSubRequired = parentField.isRequired;
- if ("address".equals(parentField.type) && subField.id.endsWith(".2")) {
- isSubRequired = false;
- }
- if (isSubRequired) subLabelText += " *";
-
- subLabel.setText(subLabelText);
- subLabel.setTextColor(Color.GRAY);
- subLabel.setTextSize(12);
- subLabel.setPadding(0, 10, 0, 0);
- container.addView(subLabel);
-
- EditText subInput = new EditText(getContext());
- subInput.setPadding(30, 30, 30, 30);
- subInput.setBackgroundResource(android.R.drawable.edit_text);
- subInput.setInputType(InputType.TYPE_CLASS_TEXT);
- if (isPersonalia && parentField.label.toLowerCase().contains("navn") && !parentField.label.toLowerCase().contains("pårørende")) {
- String lowerSub = subField.label.toLowerCase();
- if (lowerSub.contains("fornavn")) subInput.setText(user.getFirstName());
- else if (lowerSub.contains("etternavn")) subInput.setText(user.getLastName());
- }
-
- attachInteractionListener(subInput);
- container.addView(subInput);
- views.put(subField.id, subInput);
- req.put(subField.id, isSubRequired);
- }
- }
-
- private void addSectionHeader(LinearLayout container, String title, String descText) {
- TextView sectionHeader = new TextView(getContext());
- sectionHeader.setText(title);
- sectionHeader.setTextSize(18);
- sectionHeader.setTypeface(null, Typeface.BOLD);
- sectionHeader.setTextColor(Color.parseColor("#0069B3"));
- sectionHeader.setPadding(0, 20, 0, 5);
- container.addView(sectionHeader);
- if (descText != null && !descText.isEmpty()) {
- TextView desc = new TextView(getContext());
- desc.setText(Html.fromHtml(descText, Html.FROM_HTML_MODE_COMPACT));
- desc.setTextSize(12);
- desc.setTextColor(Color.GRAY);
- container.addView(desc);
- }
-
- View line = new View(getContext());
- line.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 2));
- line.setBackgroundColor(Color.LTGRAY);
- line.setPadding(0,0,0,20);
- container.addView(line);
- }
-
- private int getAddressScore(String label) {
- if (label == null) return 99;
- String l = label.toLowerCase();
- if (l.contains("adresselinje 1")) return 1;
- if (l.contains("adresselinje 2")) return 2;
- if (l.contains("postnummer") || l.contains("zip")) return 3;
- if (l.contains("poststed") || l.contains("city")) return 4;
- if (l.contains("land") || l.contains("country")) return 5;
- return 99;
- }
-
- private void evaluateAllConditionalLogic() {
- if (currentForm == null || currentForm.fields == null) return;
- for (GravityField field : currentForm.fields) {
- if (field.conditionalLogic == null) {
- setViewVisibility(field.id, true);
- continue;
- }
-
- boolean isMatch = evaluateLogic(field.conditionalLogic);
- boolean show = "show".equalsIgnoreCase(field.conditionalLogic.actionType);
- boolean shouldBeVisible = (show && isMatch) || (!show && !isMatch);
- setViewVisibility(field.id, shouldBeVisible);
- }
- }
-
- private boolean evaluateLogic(GravityField.ConditionalLogic logic) {
- if (logic.rules == null || logic.rules.isEmpty()) return true;
- boolean isAll = "all".equalsIgnoreCase(logic.logicType);
- boolean aggregatedResult = isAll;
-
- for (GravityField.Rule rule : logic.rules) {
- String val = getInputValue(rule.fieldId);
- boolean ruleMatch = checkRule(val, rule.operator, rule.value);
-
- if (isAll) {
- aggregatedResult = aggregatedResult && ruleMatch;
- if (!aggregatedResult) break;
- } else {
- aggregatedResult = aggregatedResult ||
- ruleMatch;
- if (aggregatedResult) break;
- }
- }
- return aggregatedResult;
- }
-
- private boolean checkRule(String actualValue, String operator, String targetValue) {
- if (actualValue == null) actualValue = "";
- if (targetValue == null) targetValue = "";
-
- switch (operator.toLowerCase()) {
- case "is": return actualValue.equalsIgnoreCase(targetValue);
- case "isnot": return !actualValue.equalsIgnoreCase(targetValue);
- case "contains": return actualValue.toLowerCase().contains(targetValue.toLowerCase());
- case "starts_with": return actualValue.toLowerCase().startsWith(targetValue.toLowerCase());
- case "ends_with": return actualValue.toLowerCase().endsWith(targetValue.toLowerCase());
- default: return false;
- }
- }
-
- private String getInputValue(String fieldId) {
- View view = inputViews.get(fieldId);
- return getInputValueGeneric(view);
- }
-
- private String getInputValueGeneric(View view) {
- if (view == null) return "";
- if (view instanceof EditText) return ((EditText) view).getText().toString();
- if (view instanceof RadioGroup) {
- int id = ((RadioGroup) view).getCheckedRadioButtonId();
- if (id != -1) {
- View rb = view.findViewById(id);
- if (rb != null && rb.getTag() != null) return rb.getTag().toString();
- }
- }
- if (view instanceof Spinner) {
- if (((Spinner) view).getSelectedItemPosition() == 0) return "";
- Object item = ((Spinner) view).getSelectedItem();
- return item != null ? item.toString() : "";
- }
- if (view instanceof CheckBox) {
- CheckBox cb = (CheckBox) view;
- if (cb.isChecked()) {
- return cb.getTag() != null ?
- cb.getTag().toString() : "1";
- }
- return "";
- }
- return "";
- }
-
- private void setViewVisibility(String fieldId, boolean visible) {
- View wrapper = fieldWrappers.get(fieldId);
- if (wrapper != null) {
- wrapper.setVisibility(visible ? View.VISIBLE : View.GONE);
- }
- }
-
- // --- SUBMISSION ---
-
- private void submitDynamicForm() {
- JSONObject inputValues = new JSONObject();
- boolean hasValues = false;
-
- Log.d(TAG, "submitDynamicForm: Starting validation...");
-
- for (Map.Entry entry : inputViews.entrySet()) {
- String fieldId = entry.getKey();
- View view = entry.getValue();
-
- View wrapper = fieldWrappers.get(fieldId);
- if (wrapper == null) {
- if (!view.isShown()) continue;
- } else {
- if (wrapper.getVisibility() != View.VISIBLE) continue;
- }
-
- String val = getInputValueGeneric(view);
- Boolean req = requiredFieldsMap.get(fieldId);
- if (req != null && req && val.isEmpty() && !(view instanceof Button)) {
- Log.d(TAG, "Validation failed for field " + fieldId);
- if (view instanceof EditText) {
- ((EditText)view).setError("Må fylles ut");
- view.requestFocus();
- } else {
- Toast.makeText(getContext(), "Fyll ut alle felt", Toast.LENGTH_SHORT).show();
- }
- return;
- }
- if (!val.isEmpty()) {
- try {
- GravityField fieldDef = getGravityFieldById(fieldId);
- if (fieldDef != null && "date".equals(fieldDef.type)) {
- val = formatDateForApi(val);
- }
-
- inputValues.put("input_" + fieldId, val);
- hasValues = true;
- } catch (JSONException e) {}
- }
- }
-
- if (!hasValues && fileUploads.isEmpty()) {
- Log.d(TAG, "Submit aborted: Form is empty");
- Toast.makeText(getContext(), "Skjemaet er tomt", Toast.LENGTH_SHORT).show();
- return;
- }
-
- updateStatus("Sender inn...");
- String cookie = UserManager.getInstance().getCookie();
- Log.d(TAG, "Preparing submission payload: " + inputValues.toString());
-
- if (!fileUploads.isEmpty()) {
- Log.d(TAG, "Submitting as Multipart...");
- sendMultipart(inputValues);
- } else {
- Log.d(TAG, "Submitting as JSON...");
- RequestBody body = RequestBody.create(MediaType.parse("application/json"), inputValues.toString());
- String url = BASE_URL_GF + "/forms/" + formId + "/submissions";
- Request request = new Request.Builder().url(url).post(body).header("Cookie", cookie).build();
- client.newCall(request).enqueue(new okhttp3.Callback() {
- public void onFailure(okhttp3.Call call, IOException e) {
- Log.e(TAG, "JSON submit failed", e);
- updateStatus("Feil: " + e.getMessage());
- }
- public void onResponse(okhttp3.Call call, Response response) {
- Log.d(TAG, "JSON response code: " + response.code());
- if (response.isSuccessful()) {
- if (getActivity() != null) getActivity().runOnUiThread(() -> {
- Toast.makeText(getContext(), "Sendt!", Toast.LENGTH_LONG).show();
- fetchFormEntries();
- updateStatus("OK");
- clearInputs();
- });
- } else {
- try {
- String errBody = response.body() != null ? response.body().string() : "No body";
- Log.e(TAG, "Server error body: " + errBody);
- updateStatus("Feil (" + response.code() + "): " + errBody);
- } catch(Exception e){}
- }
- }
- });
- }
- }
-
- private String formatDateForApi(String dateStr) {
- if (dateStr == null || dateStr.isEmpty()) return "";
- try {
- SimpleDateFormat displayFormat = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault());
- Date date = displayFormat.parse(dateStr);
- SimpleDateFormat apiFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
- return apiFormat.format(date);
- } catch (Exception e) {
- return dateStr;
- }
- }
-
- private GravityField getGravityFieldById(String id) {
- if (currentForm == null || currentForm.fields == null) return null;
- for (GravityField f : currentForm.fields) {
- if (f.id.equals(id)) return f;
- if (f.inputs != null) {
- for (GravityField sub : f.inputs) {
- if (sub.id.equals(id)) return sub;
- }
- }
- }
- return null;
- }
-
- private void sendMultipart(JSONObject inputValues) {
- List fileParts = new ArrayList<>();
- Map textParts = new HashMap<>();
- try {
- JSONArray names = inputValues.names();
- if (names != null) {
- for(int i=0; i entry : fileUploads.entrySet()) {
- MultipartBody.Part part = getFilePart("input_" + entry.getKey(), entry.getValue());
- if (part != null) fileParts.add(part);
- }
- RetrofitClient.getApiService().submitMultipartForm(formId, textParts, fileParts).enqueue(new retrofit2.Callback() {
- public void onResponse(retrofit2.Call call, retrofit2.Response response) {
- if (response.isSuccessful()) {
- if (getActivity() != null) getActivity().runOnUiThread(() -> {
- Toast.makeText(getContext(), "Sendt!", Toast.LENGTH_LONG).show();
- fetchFormEntries();
- updateStatus("OK");
- clearInputs();
- });
- } else {
- updateStatus("Feil: " + response.code());
- }
- }
- public void onFailure(retrofit2.Call call, Throwable t) { updateStatus("Feil: " + t.getMessage()); }
- });
- } catch (Exception e) {}
- }
-
- private MultipartBody.Part getFilePart(String partName, Uri uri) {
- try {
- InputStream inputStream = getContext().getContentResolver().openInputStream(uri);
- String fileName = getFileName(uri);
- RequestBody requestBody = new RequestBody() {
- @Override public MediaType contentType() { return MediaType.parse("application/octet-stream");
- }
- @Override public void writeTo(BufferedSink sink) throws IOException {
- try (Source source = Okio.source(inputStream)) { sink.writeAll(source);
- }
- }
- };
- return MultipartBody.Part.createFormData(partName, fileName, requestBody);
- } catch (Exception e) { return null;
- }
- }
-
- private void fetchFormEntries() {
- UserManager user = UserManager.getInstance();
- String cookie = user.getCookie();
- int userId = user.getUserId();
-
- if (cookie == null) return;
- String searchJson = "{\"field_filters\":[{\"key\":\"created_by\",\"value\":\"" + userId + "\"}]}";
- String encodedSearch = "";
- try {
- encodedSearch = URLEncoder.encode(searchJson, "UTF-8");
- } catch (UnsupportedEncodingException e) { e.printStackTrace(); }
-
- String url = BASE_URL_GF + "/entries?form_ids=" + formId + "&search=" + encodedSearch;
- Request request = new Request.Builder().url(url).header("Cookie", cookie).build();
-
- client.newCall(request).enqueue(new okhttp3.Callback() {
- @Override
- public void onFailure(@NonNull okhttp3.Call call, @NonNull IOException e) {
- Log.e(TAG, "Kunne ikke hente historikk", e);
- }
-
- @Override
- public void onResponse(@NonNull okhttp3.Call call, @NonNull Response response) throws IOException {
- if (response.isSuccessful()) {
- String jsonStr = response.body().string();
- try {
- JSONObject json = new JSONObject(jsonStr);
- if (json.has("entries")) {
- JSONArray entries = json.getJSONArray("entries");
- if (getActivity() != null) {
- getActivity().runOnUiThread(() -> {
- showHistory(entries);
- if (formId == ID_ANSATTEOPPLYSNINGER && entries.length() > 0)
- {
- try {
- prefillFormFromHistory(entries.getJSONObject(0));
- } catch (JSONException e) { e.printStackTrace(); }
- }
- });
- }
- }
- } catch (JSONException e) { e.printStackTrace();
- }
- }
- }
- });
- }
-
- private void showHistory(JSONArray entries) {
- if (historyContainer == null) return;
- historyContainer.removeAllViews();
-
- if (entries.length() == 0) {
- if (lblHistory != null) lblHistory.setVisibility(View.GONE);
- return;
- } else {
- if (lblHistory != null) lblHistory.setVisibility(View.VISIBLE);
- }
-
- 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));
- line.setBackgroundColor(Color.LTGRAY);
-
- historyContainer.addView(item);
- historyContainer.addView(line);
- }
- } catch (JSONException e) { e.printStackTrace();
- }
- }
-
- // 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
-
- 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; iInnsendt: ").append(date).append("
");
- text.append("Innsendt: ").append(date).append("\n\n");
-
- if (currentForm != null && currentForm.fields != null) {
- for (GravityField field : currentForm.fields) {
- if ("section".equals(field.type) || "html".equals(field.type) || "captcha".equals(field.type)) continue;
- // Hopp over felt 25 (Vedleggs-IDer) i Form 16, siden vi viser det bedre senere
- if (formId == ID_REFUSJON_UTLEGG && "25".equals(field.id)) continue;
-
- String value = "";
- if (field.inputs != null && !field.inputs.isEmpty()) {
- for (GravityField input : field.inputs) {
- String subVal = entry.optString(input.id);
- if (!subVal.isEmpty()) value += " " + subVal;
- }
- } else {
- value = entry.optString(String.valueOf(field.id));
- }
-
- if (!value.trim().isEmpty()) {
- if ("fileupload".equals(field.type)) {
- // Håndter filopplastinger (single/multiple)
- // Her antar vi enkel URL, men for sikkerhets skyld bruker vi extractUrl
- String cleanUrl = extractUrl(value);
- if (cleanUrl.startsWith("http")) {
- html.append("").append(field.label).append(": ")
- .append("Åpne fil