diff --git a/app/src/main/java/com/kbs/kbsintranett/FormsFragment.java b/app/src/main/java/com/kbs/kbsintranett/FormsFragment.java index 3ea6d3c..753c2d2 100644 --- a/app/src/main/java/com/kbs/kbsintranett/FormsFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/FormsFragment.java @@ -1,15 +1,19 @@ 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; @@ -18,12 +22,14 @@ 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; @@ -37,6 +43,8 @@ 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; @@ -46,6 +54,7 @@ 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; @@ -92,9 +101,11 @@ public class FormsFragment extends Fragment { 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; // NY: Knapp for å vise/skjule historikk // --- HOVEDSKJEMA STATE --- private Map fieldWrappers = new HashMap<>(); @@ -110,9 +121,15 @@ public class FormsFragment extends Fragment { 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(); @@ -131,8 +148,26 @@ public class FormsFragment extends Fragment { handleFileSelection(pendingFileFieldId, uri, isSelectingForChild); } } - pendingFileFieldId = null; - isSelectingForChild = false; + } + ); + 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(); + } } ); } @@ -143,10 +178,29 @@ public class FormsFragment extends Fragment { 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); + // --- 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) { + 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()); } @@ -161,6 +215,58 @@ public class FormsFragment extends Fragment { return view; } + // --- 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) + historyWrapper.setVisibility(View.GONE); + btnToggleHistory.setImageResource(android.R.drawable.arrow_down_float); + } else { + // Vis historikk (Gå til splitscreen) + 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 + }); + // Focus listener for edittexts etc + view.setOnFocusChangeListener((v, hasFocus) -> { + if (hasFocus) { + expandFormModule(); + } + }); + // Click listener for buttons/checkboxes + if (view.isClickable()) { + view.setOnClickListener(v -> expandFormModule()); + } + } + + // ---------------------------------- + private void fetchFormStructure() { if (loadingSpinner != null) loadingSpinner.setVisibility(View.VISIBLE); updateStatus("Laster skjema..."); @@ -202,6 +308,12 @@ public class FormsFragment extends Fragment { nestedEntries.clear(); updateStatus(""); + // Reset visibility of history on new load (Splitscreen start) + 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); @@ -219,7 +331,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; @@ -326,6 +437,7 @@ public class FormsFragment extends Fragment { btnAdd.setBackgroundColor(Color.parseColor("#53AFE9")); btnAdd.setTextColor(Color.WHITE); btnAdd.setOnClickListener(v -> { + expandFormModule(); // Trigger expand int childFormId = 18; if (field.gpnfForm != null) { try { @@ -358,7 +470,6 @@ 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) { @@ -380,8 +491,6 @@ public class FormsFragment extends Fragment { private void showChildFormDialog(GravityForm childForm) { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); - // FJERNET: builder.setTitle(childForm.title); - Brukeren ønsket ikke tittel i popup - childInputViews.clear(); childRequiredFieldsMap.clear(); childFileUploads.clear(); @@ -391,10 +500,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); @@ -405,7 +512,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)) { @@ -479,7 +585,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 { @@ -542,27 +647,20 @@ public class FormsFragment extends Fragment { } } - // --- FELLES METODER --- + // --- 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"); + btnUpload.setText("Velg fil / Ta bilde"); btnUpload.setOnClickListener(v -> { - if (filePickerLauncher == null) return; - try { - pendingFileFieldId = field.id; - isSelectingForChild = isChild; - 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); - } catch (Exception e) { - Toast.makeText(getContext(), "Kunne ikke åpne filvelger", Toast.LENGTH_SHORT).show(); - } + // 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"); @@ -578,6 +676,62 @@ public class FormsFragment extends Fragment { reqMap.put(field.id, field.isRequired); } + // Hjelpemetode for å vise dialog + private void showFileSourceDialog() { + String[] options = {"Ta bilde", "Velg fil"}; + new AlertDialog.Builder(getContext()) + .setTitle("Last opp vedlegg") + .setItems(options, (dialog, which) -> { + if (which == 0) { + // Ta bilde - SJEKKER PERMISSION FØRST + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) + == PackageManager.PERMISSION_GRANTED) { + openCamera(); + } else { + // Spør om lov + requestPermissionLauncher.launch(Manifest.permission.CAMERA); + } + } else { + // Velg fil + if (filePickerLauncher != null) { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + 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); @@ -631,6 +785,7 @@ public class FormsFragment extends Fragment { 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); @@ -671,6 +826,8 @@ 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); views.put(field.id, input); req.put(field.id, field.isRequired); @@ -682,6 +839,9 @@ public class FormsFragment extends Fragment { 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); @@ -694,10 +854,19 @@ public class FormsFragment extends Fragment { RadioButton rb = new RadioButton(getContext()); rb.setText(choice.text); rb.setTag(choice.value); + // Also trigger expand on RadioButton click + rb.setOnClickListener(v -> { + expandFormModule(); + evaluateAllConditionalLogic(); + }); group.addView(rb); } } - group.setOnCheckedChangeListener((g, i) -> evaluateAllConditionalLogic()); + // Fallback listener + group.setOnCheckedChangeListener((g, i) -> { + expandFormModule(); + evaluateAllConditionalLogic(); + }); container.addView(group); views.put(field.id, group); req.put(field.id, field.isRequired); @@ -705,6 +874,13 @@ public class FormsFragment extends Fragment { private void renderSelectField(LinearLayout container, GravityField field, Map views, Map req) { Spinner spinner = new Spinner(getContext()); + // Spinner touch listener is tricky, usually set onTouchListener works + spinner.setOnTouchListener((v, event) -> { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + expandFormModule(); + } + return false; + }); List labels = new ArrayList<>(); labels.add("- Velg -"); if (field.choices != null) { @@ -723,10 +899,11 @@ public class FormsFragment extends Fragment { checkBox.setText(cbText); String inputId = (field.inputs != null && !field.inputs.isEmpty()) ? field.inputs.get(0).id : field.id; - // For Consent fields, Gravity Forms typically expects "1" if checked. checkBox.setTag("1"); - - checkBox.setOnCheckedChangeListener((b, c) -> evaluateAllConditionalLogic()); + checkBox.setOnCheckedChangeListener((b, c) -> { + expandFormModule(); + evaluateAllConditionalLogic(); + }); container.addView(checkBox); views.put(inputId, checkBox); req.put(inputId, field.isRequired); @@ -739,17 +916,16 @@ public class FormsFragment extends Fragment { CheckBox checkBox = new CheckBox(getContext()); checkBox.setText(inputDef.label); - // --- VIKTIG ENDRING: HENT KORREKT VERDI --- - // Gravity Forms sjekkbokser trenger verdien fra "choices" listen, ikke bare "1". - // F.eks: "Ja" hvis valget er "Ja". - 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) -> evaluateAllConditionalLogic()); + checkBox.setOnCheckedChangeListener((b, c) -> { + expandFormModule(); + evaluateAllConditionalLogic(); + }); container.addView(checkBox); views.put(inputDef.id, checkBox); req.put(inputDef.id, false); @@ -776,6 +952,7 @@ public class FormsFragment extends Fragment { 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)); @@ -827,6 +1004,7 @@ public class FormsFragment extends Fragment { else if (lowerSub.contains("etternavn")) subInput.setText(user.getLastName()); } + attachInteractionListener(subInput); container.addView(subInput); views.put(subField.id, subInput); req.put(subField.id, isSubRequired); @@ -895,7 +1073,8 @@ public class FormsFragment extends Fragment { aggregatedResult = aggregatedResult && ruleMatch; if (!aggregatedResult) break; } else { - aggregatedResult = aggregatedResult || ruleMatch; + aggregatedResult = aggregatedResult || + ruleMatch; if (aggregatedResult) break; } } @@ -936,16 +1115,14 @@ public class FormsFragment extends Fragment { Object item = ((Spinner) view).getSelectedItem(); return item != null ? item.toString() : ""; } - // --- VIKTIG ENDRING: HENT TAG-VERDI --- if (view instanceof CheckBox) { CheckBox cb = (CheckBox) view; if (cb.isChecked()) { - // Returner verdien lagret i Tag (f.eks "Ja") i stedet for hardkodet "1" - return cb.getTag() != null ? cb.getTag().toString() : "1"; + return cb.getTag() != null ? + cb.getTag().toString() : "1"; } return ""; } - // ---------------------------------------- return ""; } @@ -989,7 +1166,6 @@ public class FormsFragment extends Fragment { } if (!val.isEmpty()) { try { - // --- DATO FORMATERING FOR API (Fix 400 Bad Request) --- GravityField fieldDef = getGravityFieldById(fieldId); if (fieldDef != null && "date".equals(fieldDef.type)) { val = formatDateForApi(val); @@ -1035,10 +1211,9 @@ public class FormsFragment extends Fragment { }); } else { try { - // --- BEDRE LOGGING FOR 400 FEIL --- String errBody = response.body() != null ? response.body().string() : "No body"; Log.e(TAG, "Server error body: " + errBody); - updateStatus("Feil (" + response.code() + "): " + errBody); // Viser feilmeldingen i UI + updateStatus("Feil (" + response.code() + "): " + errBody); } catch(Exception e){} } } @@ -1046,7 +1221,6 @@ public class FormsFragment extends Fragment { } } - // Konverterer dd.MM.yyyy -> yyyy-MM-dd private String formatDateForApi(String dateStr) { if (dateStr == null || dateStr.isEmpty()) return ""; try { @@ -1055,7 +1229,7 @@ public class FormsFragment extends Fragment { SimpleDateFormat apiFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); return apiFormat.format(date); } catch (Exception e) { - return dateStr; // Fallback hvis formatet er ukjent + return dateStr; } } @@ -1110,13 +1284,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() { @@ -1151,7 +1328,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(); } @@ -1159,7 +1337,8 @@ public class FormsFragment extends Fragment { }); } } - } catch (JSONException e) { e.printStackTrace(); } + } catch (JSONException e) { e.printStackTrace(); + } } } }); @@ -1177,14 +1356,21 @@ public class FormsFragment extends Fragment { } try { - int count = Math.min(entries.length(), 5); + // 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("Innsendt: " + date); + 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); @@ -1192,7 +1378,64 @@ 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 + 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") + + // 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; + 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; + } + } + } 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)"; + } + + details.append("").append(field.label).append(":
") + .append(value).append("

"); + } + } + } + + } catch (Exception e) { + details.append("Kunne ikke vise detaljer."); + } + + ScrollView scroll = new ScrollView(getContext()); + TextView text = new TextView(getContext()); + text.setText(Html.fromHtml(details.toString(), Html.FROM_HTML_MODE_COMPACT)); + text.setPadding(40, 40, 40, 40); + scroll.addView(text); + + new AlertDialog.Builder(getContext()) + .setTitle("Detaljer") + .setView(scroll) + .setPositiveButton("Lukk", null) + .show(); } private void prefillFormFromHistory(JSONObject latestEntry) { @@ -1239,8 +1482,6 @@ public class FormsFragment extends Fragment { } else if (view instanceof RadioGroup) { ((RadioGroup) view).clearCheck(); } else if (view instanceof Button) { - // Fix: CheckBox inherits from Button, so we must be careful. - // Only cast to TextView if the tag is actually a TextView (File Upload logic) Object tag = view.getTag(); if (tag instanceof TextView) { ((TextView) tag).setText("Ingen fil valgt"); @@ -1253,12 +1494,12 @@ public class FormsFragment extends Fragment { if (totalAmountView != null) totalAmountView.setText("Kr 0,00"); } - // --- HJELPEKLASSE FOR NESTED ENTRIES --- private static class NestedEntry { 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) { diff --git a/app/src/main/java/com/kbs/kbsintranett/FormsListFragment.java b/app/src/main/java/com/kbs/kbsintranett/FormsListFragment.java index a087cdc..741f003 100644 --- a/app/src/main/java/com/kbs/kbsintranett/FormsListFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/FormsListFragment.java @@ -2,194 +2,60 @@ package com.kbs.kbsintranett; import android.graphics.Color; import android.os.Bundle; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.LinearLayout; -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 java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - public class FormsListFragment extends Fragment { - private static final String TAG = "FormsListFragment"; - private LinearLayout container; - private ProgressBar loadingSpinner; - private TextView txtStatus; - - // Regex for å finne tall i starten av tittelen (f.eks "10. Tittel") - // Group 1 = Tallet - // Group 2 = Resten av teksten (den rene tittelen) - private static final Pattern TITLE_PATTERN = Pattern.compile("^(\\d+)[.\\s-]+\\s*(.*)"); - @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_forms, container, false); - this.container = view.findViewById(R.id.form_container); - this.loadingSpinner = view.findViewById(R.id.loading_spinner); - this.txtStatus = view.findViewById(R.id.txt_status); + View view = inflater.inflate(R.layout.fragment_forms_list, container, false); + LinearLayout formsContainer = view.findViewById(R.id.forms_container); - View historyTitle = view.findViewById(R.id.historyContainer); - if (historyTitle != null) historyTitle.setVisibility(View.GONE); - - LinearLayout mainLayout = view.findViewById(R.id.main_layout); - if (mainLayout != null && mainLayout.getChildCount() > 4) { - for(int i = 0; i < mainLayout.getChildCount(); i++) { - View child = mainLayout.getChildAt(i); - if (child instanceof TextView && ((TextView)child).getText().toString().contains("Tidligere")) { - child.setVisibility(View.GONE); - } - } - } + // 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); + addFormButton(formsContainer, "10. HMS-bekreftelse", 10); + addFormButton(formsContainer, "11. Egenmelding", 11); + addFormButton(formsContainer, "12. Sjekkliste for firmabil", 12); + addFormButton(formsContainer, "14. SJA (Sikker Jobbanalyse)", 14); + addFormButton(formsContainer, "15. Fraværsvarsel", 15); + addFormButton(formsContainer, "16. Refusjon utlegg", 16); + addFormButton(formsContainer, "21. Forberedelse til medarbeidersamtale", 21); + addFormButton(formsContainer, "22. Medarbeiderundersøkelse", 22); return view; } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - fetchFormsList(); - } - - private void fetchFormsList() { - if (container != null) container.removeAllViews(); - if (loadingSpinner != null) loadingSpinner.setVisibility(View.VISIBLE); - if (txtStatus != null) txtStatus.setText(""); - - // Vi bruker standard-kallet som er raskt og effektivt - RetrofitClient.getApiService().getFormsListMap().enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (getContext() == null) return; - if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE); - - if (response.isSuccessful() && response.body() != null) { - Map formMap = response.body(); - - if (formMap.isEmpty()) { - if (txtStatus != null) txtStatus.setText("Ingen skjemaer funnet."); - return; - } - - List visibleForms = new ArrayList<>(); - for (GravityForm form : formMap.values()) { - // Sjekk om aktiv - boolean isActive = form.isActive == null || !"0".equals(form.isActive); - - // Sjekk om vi finner et sorteringstall i tittelen - Integer sortOrder = getSortOrderFromTitle(form.title); - - if (form != null && form.title != null && isActive && sortOrder != null) { - visibleForms.add(form); - } - } - - if (visibleForms.isEmpty()) { - if (txtStatus != null) txtStatus.setText("Ingen tilgjengelige skjemaer (sjekk nummerering i titler)."); - return; - } - - // Sorter listen basert på tallet i tittelen - Collections.sort(visibleForms, new Comparator() { - @Override - public int compare(GravityForm f1, GravityForm f2) { - return Integer.compare(getSortOrderFromTitle(f1.title), getSortOrderFromTitle(f2.title)); - } - }); - - for (GravityForm form : visibleForms) { - addFormButton(form); - } - - } else { - if (txtStatus != null) txtStatus.setText("Kunne ikke laste listen (Kode " + response.code() + ")"); - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - if (getContext() == null) return; - if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE); - if (txtStatus != null) txtStatus.setText("Nettverksfeil: " + t.getMessage()); - } - }); - } - - /** - * Henter ut tallet fra starten av tittelen. - * Returnerer null hvis tittelen ikke starter med et tall. - */ - private Integer getSortOrderFromTitle(String title) { - if (title == null) return null; - Matcher m = TITLE_PATTERN.matcher(title.trim()); - if (m.find()) { - try { - return Integer.parseInt(m.group(1)); - } catch (NumberFormatException e) { - return null; - } - } - return null; // Ingen tall funnet -> Skjules - } - - /** - * Henter ut selve navnet på skjemaet (uten tallet foran). - * "10. Ansatte" -> "Ansatte" - */ - private String getCleanTitle(String title) { - if (title == null) return ""; - Matcher m = TITLE_PATTERN.matcher(title.trim()); - if (m.find()) { - // Group 2 er resten av teksten etter tallet og punktum/mellomrom - return m.group(2); - } - return title; // Fallback hvis regex ikke matcher (skal ikke skje pga filtreringen over) - } - - private void addFormButton(GravityForm form) { - Button button = new Button(getContext()); - - // VIS VASKET TITTEL PÅ KNAPPEN - String displayTitle = getCleanTitle(form.title); - button.setText(displayTitle.toUpperCase()); - - button.setTextColor(Color.WHITE); - button.setBackgroundColor(Color.parseColor("#0069B3")); // KBS Blå - button.setTextSize(14); - button.setPadding(30, 30, 30, 30); - - button.setOnClickListener(v -> { - Bundle bundle = new Bundle(); - bundle.putInt("formId", form.id); - Navigation.findNavController(v).navigate(R.id.action_formsListFragment_to_formsDetailFragment, bundle); - }); + private void addFormButton(LinearLayout container, String title, int formId) { + Button btn = new Button(getContext()); + btn.setText(title); + btn.setBackgroundColor(Color.parseColor("#0069B3")); // KBS Blå + btn.setTextColor(Color.WHITE); + btn.setPadding(30, 30, 30, 30); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ); - params.setMargins(0, 0, 0, 20); - button.setLayoutParams(params); + ViewGroup.LayoutParams.WRAP_CONTENT); + params.setMargins(0, 0, 0, 20); // Litt avstand mellom knappene + btn.setLayoutParams(params); - container.addView(button); + btn.setOnClickListener(v -> { + Bundle bundle = new Bundle(); + bundle.putInt("formId", formId); + // 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); } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_forms.xml b/app/src/main/res/layout/fragment_forms.xml index cf5a7b2..0190f49 100644 --- a/app/src/main/res/layout/fragment_forms.xml +++ b/app/src/main/res/layout/fragment_forms.xml @@ -1,74 +1,104 @@ - + android:orientation="vertical" + android:background="#F5F5F5" + tools:context=".FormsFragment"> - - - - + android:layout_height="0dp" + android:layout_weight="3" + android:orientation="vertical" + android:background="#FFFFFF" + android:elevation="4dp" + android:layout_marginBottom="8dp" + android:padding="16dp"> - - - - - - - - - + android:layout_height="match_parent"> + + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_forms_list.xml b/app/src/main/res/layout/fragment_forms_list.xml new file mode 100644 index 0000000..b9fc2e1 --- /dev/null +++ b/app/src/main/res/layout/fragment_forms_list.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..ed349d1 --- /dev/null +++ b/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/hele_prosjektet.txt b/hele_prosjektet.txt index 9ad2a5e..80f4c21 100644 --- a/hele_prosjektet.txt +++ b/hele_prosjektet.txt @@ -198,6 +198,17 @@ FILSTI: app\src\main\AndroidManifest.xml + + + + + @@ -437,16 +448,20 @@ 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; @@ -455,6 +470,7 @@ 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; @@ -474,6 +490,8 @@ 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; @@ -483,6 +501,7 @@ 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; @@ -529,6 +548,7 @@ public class FormsFragment extends Fragment { private LinearLayout formContainer; private LinearLayout historyContainer; + private View historyWrapper; // Wrapper for historikk-modulen som skal skjules private TextView txtStatus; private TextView lblHistory; private ProgressBar loadingSpinner; @@ -547,9 +567,15 @@ public class FormsFragment extends Fragment { 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(); @@ -559,6 +585,7 @@ public class FormsFragment extends Fragment { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + filePickerLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { @@ -568,8 +595,28 @@ public class FormsFragment extends Fragment { handleFileSelection(pendingFileFieldId, uri, isSelectingForChild); } } - pendingFileFieldId = null; - isSelectingForChild = false; + } + ); + + 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(); + } } ); } @@ -578,12 +625,26 @@ 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 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. + 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()); } @@ -598,6 +659,41 @@ public class FormsFragment extends Fragment { return view; } + // --- 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); + } + } + + 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 + }); + + // Focus listener for edittexts etc + view.setOnFocusChangeListener((v, hasFocus) -> { + if (hasFocus) { + expandFormModule(); + } + }); + + // Click listener for buttons/checkboxes + if (view.isClickable()) { + view.setOnClickListener(v -> expandFormModule()); + } + } + + // ---------------------------------- + private void fetchFormStructure() { if (loadingSpinner != null) loadingSpinner.setVisibility(View.VISIBLE); updateStatus("Laster skjema..."); @@ -639,6 +735,9 @@ public class FormsFragment extends Fragment { nestedEntries.clear(); updateStatus(""); + // Reset visibility of history on new load + if (historyWrapper != null) historyWrapper.setVisibility(View.VISIBLE); + TextView title = new TextView(getContext()); title.setText(getCleanTitle(form.title)); title.setTextSize(24); @@ -763,6 +862,7 @@ public class FormsFragment extends Fragment { btnAdd.setBackgroundColor(Color.parseColor("#53AFE9")); btnAdd.setTextColor(Color.WHITE); btnAdd.setOnClickListener(v -> { + expandFormModule(); // Trigger expand int childFormId = 18; if (field.gpnfForm != null) { try { @@ -817,7 +917,6 @@ public class FormsFragment extends Fragment { private void showChildFormDialog(GravityForm childForm) { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); - // FJERNET: builder.setTitle(childForm.title); - Brukeren ønsket ikke tittel i popup childInputViews.clear(); childRequiredFieldsMap.clear(); @@ -979,27 +1078,20 @@ public class FormsFragment extends Fragment { } } - // --- FELLES METODER --- + // --- 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"); + btnUpload.setText("Velg fil / Ta bilde"); btnUpload.setOnClickListener(v -> { - if (filePickerLauncher == null) return; - try { - pendingFileFieldId = field.id; - isSelectingForChild = isChild; - 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); - } catch (Exception e) { - Toast.makeText(getContext(), "Kunne ikke åpne filvelger", Toast.LENGTH_SHORT).show(); - } + // 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"); @@ -1015,6 +1107,63 @@ public class FormsFragment extends Fragment { reqMap.put(field.id, field.isRequired); } + // Hjelpemetode for å vise dialog + private void showFileSourceDialog() { + String[] options = {"Ta bilde", "Velg fil"}; + new AlertDialog.Builder(getContext()) + .setTitle("Last opp vedlegg") + .setItems(options, (dialog, which) -> { + if (which == 0) { + // Ta bilde - SJEKKER PERMISSION FØRST + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) + == PackageManager.PERMISSION_GRANTED) { + openCamera(); + } else { + // Spør om lov + requestPermissionLauncher.launch(Manifest.permission.CAMERA); + } + } else { + // Velg fil + if (filePickerLauncher != null) { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + 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); @@ -1068,6 +1217,7 @@ public class FormsFragment extends Fragment { 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); @@ -1108,6 +1258,9 @@ 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); views.put(field.id, input); req.put(field.id, field.isRequired); @@ -1119,6 +1272,9 @@ public class FormsFragment extends Fragment { 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); @@ -1131,10 +1287,20 @@ public class FormsFragment extends Fragment { RadioButton rb = new RadioButton(getContext()); rb.setText(choice.text); rb.setTag(choice.value); + // Also trigger expand on RadioButton click + rb.setOnClickListener(v -> { + expandFormModule(); + evaluateAllConditionalLogic(); + }); group.addView(rb); } } - group.setOnCheckedChangeListener((g, i) -> evaluateAllConditionalLogic()); + // Fallback listener + group.setOnCheckedChangeListener((g, i) -> { + expandFormModule(); + evaluateAllConditionalLogic(); + }); + container.addView(group); views.put(field.id, group); req.put(field.id, field.isRequired); @@ -1142,6 +1308,14 @@ public class FormsFragment extends Fragment { private void renderSelectField(LinearLayout container, GravityField field, Map views, Map req) { Spinner spinner = new Spinner(getContext()); + // Spinner touch listener is tricky, usually set onTouchListener works + spinner.setOnTouchListener((v, event) -> { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + expandFormModule(); + } + return false; + }); + List labels = new ArrayList<>(); labels.add("- Velg -"); if (field.choices != null) { @@ -1160,10 +1334,13 @@ public class FormsFragment extends Fragment { checkBox.setText(cbText); String inputId = (field.inputs != null && !field.inputs.isEmpty()) ? field.inputs.get(0).id : field.id; - // For Consent fields, Gravity Forms typically expects "1" if checked. checkBox.setTag("1"); - checkBox.setOnCheckedChangeListener((b, c) -> evaluateAllConditionalLogic()); + checkBox.setOnCheckedChangeListener((b, c) -> { + expandFormModule(); + evaluateAllConditionalLogic(); + }); + container.addView(checkBox); views.put(inputId, checkBox); req.put(inputId, field.isRequired); @@ -1176,17 +1353,16 @@ public class FormsFragment extends Fragment { CheckBox checkBox = new CheckBox(getContext()); checkBox.setText(inputDef.label); - // --- VIKTIG ENDRING: HENT KORREKT VERDI --- - // Gravity Forms sjekkbokser trenger verdien fra "choices" listen, ikke bare "1". - // F.eks: "Ja" hvis valget er "Ja". 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) -> evaluateAllConditionalLogic()); + checkBox.setOnCheckedChangeListener((b, c) -> { + expandFormModule(); + evaluateAllConditionalLogic(); + }); container.addView(checkBox); views.put(inputDef.id, checkBox); req.put(inputDef.id, false); @@ -1213,6 +1389,7 @@ public class FormsFragment extends Fragment { 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)); @@ -1264,6 +1441,8 @@ public class FormsFragment extends Fragment { else if (lowerSub.contains("etternavn")) subInput.setText(user.getLastName()); } + attachInteractionListener(subInput); + container.addView(subInput); views.put(subField.id, subInput); req.put(subField.id, isSubRequired); @@ -1373,16 +1552,13 @@ public class FormsFragment extends Fragment { Object item = ((Spinner) view).getSelectedItem(); return item != null ? item.toString() : ""; } - // --- VIKTIG ENDRING: HENT TAG-VERDI --- if (view instanceof CheckBox) { CheckBox cb = (CheckBox) view; if (cb.isChecked()) { - // Returner verdien lagret i Tag (f.eks "Ja") i stedet for hardkodet "1" return cb.getTag() != null ? cb.getTag().toString() : "1"; } return ""; } - // ---------------------------------------- return ""; } @@ -1426,7 +1602,6 @@ public class FormsFragment extends Fragment { } if (!val.isEmpty()) { try { - // --- DATO FORMATERING FOR API (Fix 400 Bad Request) --- GravityField fieldDef = getGravityFieldById(fieldId); if (fieldDef != null && "date".equals(fieldDef.type)) { val = formatDateForApi(val); @@ -1472,10 +1647,9 @@ public class FormsFragment extends Fragment { }); } else { try { - // --- BEDRE LOGGING FOR 400 FEIL --- String errBody = response.body() != null ? response.body().string() : "No body"; Log.e(TAG, "Server error body: " + errBody); - updateStatus("Feil (" + response.code() + "): " + errBody); // Viser feilmeldingen i UI + updateStatus("Feil (" + response.code() + "): " + errBody); } catch(Exception e){} } } @@ -1483,7 +1657,6 @@ public class FormsFragment extends Fragment { } } - // Konverterer dd.MM.yyyy -> yyyy-MM-dd private String formatDateForApi(String dateStr) { if (dateStr == null || dateStr.isEmpty()) return ""; try { @@ -1492,7 +1665,7 @@ public class FormsFragment extends Fragment { SimpleDateFormat apiFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); return apiFormat.format(date); } catch (Exception e) { - return dateStr; // Fallback hvis formatet er ukjent + return dateStr; } } @@ -1614,14 +1787,24 @@ public class FormsFragment extends Fragment { } try { - int count = Math.min(entries.length(), 5); + // 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("Innsendt: " + date); + 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); @@ -1632,6 +1815,64 @@ public class FormsFragment extends Fragment { } catch (JSONException e) { e.printStackTrace(); } } + // NY METODE: Vis detaljer i dialog + 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") + + // 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; + + 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; + } + } + } 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)"; + } + + details.append("").append(field.label).append(":
") + .append(value).append("

"); + } + } + } + + } catch (Exception e) { + details.append("Kunne ikke vise detaljer."); + } + + ScrollView scroll = new ScrollView(getContext()); + TextView text = new TextView(getContext()); + text.setText(Html.fromHtml(details.toString(), Html.FROM_HTML_MODE_COMPACT)); + text.setPadding(40, 40, 40, 40); + scroll.addView(text); + + new AlertDialog.Builder(getContext()) + .setTitle("Detaljer") + .setView(scroll) + .setPositiveButton("Lukk", null) + .show(); + } + private void prefillFormFromHistory(JSONObject latestEntry) { if (latestEntry == null) return; for (Map.Entry entry : inputViews.entrySet()) { @@ -1676,8 +1917,6 @@ public class FormsFragment extends Fragment { } else if (view instanceof RadioGroup) { ((RadioGroup) view).clearCheck(); } else if (view instanceof Button) { - // Fix: CheckBox inherits from Button, so we must be careful. - // Only cast to TextView if the tag is actually a TextView (File Upload logic) Object tag = view.getTag(); if (tag instanceof TextView) { ((TextView) tag).setText("Ingen fil valgt"); @@ -1690,7 +1929,6 @@ public class FormsFragment extends Fragment { if (totalAmountView != null) totalAmountView.setText("Kr 0,00"); } - // --- HJELPEKLASSE FOR NESTED ENTRIES --- private static class NestedEntry { String id; String description; @@ -1712,195 +1950,59 @@ package com.kbs.kbsintranett; import android.graphics.Color; import android.os.Bundle; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.LinearLayout; -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 java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - public class FormsListFragment extends Fragment { - private static final String TAG = "FormsListFragment"; - private LinearLayout container; - private ProgressBar loadingSpinner; - private TextView txtStatus; - - // Regex for å finne tall i starten av tittelen (f.eks "10. Tittel") - // Group 1 = Tallet - // Group 2 = Resten av teksten (den rene tittelen) - private static final Pattern TITLE_PATTERN = Pattern.compile("^(\\d+)[.\\s-]+\\s*(.*)"); - @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_forms, container, false); - this.container = view.findViewById(R.id.form_container); - this.loadingSpinner = view.findViewById(R.id.loading_spinner); - this.txtStatus = view.findViewById(R.id.txt_status); + View view = inflater.inflate(R.layout.fragment_forms_list, container, false); + LinearLayout formsContainer = view.findViewById(R.id.forms_container); - View historyTitle = view.findViewById(R.id.historyContainer); - if (historyTitle != null) historyTitle.setVisibility(View.GONE); - - LinearLayout mainLayout = view.findViewById(R.id.main_layout); - if (mainLayout != null && mainLayout.getChildCount() > 4) { - for(int i = 0; i < mainLayout.getChildCount(); i++) { - View child = mainLayout.getChildAt(i); - if (child instanceof TextView && ((TextView)child).getText().toString().contains("Tidligere")) { - child.setVisibility(View.GONE); - } - } - } + addFormButton(formsContainer, "1. Ansatteopplysninger", 1); + addFormButton(formsContainer, "4. RUH (Rapport om uønsket hendelse)", 4); + addFormButton(formsContainer, "9. Sikkerhetskurs / Kompetansebevis", 9); + addFormButton(formsContainer, "10. HMS-bekreftelse", 10); + addFormButton(formsContainer, "11. Egenmelding", 11); + addFormButton(formsContainer, "12. Sjekkliste for firmabil", 12); + addFormButton(formsContainer, "14. SJA (Sikker Jobbanalyse)", 14); + addFormButton(formsContainer, "15. Fraværsvarsel", 15); + addFormButton(formsContainer, "16. Refusjon utlegg", 16); + addFormButton(formsContainer, "21. Forberedelse til medarbeidersamtale", 21); + addFormButton(formsContainer, "22. Medarbeiderundersøkelse", 22); return view; } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - fetchFormsList(); - } - - private void fetchFormsList() { - if (container != null) container.removeAllViews(); - if (loadingSpinner != null) loadingSpinner.setVisibility(View.VISIBLE); - if (txtStatus != null) txtStatus.setText(""); - - // Vi bruker standard-kallet som er raskt og effektivt - RetrofitClient.getApiService().getFormsListMap().enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (getContext() == null) return; - if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE); - - if (response.isSuccessful() && response.body() != null) { - Map formMap = response.body(); - - if (formMap.isEmpty()) { - if (txtStatus != null) txtStatus.setText("Ingen skjemaer funnet."); - return; - } - - List visibleForms = new ArrayList<>(); - for (GravityForm form : formMap.values()) { - // Sjekk om aktiv - boolean isActive = form.isActive == null || !"0".equals(form.isActive); - - // Sjekk om vi finner et sorteringstall i tittelen - Integer sortOrder = getSortOrderFromTitle(form.title); - - if (form != null && form.title != null && isActive && sortOrder != null) { - visibleForms.add(form); - } - } - - if (visibleForms.isEmpty()) { - if (txtStatus != null) txtStatus.setText("Ingen tilgjengelige skjemaer (sjekk nummerering i titler)."); - return; - } - - // Sorter listen basert på tallet i tittelen - Collections.sort(visibleForms, new Comparator() { - @Override - public int compare(GravityForm f1, GravityForm f2) { - return Integer.compare(getSortOrderFromTitle(f1.title), getSortOrderFromTitle(f2.title)); - } - }); - - for (GravityForm form : visibleForms) { - addFormButton(form); - } - - } else { - if (txtStatus != null) txtStatus.setText("Kunne ikke laste listen (Kode " + response.code() + ")"); - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - if (getContext() == null) return; - if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE); - if (txtStatus != null) txtStatus.setText("Nettverksfeil: " + t.getMessage()); - } - }); - } - - /** - * Henter ut tallet fra starten av tittelen. - * Returnerer null hvis tittelen ikke starter med et tall. - */ - private Integer getSortOrderFromTitle(String title) { - if (title == null) return null; - Matcher m = TITLE_PATTERN.matcher(title.trim()); - if (m.find()) { - try { - return Integer.parseInt(m.group(1)); - } catch (NumberFormatException e) { - return null; - } - } - return null; // Ingen tall funnet -> Skjules - } - - /** - * Henter ut selve navnet på skjemaet (uten tallet foran). - * "10. Ansatte" -> "Ansatte" - */ - private String getCleanTitle(String title) { - if (title == null) return ""; - Matcher m = TITLE_PATTERN.matcher(title.trim()); - if (m.find()) { - // Group 2 er resten av teksten etter tallet og punktum/mellomrom - return m.group(2); - } - return title; // Fallback hvis regex ikke matcher (skal ikke skje pga filtreringen over) - } - - private void addFormButton(GravityForm form) { - Button button = new Button(getContext()); - - // VIS VASKET TITTEL PÅ KNAPPEN - String displayTitle = getCleanTitle(form.title); - button.setText(displayTitle.toUpperCase()); - - button.setTextColor(Color.WHITE); - button.setBackgroundColor(Color.parseColor("#0069B3")); // KBS Blå - button.setTextSize(14); - button.setPadding(30, 30, 30, 30); - - button.setOnClickListener(v -> { - Bundle bundle = new Bundle(); - bundle.putInt("formId", form.id); - Navigation.findNavController(v).navigate(R.id.action_formsListFragment_to_formsDetailFragment, bundle); - }); + private void addFormButton(LinearLayout container, String title, int formId) { + Button btn = new Button(getContext()); + btn.setText(title); + btn.setBackgroundColor(Color.parseColor("#0069B3")); // KBS Blå + btn.setTextColor(Color.WHITE); + btn.setPadding(30, 30, 30, 30); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ); - params.setMargins(0, 0, 0, 20); - button.setLayoutParams(params); + ViewGroup.LayoutParams.WRAP_CONTENT); + params.setMargins(0, 0, 0, 20); // Litt avstand mellom knappene + btn.setLayoutParams(params); - container.addView(button); + btn.setOnClickListener(v -> { + Bundle bundle = new Bundle(); + bundle.putInt("formId", formId); + Navigation.findNavController(v).navigate(R.id.nav_forms, bundle); + }); + + container.addView(btn); } } @@ -3212,79 +3314,130 @@ FILSTI: app\src\main\res\layout\activity_main.xml FILSTI: app\src\main\res\layout\fragment_forms.xml ============================================================ - + android:orientation="vertical" + android:background="#F5F5F5" + tools:context=".FormsFragment"> - - - - + android:layout_height="0dp" + android:layout_weight="3" + android:orientation="vertical" + android:background="#FFFFFF" + android:elevation="4dp" + android:layout_marginBottom="8dp" + android:padding="16dp"> - - - - - - + + + + + + + + + + + + + + + + + + + + + + + +============================================================ +FILSTI: app\src\main\res\layout\fragment_forms_list.xml +============================================================ + + + + + + + + + - - - - + ============================================================ FILSTI: app\src\main\res\layout\fragment_handbook.xml @@ -3836,6 +3989,14 @@ FILSTI: app\src\main\res\xml\data_extraction_rules.xml --> +============================================================ +FILSTI: app\src\main\res\xml\file_paths.xml +============================================================ + + + + + ============================================================ FILSTI: app\src\test\java\com\kbs\kbsintranett\ExampleUnitTest.java ============================================================