From 0c33b4fc0ff7247f3a93bea6e6977c131075ca17 Mon Sep 17 00:00:00 2001 From: ErolHaagenrud Date: Thu, 11 Dec 2025 13:37:05 +0100 Subject: [PATCH] =?UTF-8?q?F=C3=B8r=20kalenderarbeid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/kbs/kbsintranett/FormsFragment.java | 272 ++++++++++--- .../kbs/kbsintranett/WordPressApiService.java | 9 +- hele_prosjektet.txt | 380 ++++++++++++++---- 3 files changed, 528 insertions(+), 133 deletions(-) diff --git a/app/src/main/java/com/kbs/kbsintranett/FormsFragment.java b/app/src/main/java/com/kbs/kbsintranett/FormsFragment.java index 753c2d2..3692db6 100644 --- a/app/src/main/java/com/kbs/kbsintranett/FormsFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/FormsFragment.java @@ -49,6 +49,8 @@ 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; @@ -71,7 +73,6 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import okhttp3.Call; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.OkHttpClient; @@ -105,7 +106,7 @@ public class FormsFragment extends Fragment { private TextView txtStatus; private TextView lblHistory; private ProgressBar loadingSpinner; - private ImageView btnToggleHistory; // NY: Knapp for å vise/skjule historikk + private ImageView btnToggleHistory; // --- HOVEDSKJEMA STATE --- private Map fieldWrappers = new HashMap<>(); @@ -183,12 +184,10 @@ public class FormsFragment extends Fragment { lblHistory = view.findViewById(R.id.lbl_history); loadingSpinner = view.findViewById(R.id.loading_spinner); - // --- NY KODE START: Vis/Skjul knapp --- btnToggleHistory = view.findViewById(R.id.btn_toggle_history); if (btnToggleHistory != null) { btnToggleHistory.setOnClickListener(v -> toggleHistoryVisibility()); } - // --- NY KODE SLUTT --- // --- FIKS FOR NULLPOINTER EXCEPTION PÅ LAYOUTTRANSITION --- if (view instanceof ViewGroup) { @@ -217,28 +216,24 @@ public class FormsFragment extends Fragment { // --- UI LOGIKK FOR DELT SKJERM --- - // Kalles når brukeren interagerer med et felt i skjemaet private void expandFormModule() { if (historyWrapper != null && historyWrapper.getVisibility() == View.VISIBLE) { historyWrapper.setVisibility(View.GONE); - - // Oppdater ikonet til å peke NED (for å vise at man kan hente historikk ned igjen) if (btnToggleHistory != null) { btnToggleHistory.setImageResource(android.R.drawable.arrow_down_float); } } } - // NY METODE: Håndterer klikk på pil-knappen private void toggleHistoryVisibility() { if (historyWrapper == null || btnToggleHistory == null) return; if (historyWrapper.getVisibility() == View.VISIBLE) { - // Skjul historikk (Gå til fullskjerm) + // Skjul historikk historyWrapper.setVisibility(View.GONE); btnToggleHistory.setImageResource(android.R.drawable.arrow_down_float); } else { - // Vis historikk (Gå til splitscreen) + // Vis historikk historyWrapper.setVisibility(View.VISIBLE); btnToggleHistory.setImageResource(android.R.drawable.arrow_up_float); } @@ -246,20 +241,17 @@ public class FormsFragment extends Fragment { private void attachInteractionListener(View view) { if (view == null) return; - // Touch listener fanger opp klikk før tastaturet kommer opp view.setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_DOWN) { expandFormModule(); } - return false; // Return false to allow normal processing + return false; }); - // Focus listener for edittexts etc view.setOnFocusChangeListener((v, hasFocus) -> { if (hasFocus) { expandFormModule(); } }); - // Click listener for buttons/checkboxes if (view.isClickable()) { view.setOnClickListener(v -> expandFormModule()); } @@ -308,7 +300,7 @@ public class FormsFragment extends Fragment { nestedEntries.clear(); updateStatus(""); - // Reset visibility of history on new load (Splitscreen start) + // 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); @@ -444,7 +436,8 @@ public class FormsFragment extends Fragment { childFormId = Integer.parseInt(field.gpnfForm); } catch (NumberFormatException e) { e.printStackTrace(); } } - openChildFormDialog(childFormId); + // NYTT: Sender med felt-ID fra hovedskjemaet (f.eks "25") + openChildFormDialog(childFormId, field.id); }); container.addView(btnAdd); @@ -462,7 +455,8 @@ public class FormsFragment extends Fragment { container.addView(totalAmountView); } - private void openChildFormDialog(int childFormId) { + // 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()) @@ -475,7 +469,7 @@ public class FormsFragment extends Fragment { public void onResponse(retrofit2.Call call, retrofit2.Response response) { loadingDialog.dismiss(); if (response.isSuccessful() && response.body() != null) { - showChildFormDialog(response.body()); + showChildFormDialog(response.body(), parentFieldId); } else { Toast.makeText(getContext(), "Kunne ikke hente underskjema", Toast.LENGTH_SHORT).show(); } @@ -489,7 +483,8 @@ public class FormsFragment extends Fragment { }); } - private void showChildFormDialog(GravityForm childForm) { + // NY SIGNATUR: Tar imot parentFieldId + private void showChildFormDialog(GravityForm childForm, String parentFieldId) { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); childInputViews.clear(); childRequiredFieldsMap.clear(); @@ -534,11 +529,12 @@ public class FormsFragment extends Fragment { dialog.show(); dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> { - submitChildForm(childForm.id, dialog); + submitChildForm(childForm.id, dialog, parentFieldId); }); } - private void submitChildForm(int childFormId, AlertDialog dialog) { + // 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()); @@ -562,6 +558,15 @@ public class FormsFragment extends Fragment { 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(); @@ -1196,11 +1201,11 @@ public class FormsFragment extends Fragment { 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(Call call, IOException e) { + public void onFailure(okhttp3.Call call, IOException e) { Log.e(TAG, "JSON submit failed", e); updateStatus("Feil: " + e.getMessage()); } - public void onResponse(Call call, Response response) { + public void onResponse(okhttp3.Call call, Response response) { Log.d(TAG, "JSON response code: " + response.code()); if (response.isSuccessful()) { if (getActivity() != null) getActivity().runOnUiThread(() -> { @@ -1313,12 +1318,12 @@ public class FormsFragment extends Fragment { client.newCall(request).enqueue(new okhttp3.Callback() { @Override - public void onFailure(@NonNull Call call, @NonNull IOException e) { + public void onFailure(@NonNull okhttp3.Call call, @NonNull IOException e) { Log.e(TAG, "Kunne ikke hente historikk", e); } @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { + public void onResponse(@NonNull okhttp3.Call call, @NonNull Response response) throws IOException { if (response.isSuccessful()) { String jsonStr = response.body().string(); try { @@ -1382,52 +1387,205 @@ public class FormsFragment extends Fragment { } } - // NY METODE: Vis detaljer i dialog + // NY METODE: Vis detaljer i dialog med delingsknapp private void showEntryDetails(JSONObject entry) { - StringBuilder details = new StringBuilder(); - try { - // Loop gjennom alle felter i entry og match med skjema-definisjon - // Merk: Entry keys er felt-IDer (f.eks "1", "3.2") + // 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"); - // Dato - details.append("Innsendt: ").append(entry.optString("date_created")).append("

"); - // Iterer gjennom feltene i skjema-definisjonen for å få riktig rekkefølge 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()) { - // Composite fields (Address, Name) for (GravityField input : field.inputs) { String subVal = entry.optString(input.id); - if (!subVal.isEmpty()) { - value += " " + subVal; - } + if (!subVal.isEmpty()) value += " " + subVal; } } else { - // Standard field value = entry.optString(String.valueOf(field.id)); } if (!value.trim().isEmpty()) { - // For filopplastinger er verdien ofte en URL. if ("fileupload".equals(field.type)) { - value = "(Vedlegg)"; + // 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

"); + text.append(field.label).append(":\n").append(cleanUrl).append("\n\n"); + } else { + html.append("").append(field.label).append(":
").append(value).append("

"); + text.append(field.label).append(":\n").append(value).append("\n\n"); + } + } else { + html.append("").append(field.label).append(":
").append(value).append("

"); + text.append(field.label).append(":\n").append(value).append("\n\n"); } - - details.append("").append(field.label).append(":
") - .append(value).append("

"); } } } + } catch (Exception e) {} + } - } catch (Exception e) { - details.append("Kunne ikke vise detaljer."); + // 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() : ""; + + html.append("Vedlegg ").append(index + 1).append(":
"); + text.append("Vedlegg ").append(index + 1).append(":\n"); + + 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("\\/", "/"); + html.append("Åpne fil ").append(i+1).append("
"); + 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 { + JSONArray arr = new JSONArray(rawString); + for (int i = 0; i < arr.length(); i++) { + String url = arr.getString(i).replace("\\/", "/"); + html.append("Åpne fil ").append(i+1).append("
"); + 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
"); + text.append(clean).append("\n"); + } + } + } + } + html.append("
"); + text.append("\n"); + + } 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); + } + }); + } + + private void showFinalDialog(StringBuilder htmlBuilder, StringBuilder textBuilder) { ScrollView scroll = new ScrollView(getContext()); TextView text = new TextView(getContext()); - text.setText(Html.fromHtml(details.toString(), Html.FROM_HTML_MODE_COMPACT)); + text.setMovementMethod(android.text.method.LinkMovementMethod.getInstance()); + text.setText(Html.fromHtml(htmlBuilder.toString(), Html.FROM_HTML_MODE_COMPACT)); text.setPadding(40, 40, 40, 40); scroll.addView(text); @@ -1435,9 +1593,31 @@ public class FormsFragment extends Fragment { .setTitle("Detaljer") .setView(scroll) .setPositiveButton("Lukk", null) + .setNeutralButton("Del", (d, w) -> shareEntryDetails(textBuilder.toString())) + .create() .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("[", "") + .replace("]", "") + .replace("\"", "") + .replace("\\/", "/"); + return clean.trim(); + } + + private void shareEntryDetails(String text) { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, text); + sendIntent.setType("text/plain"); + + Intent shareIntent = Intent.createChooser(sendIntent, "Del innsending via..."); + startActivity(shareIntent); + } + private void prefillFormFromHistory(JSONObject latestEntry) { if (latestEntry == null) return; for (Map.Entry entry : inputViews.entrySet()) { diff --git a/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java b/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java index ca274b5..514b60a 100644 --- a/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java +++ b/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java @@ -1,4 +1,3 @@ -// FILSTI: app\src\main\java\com\kbs\kbsintranett\WordPressApiService.java package com.kbs.kbsintranett; import com.google.gson.JsonElement; @@ -14,7 +13,7 @@ import retrofit2.http.Path; import retrofit2.http.Multipart; import retrofit2.http.Part; import retrofit2.http.PartMap; -import retrofit2.http.Query; // NYTT +import retrofit2.http.Query; public interface WordPressApiService { // 1. Hent nyheter @@ -50,11 +49,15 @@ public interface WordPressApiService { @GET("wp-json/kbs/v1/calendar/events") Call> getCalendarEvents(); - // 8. HENT INNSENDINGER (Entries) - NYTT + // 8. HENT INNSENDINGER (Entries) - LISTE @GET("wp-json/gf/v2/entries") Call getEntries( @Query("form_ids") int formId, @Query("search") String searchJson, @Query("paging[page_size]") int pageSize ); + + // 9. HENT ÉN ENKELT INNSENDING (Ny! Brukes for å hente detaljer fra underskjema) + @GET("wp-json/gf/v2/entries/{entry_id}") + Call getSingleEntry(@Path("entry_id") String entryId); } \ No newline at end of file diff --git a/hele_prosjektet.txt b/hele_prosjektet.txt index 80f4c21..21a2fc1 100644 --- a/hele_prosjektet.txt +++ b/hele_prosjektet.txt @@ -477,6 +477,7 @@ 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; @@ -496,6 +497,8 @@ 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; @@ -518,7 +521,6 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import okhttp3.Call; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.OkHttpClient; @@ -548,10 +550,11 @@ public class FormsFragment extends Fragment { private LinearLayout formContainer; private LinearLayout historyContainer; - private View historyWrapper; // Wrapper for historikk-modulen som skal skjules + 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<>(); @@ -585,7 +588,6 @@ public class FormsFragment extends Fragment { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - filePickerLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { @@ -597,7 +599,6 @@ public class FormsFragment extends Fragment { } } ); - takePictureLauncher = registerForActivityResult( new ActivityResultContracts.TakePicture(), success -> { @@ -608,7 +609,6 @@ public class FormsFragment extends Fragment { } } ); - requestPermissionLauncher = registerForActivityResult( new ActivityResultContracts.RequestPermission(), isGranted -> { @@ -625,16 +625,19 @@ public class FormsFragment extends Fragment { @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); // Ny referanse + 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); - // --- FIKS FOR NULLPOINTER EXCEPTION --- - // Vi sjekker om LayoutTransition finnes, hvis ikke lager vi en ny. + 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) { @@ -643,7 +646,7 @@ public class FormsFragment extends Fragment { } transition.enableTransitionType(LayoutTransition.CHANGING); } - // -------------------------------------- + // ---------------------------------------------------------- if (formContainer == null) { formContainer = new LinearLayout(getContext()); @@ -661,32 +664,42 @@ public class FormsFragment extends Fragment { // --- UI LOGIKK FOR DELT SKJERM --- - // Kalles når brukeren interagerer med et felt i skjemaet 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; - - // Touch listener fanger opp klikk før tastaturet kommer opp view.setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_DOWN) { expandFormModule(); } - return false; // Return false to allow normal processing + return false; }); - - // Focus listener for edittexts etc view.setOnFocusChangeListener((v, hasFocus) -> { if (hasFocus) { expandFormModule(); } }); - - // Click listener for buttons/checkboxes if (view.isClickable()) { view.setOnClickListener(v -> expandFormModule()); } @@ -736,7 +749,10 @@ public class FormsFragment extends Fragment { updateStatus(""); // Reset visibility of history on new load - if (historyWrapper != null) historyWrapper.setVisibility(View.VISIBLE); + 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)); @@ -755,7 +771,6 @@ public class FormsFragment extends Fragment { } if (form.fields == null) return; - for (GravityField field : form.fields) { if ("hidden".equals(field.type) || field.isHidden || "hidden".equals(field.visibility)) { continue; @@ -869,7 +884,8 @@ public class FormsFragment extends Fragment { childFormId = Integer.parseInt(field.gpnfForm); } catch (NumberFormatException e) { e.printStackTrace(); } } - openChildFormDialog(childFormId); + // NYTT: Sender med felt-ID fra hovedskjemaet (f.eks "25") + openChildFormDialog(childFormId, field.id); }); container.addView(btnAdd); @@ -887,7 +903,8 @@ public class FormsFragment extends Fragment { container.addView(totalAmountView); } - private void openChildFormDialog(int childFormId) { + // 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()) @@ -895,13 +912,12 @@ public class FormsFragment extends Fragment { .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()); + showChildFormDialog(response.body(), parentFieldId); } else { Toast.makeText(getContext(), "Kunne ikke hente underskjema", Toast.LENGTH_SHORT).show(); } @@ -915,9 +931,9 @@ public class FormsFragment extends Fragment { }); } - private void showChildFormDialog(GravityForm childForm) { + // 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(); @@ -927,10 +943,8 @@ public class FormsFragment extends Fragment { 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); @@ -941,7 +955,6 @@ public class FormsFragment extends Fragment { 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)) { @@ -964,11 +977,12 @@ public class FormsFragment extends Fragment { dialog.show(); dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> { - submitChildForm(childForm.id, dialog); + submitChildForm(childForm.id, dialog, parentFieldId); }); } - private void submitChildForm(int childFormId, AlertDialog dialog) { + // 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()); @@ -992,6 +1006,15 @@ public class FormsFragment extends Fragment { 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(); @@ -1015,7 +1038,6 @@ public class FormsFragment extends Fragment { // 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 { @@ -1156,7 +1178,6 @@ public class FormsFragment extends Fragment { 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(); @@ -1258,7 +1279,6 @@ 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 container.addView(input); @@ -1300,7 +1320,6 @@ public class FormsFragment extends Fragment { expandFormModule(); evaluateAllConditionalLogic(); }); - container.addView(group); views.put(field.id, group); req.put(field.id, field.isRequired); @@ -1315,7 +1334,6 @@ public class FormsFragment extends Fragment { } return false; }); - List labels = new ArrayList<>(); labels.add("- Velg -"); if (field.choices != null) { @@ -1335,12 +1353,10 @@ public class FormsFragment extends Fragment { 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); @@ -1353,12 +1369,12 @@ public class FormsFragment extends Fragment { CheckBox checkBox = new CheckBox(getContext()); checkBox.setText(inputDef.label); - String value = "1"; // Fallback + 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(); @@ -1442,7 +1458,6 @@ public class FormsFragment extends Fragment { } attachInteractionListener(subInput); - container.addView(subInput); views.put(subField.id, subInput); req.put(subField.id, isSubRequired); @@ -1511,7 +1526,8 @@ public class FormsFragment extends Fragment { aggregatedResult = aggregatedResult && ruleMatch; if (!aggregatedResult) break; } else { - aggregatedResult = aggregatedResult || ruleMatch; + aggregatedResult = aggregatedResult || + ruleMatch; if (aggregatedResult) break; } } @@ -1555,7 +1571,8 @@ public class FormsFragment extends Fragment { if (view instanceof CheckBox) { CheckBox cb = (CheckBox) view; if (cb.isChecked()) { - return cb.getTag() != null ? cb.getTag().toString() : "1"; + return cb.getTag() != null ? + cb.getTag().toString() : "1"; } return ""; } @@ -1632,11 +1649,11 @@ public class FormsFragment extends Fragment { 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(Call call, IOException e) { + public void onFailure(okhttp3.Call call, IOException e) { Log.e(TAG, "JSON submit failed", e); updateStatus("Feil: " + e.getMessage()); } - public void onResponse(Call call, Response response) { + public void onResponse(okhttp3.Call call, Response response) { Log.d(TAG, "JSON response code: " + response.code()); if (response.isSuccessful()) { if (getActivity() != null) getActivity().runOnUiThread(() -> { @@ -1720,13 +1737,16 @@ public class FormsFragment extends Fragment { 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 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); } + try (Source source = Okio.source(inputStream)) { sink.writeAll(source); + } } }; return MultipartBody.Part.createFormData(partName, fileName, requestBody); - } catch (Exception e) { return null; } + } catch (Exception e) { return null; + } } private void fetchFormEntries() { @@ -1746,12 +1766,12 @@ public class FormsFragment extends Fragment { client.newCall(request).enqueue(new okhttp3.Callback() { @Override - public void onFailure(@NonNull Call call, @NonNull IOException e) { + public void onFailure(@NonNull okhttp3.Call call, @NonNull IOException e) { Log.e(TAG, "Kunne ikke hente historikk", e); } @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { + public void onResponse(@NonNull okhttp3.Call call, @NonNull Response response) throws IOException { if (response.isSuccessful()) { String jsonStr = response.body().string(); try { @@ -1761,7 +1781,8 @@ public class FormsFragment extends Fragment { if (getActivity() != null) { getActivity().runOnUiThread(() -> { showHistory(entries); - if (formId == ID_ANSATTEOPPLYSNINGER && entries.length() > 0) { + if (formId == ID_ANSATTEOPPLYSNINGER && entries.length() > 0) + { try { prefillFormFromHistory(entries.getJSONObject(0)); } catch (JSONException e) { e.printStackTrace(); } @@ -1769,7 +1790,8 @@ public class FormsFragment extends Fragment { }); } } - } catch (JSONException e) { e.printStackTrace(); } + } catch (JSONException e) { e.printStackTrace(); + } } } }); @@ -1795,16 +1817,13 @@ public class FormsFragment extends Fragment { // 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); @@ -1812,57 +1831,209 @@ public class FormsFragment extends Fragment { historyContainer.addView(item); historyContainer.addView(line); } - } catch (JSONException e) { e.printStackTrace(); } + } catch (JSONException e) { e.printStackTrace(); + } } - // NY METODE: Vis detaljer i dialog + // NY METODE: Vis detaljer i dialog med delingsknapp private void showEntryDetails(JSONObject entry) { - StringBuilder details = new StringBuilder(); + // 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"); - // Dato - details.append("Innsendt: ").append(entry.optString("date_created")).append("

"); - - // Iterer gjennom feltene i skjema-definisjonen for å få riktig rekkefølge 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()) { - // Composite fields (Address, Name) for (GravityField input : field.inputs) { String subVal = entry.optString(input.id); - if (!subVal.isEmpty()) { - value += " " + subVal; - } + if (!subVal.isEmpty()) value += " " + subVal; } } else { - // Standard field value = entry.optString(String.valueOf(field.id)); } if (!value.trim().isEmpty()) { - // For filopplastinger er verdien ofte en URL. if ("fileupload".equals(field.type)) { - value = "(Vedlegg)"; + // 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

"); + text.append(field.label).append(":\n").append(cleanUrl).append("\n\n"); + } else { + html.append("").append(field.label).append(":
").append(value).append("

"); + text.append(field.label).append(":\n").append(value).append("\n\n"); + } + } else { + html.append("").append(field.label).append(":
").append(value).append("

"); + text.append(field.label).append(":\n").append(value).append("\n\n"); } - - details.append("").append(field.label).append(":
") - .append(value).append("

"); } } } + } catch (Exception e) {} + } - } catch (Exception e) { - details.append("Kunne ikke vise detaljer."); + // 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() : ""; + + html.append("Vedlegg ").append(index + 1).append(":
"); + text.append("Vedlegg ").append(index + 1).append(":\n"); + + 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("\\/", "/"); + html.append("Åpne fil ").append(i+1).append("
"); + 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 { + JSONArray arr = new JSONArray(rawString); + for (int i = 0; i < arr.length(); i++) { + String url = arr.getString(i).replace("\\/", "/"); + html.append("Åpne fil ").append(i+1).append("
"); + 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
"); + text.append(clean).append("\n"); + } + } + } + } + html.append("
"); + text.append("\n"); + + } 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); + } + }); + } + + private void showFinalDialog(StringBuilder htmlBuilder, StringBuilder textBuilder) { ScrollView scroll = new ScrollView(getContext()); TextView text = new TextView(getContext()); - text.setText(Html.fromHtml(details.toString(), Html.FROM_HTML_MODE_COMPACT)); + text.setMovementMethod(android.text.method.LinkMovementMethod.getInstance()); + text.setText(Html.fromHtml(htmlBuilder.toString(), Html.FROM_HTML_MODE_COMPACT)); text.setPadding(40, 40, 40, 40); scroll.addView(text); @@ -1870,9 +2041,31 @@ public class FormsFragment extends Fragment { .setTitle("Detaljer") .setView(scroll) .setPositiveButton("Lukk", null) + .setNeutralButton("Del", (d, w) -> shareEntryDetails(textBuilder.toString())) + .create() .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("[", "") + .replace("]", "") + .replace("\"", "") + .replace("\\/", "/"); + return clean.trim(); + } + + private void shareEntryDetails(String text) { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, text); + sendIntent.setType("text/plain"); + + Intent shareIntent = Intent.createChooser(sendIntent, "Del innsending via..."); + startActivity(shareIntent); + } + private void prefillFormFromHistory(JSONObject latestEntry) { if (latestEntry == null) return; for (Map.Entry entry : inputViews.entrySet()) { @@ -1933,7 +2126,8 @@ public class FormsFragment extends Fragment { String id; String description; String price; - NestedEntry(String id, String d, String p) { this.id = id; this.description = d; this.price = p; } + NestedEntry(String id, String d, String p) { this.id = id; this.description = d; this.price = p; + } } private void updateStatus(String msg) { @@ -1968,6 +2162,7 @@ public class FormsListFragment extends Fragment { View view = inflater.inflate(R.layout.fragment_forms_list, container, false); LinearLayout formsContainer = view.findViewById(R.id.forms_container); + // Legger til knappene for de ulike skjemaene addFormButton(formsContainer, "1. Ansatteopplysninger", 1); addFormButton(formsContainer, "4. RUH (Rapport om uønsket hendelse)", 4); addFormButton(formsContainer, "9. Sikkerhetskurs / Kompetansebevis", 9); @@ -1999,7 +2194,8 @@ public class FormsListFragment extends Fragment { btn.setOnClickListener(v -> { Bundle bundle = new Bundle(); bundle.putInt("formId", formId); - Navigation.findNavController(v).navigate(R.id.nav_forms, bundle); + // HER VAR FEILEN: Endret R.id.nav_forms til riktig action ID + Navigation.findNavController(v).navigate(R.id.action_formsListFragment_to_formsDetailFragment, bundle); }); container.addView(btn); @@ -3034,7 +3230,6 @@ public class UserManager { ============================================================ FILSTI: app\src\main\java\com\kbs\kbsintranett\WordPressApiService.java ============================================================ -// FILSTI: app\src\main\java\com\kbs\kbsintranett\WordPressApiService.java package com.kbs.kbsintranett; import com.google.gson.JsonElement; @@ -3050,7 +3245,7 @@ import retrofit2.http.Path; import retrofit2.http.Multipart; import retrofit2.http.Part; import retrofit2.http.PartMap; -import retrofit2.http.Query; // NYTT +import retrofit2.http.Query; public interface WordPressApiService { // 1. Hent nyheter @@ -3086,13 +3281,17 @@ public interface WordPressApiService { @GET("wp-json/kbs/v1/calendar/events") Call> getCalendarEvents(); - // 8. HENT INNSENDINGER (Entries) - NYTT + // 8. HENT INNSENDINGER (Entries) - LISTE @GET("wp-json/gf/v2/entries") Call getEntries( @Query("form_ids") int formId, @Query("search") String searchJson, @Query("paging[page_size]") int pageSize ); + + // 9. HENT ÉN ENKELT INNSENDING (Ny! Brukes for å hente detaljer fra underskjema) + @GET("wp-json/gf/v2/entries/{entry_id}") + Call getSingleEntry(@Path("entry_id") String entryId); } ============================================================ @@ -3316,6 +3515,7 @@ FILSTI: app\src\main\res\layout\fragment_forms.xml + android:textSize="12sp" + android:text="" /> + +