Før drastisk endring i FormsListFragment.java

This commit is contained in:
ErolHaagenrud 2025-12-10 11:42:25 +01:00
parent 56f14e9b92
commit 00028f10e5
4 changed files with 563 additions and 189 deletions

View file

@ -1,18 +1,26 @@
package com.kbs.kbsintranett;
import android.app.DatePickerDialog;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;
import android.text.Editable;
import android.text.Html;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
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.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
@ -28,8 +36,8 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -47,8 +55,10 @@ public class FormsFragment extends Fragment {
private static final String TAG = "FormsFragment";
private static final String BASE_URL_GF = "https://intranet.kbs.no/wp-json/gf/v2";
// ID 1 = Ansatteopplysninger (Skal ha autofyll fra historikk)
// SKJEMA ID-er
private static final int ID_ANSATTEOPPLYSNINGER = 1;
private static final int ID_HMS_BEKREFTELSE = 10;
private static final int ID_EGENMELDING = 11;
private int formId = 1;
@ -58,25 +68,27 @@ public class FormsFragment extends Fragment {
private TextView lblHistory; // Overskriften "Tidligere innsendinger"
private ProgressBar loadingSpinner;
private Map<String, EditText> dynamicFields = new HashMap<>();
// Vi lagrer View-objektene for å kunne toggle synlighet (Conditional Logic)
// Key = Field ID (f.eks "2", "7.1")
private Map<String, View> fieldWrappers = new HashMap<>(); // Beholderen (Label + Input)
private Map<String, View> inputViews = new HashMap<>(); // Selve input-feltet (EditText, RadioGroup, etc)
private Map<String, Boolean> requiredFieldsMap = new HashMap<>();
private GravityForm currentForm; // Holder referanse til skjemaet for logikk-sjekk
private final OkHttpClient client = new OkHttpClient();
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_forms, container, false);
formContainer = view.findViewById(R.id.form_container);
historyContainer = view.findViewById(R.id.historyContainer);
txtStatus = view.findViewById(R.id.txt_status);
lblHistory = view.findViewById(R.id.lbl_history);
loadingSpinner = view.findViewById(R.id.loading_spinner);
// Fallback hvis XML feiler
if (formContainer == null) {
// Hvis brukeren ikke har oppdatert XML ennå, unngå krasj
formContainer = new LinearLayout(getContext());
}
@ -99,11 +111,11 @@ public class FormsFragment extends Fragment {
@Override
public void onResponse(retrofit2.Call<GravityForm> call, retrofit2.Response<GravityForm> response) {
if (response.isSuccessful() && response.body() != null) {
GravityForm form = response.body();
currentForm = response.body();
if (getActivity() != null) {
getActivity().runOnUiThread(() -> {
if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE);
renderDynamicForm(form);
renderDynamicForm(currentForm);
fetchFormEntries();
});
}
@ -124,7 +136,8 @@ public class FormsFragment extends Fragment {
private void renderDynamicForm(GravityForm form) {
if (formContainer == null) return;
formContainer.removeAllViews();
dynamicFields.clear();
fieldWrappers.clear();
inputViews.clear();
requiredFieldsMap.clear();
updateStatus("");
@ -146,80 +159,109 @@ public class FormsFragment extends Fragment {
if (form.fields == null) return;
UserManager user = UserManager.getInstance();
boolean isPersonaliaSection = true;
boolean hasFilledName = false;
boolean hasFilledEmail = false;
boolean hasFilledPhone = false;
boolean hasFilledJob = false;
// Bygg feltene
for (GravityField field : form.fields) {
if ("hidden".equals(field.type)) continue;
if (field.isHidden) continue;
if ("hidden".equals(field.visibility)) continue;
if ("section".equals(field.type)) {
String labelLower = field.label.toLowerCase();
isPersonaliaSection = labelLower.contains("personalia");
addSectionHeader(field.label, field.description);
// Ignorerte felttyper
if ("hidden".equals(field.type) || field.isHidden || "hidden".equals(field.visibility)) {
continue;
}
if ("html".equals(field.type)) {
if (field.content != null && field.content.toLowerCase().contains("pårørende")) {
isPersonaliaSection = false;
// Wrapper for hele feltet (Label + Input + Description)
LinearLayout fieldWrapper = new LinearLayout(getContext());
fieldWrapper.setOrientation(LinearLayout.VERTICAL);
fieldWrapper.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
fieldWrapper.setPadding(0, 10, 0, 20);
// Lagre referanse til wrapperen basert Felt-ID
fieldWrappers.put(String.valueOf(field.id), fieldWrapper);
// 1. Seksjoner (Overskrifter)
if ("section".equals(field.type)) {
addSectionHeader(fieldWrapper, field.label, field.description);
formContainer.addView(fieldWrapper);
continue;
}
// 2. HTML-blokker
if ("html".equals(field.type)) {
if (field.content != null && !field.content.isEmpty()) {
TextView htmlView = new TextView(getContext());
htmlView.setText(Html.fromHtml(field.content, Html.FROM_HTML_MODE_COMPACT));
htmlView.setPadding(0, 40, 0, 10);
formContainer.addView(htmlView);
fieldWrapper.addView(htmlView);
}
formContainer.addView(fieldWrapper);
continue;
}
if (field.inputs != null && !field.inputs.isEmpty()) {
renderCompositeField(field, isPersonaliaSection);
continue;
}
// ENKELTFELT
// 3. Label (Overskrift for feltet)
TextView label = new TextView(getContext());
String labelText = field.label;
if (field.isRequired) labelText += " *";
label.setText(labelText);
label.setTextColor(Color.DKGRAY);
label.setPadding(0, 25, 0, 5);
formContainer.addView(label);
label.setTypeface(null, Typeface.BOLD);
label.setPadding(0, 10, 0, 5);
fieldWrapper.addView(label);
// 4. Input-felt basert type
if (field.inputs != null && !field.inputs.isEmpty()) {
// Sammensatte felt (Navn, Adresse, Checkbox med inputs)
if ("consent".equals(field.type)) {
renderConsentField(fieldWrapper, field);
} else if ("checkbox".equals(field.type)) {
renderCheckboxField(fieldWrapper, field);
} else {
renderCompositeField(fieldWrapper, field);
}
} else if ("radio".equals(field.type)) {
renderRadioField(fieldWrapper, field);
} else if ("select".equals(field.type)) {
renderSelectField(fieldWrapper, field);
} else if ("textarea".equals(field.type)) {
renderTextAreaField(fieldWrapper, field);
} else if ("date".equals(field.type)) {
renderDateField(fieldWrapper, field);
} else if ("consent".equals(field.type)) {
renderConsentField(fieldWrapper, field);
} else {
// Standard tekst, e-post, telefon, nummer
renderTextField(fieldWrapper, field);
}
// 5. Beskrivelse (hvis finnes)
if (field.description != null && !field.description.isEmpty()) {
TextView desc = new TextView(getContext());
desc.setText(Html.fromHtml(field.description, Html.FROM_HTML_MODE_COMPACT));
desc.setTextSize(12);
desc.setTextColor(Color.GRAY);
fieldWrapper.addView(desc);
}
formContainer.addView(fieldWrapper);
}
// SEND-KNAPP
Button dynamicSubmit = new Button(getContext());
dynamicSubmit.setText("Send inn skjema");
dynamicSubmit.setTextColor(Color.WHITE);
dynamicSubmit.setBackgroundColor(Color.parseColor("#0069B3")); // KBS Blå
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(0, 60, 0, 20);
dynamicSubmit.setLayoutParams(params);
dynamicSubmit.setOnClickListener(v -> submitDynamicForm());
formContainer.addView(dynamicSubmit);
// Etter at alt er tegnet, kjør en initial sjekk logikken
evaluateAllConditionalLogic();
}
// --- RENDER METHODS ---
private void renderTextField(LinearLayout container, GravityField field) {
EditText input = new EditText(getContext());
input.setPadding(30, 30, 30, 30);
input.setBackgroundResource(android.R.drawable.edit_text);
// Autofyll fra UserManager (Standardinfo som alltid er greit å fylle ut)
if (isPersonaliaSection) {
String lowerLabel = field.label.toLowerCase();
if (!hasFilledName && (lowerLabel.equals("navn") || lowerLabel.equals("navn *"))) {
input.setText(user.getUserDisplayName());
hasFilledName = true;
}
else if (!hasFilledEmail && lowerLabel.contains("e-post") && lowerLabel.contains("arbeid")) {
input.setText(user.getUserEmail());
hasFilledEmail = true;
}
else if (!hasFilledJob && lowerLabel.contains("stilling")) {
input.setText(user.getStilling());
hasFilledJob = true;
}
else if (!hasFilledPhone && (lowerLabel.contains("mobil") || lowerLabel.equals("mobiltelefon"))) {
input.setText(user.getMobiltelefon());
hasFilledPhone = true;
}
}
if ("number".equals(field.type) || "phone".equals(field.type)) {
input.setInputType(InputType.TYPE_CLASS_PHONE);
} else if ("email".equals(field.type)) {
@ -228,69 +270,211 @@ public class FormsFragment extends Fragment {
input.setInputType(InputType.TYPE_CLASS_TEXT);
}
formContainer.addView(input);
if (field.description != null && !field.description.isEmpty()) {
addDescription(field.description);
}
dynamicFields.put(String.valueOf(field.id), input);
requiredFieldsMap.put(String.valueOf(field.id), field.isRequired);
}
// SEND-KNAPP
Button dynamicSubmit = new Button(getContext());
dynamicSubmit.setText("Send inn skjema");
dynamicSubmit.setTextColor(Color.WHITE);
dynamicSubmit.setBackgroundColor(Color.parseColor("#0069B3")); // KBS Blå
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
params.setMargins(0, 60, 0, 20);
dynamicSubmit.setLayoutParams(params);
dynamicSubmit.setOnClickListener(v -> submitDynamicForm());
formContainer.addView(dynamicSubmit);
}
private void renderCompositeField(GravityField parentField, boolean isPersonaliaSection) {
TextView groupLabel = new TextView(getContext());
String groupText = parentField.label;
if (parentField.isRequired) groupText += " *";
groupLabel.setText(groupText);
groupLabel.setTextColor(Color.DKGRAY);
groupLabel.setTypeface(null, Typeface.BOLD);
groupLabel.setPadding(0, 30, 0, 5);
formContainer.addView(groupLabel);
UserManager user = UserManager.getInstance();
String lowerParentLabel = parentField.label.toLowerCase();
String lowerLabel = field.label.toLowerCase();
List<GravityField> inputs = new ArrayList<>(parentField.inputs);
// Autofyll Logikk
if (formId == ID_ANSATTEOPPLYSNINGER) {
if (lowerLabel.contains("e-post") && lowerLabel.contains("arbeid")) {
input.setText(user.getUserEmail());
} else if (lowerLabel.contains("stilling")) {
input.setText(user.getStilling());
} else if ((lowerLabel.contains("mobil") || lowerLabel.equals("mobiltelefon"))) {
input.setText(user.getMobiltelefon());
}
}
else if (formId == ID_HMS_BEKREFTELSE) {
if (lowerLabel.contains("e-post")) {
input.setText(user.getUserEmail());
}
}
else if (formId == ID_EGENMELDING) {
if (lowerLabel.contains("navn")) {
input.setText(user.getUserDisplayName());
} else if (lowerLabel.contains("stilling")) {
input.setText(user.getStilling());
} else if (lowerLabel.contains("e-post")) {
input.setText(user.getUserEmail());
}
}
// Sortering for adresse
if (parentField.type.equals("address")) {
Collections.sort(inputs, new Comparator<GravityField>() {
// Lytter for å trigge Conditional Logic når tekst endres
input.addTextChangedListener(new TextWatcher() {
@Override
public int compare(GravityField f1, GravityField f2) {
int s1 = getAddressScore(f1.label);
int s2 = getAddressScore(f2.label);
return Integer.compare(s1, s2);
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
evaluateAllConditionalLogic();
}
});
container.addView(input);
inputViews.put(field.id, input);
requiredFieldsMap.put(field.id, field.isRequired);
}
private void renderTextAreaField(LinearLayout container, GravityField field) {
EditText input = new EditText(getContext());
input.setPadding(30, 30, 30, 30);
input.setBackgroundResource(android.R.drawable.edit_text);
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
input.setMinLines(3);
input.setGravity(android.view.Gravity.TOP | android.view.Gravity.START);
input.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
evaluateAllConditionalLogic();
}
});
container.addView(input);
inputViews.put(field.id, input);
requiredFieldsMap.put(field.id, field.isRequired);
}
private void renderRadioField(LinearLayout container, GravityField field) {
RadioGroup group = new RadioGroup(getContext());
group.setOrientation(RadioGroup.VERTICAL);
if (field.choices != null) {
for (GravityField.Choice choice : field.choices) {
RadioButton rb = new RadioButton(getContext());
rb.setText(choice.text);
rb.setTag(choice.value);
group.addView(rb);
}
}
// Lytter for Conditional Logic
group.setOnCheckedChangeListener((group1, checkedId) -> evaluateAllConditionalLogic());
container.addView(group);
inputViews.put(field.id, group);
requiredFieldsMap.put(field.id, field.isRequired);
}
private void renderSelectField(LinearLayout container, GravityField field) {
Spinner spinner = new Spinner(getContext());
List<String> labels = new ArrayList<>();
labels.add("- Velg -");
if (field.choices != null) {
for (GravityField.Choice c : field.choices) {
labels.add(c.text);
}
}
ArrayAdapter<String> adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, labels);
spinner.setAdapter(adapter);
container.addView(spinner);
inputViews.put(field.id, spinner);
requiredFieldsMap.put(field.id, field.isRequired);
}
private void renderConsentField(LinearLayout container, GravityField field) {
CheckBox checkBox = new CheckBox(getContext());
String cbText = (field.checkboxLabel != null && !field.checkboxLabel.isEmpty())
? field.checkboxLabel
: field.label;
checkBox.setText(cbText);
checkBox.setTextSize(16);
checkBox.setPadding(10, 10, 10, 10);
String inputId = field.id;
if (field.inputs != null && !field.inputs.isEmpty()) {
inputId = field.inputs.get(0).id;
}
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> evaluateAllConditionalLogic());
container.addView(checkBox);
inputViews.put(inputId, checkBox);
requiredFieldsMap.put(inputId, field.isRequired);
}
private void renderCheckboxField(LinearLayout container, GravityField field) {
// I Gravity Forms er checkboxes en liste av inputs hvis det er flere valg.
// Hvis det bare er ett valg (som "Jeg bekrefter..."), er det ofte én input.
// Sjekk om det er inputs
if (field.inputs != null) {
for (GravityField inputDef : field.inputs) {
CheckBox checkBox = new CheckBox(getContext());
checkBox.setText(inputDef.label);
checkBox.setTextSize(16);
checkBox.setPadding(10, 10, 10, 10);
// Verdien som sendes er vanligvis etiketten hvis den er huket av.
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> evaluateAllConditionalLogic());
container.addView(checkBox);
inputViews.put(inputDef.id, checkBox);
// Checkboxer er sjeldent individuelt påkrevd i en gruppe, men selve feltet er.
// Vi markerer den spesifikke boksen som påkrevd hvis feltet er det og det bare er én.
if (field.inputs.size() == 1) {
requiredFieldsMap.put(inputDef.id, field.isRequired);
} else {
// Hvis det er flere, er validering mer komplekst (minst én velges).
// Foreløpig enkel implementasjon.
requiredFieldsMap.put(inputDef.id, false);
}
}
}
}
private void renderDateField(LinearLayout container, GravityField field) {
EditText dateInput = new EditText(getContext());
dateInput.setPadding(30, 30, 30, 30);
dateInput.setBackgroundResource(android.R.drawable.edit_text);
dateInput.setFocusable(false); // Hindre tastatur
dateInput.setClickable(true); // Tillat klikk
dateInput.setHint("dd.mm.yyyy");
dateInput.setOnClickListener(v -> {
final Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
DatePickerDialog datePickerDialog = new DatePickerDialog(getContext(),
(view, year1, monthOfYear, dayOfMonth) -> {
// Format: dd.MM.yyyy
String selectedDate = String.format(java.util.Locale.getDefault(), "%02d.%02d.%d", dayOfMonth, monthOfYear + 1, year1);
dateInput.setText(selectedDate);
evaluateAllConditionalLogic();
}, year, month, day);
datePickerDialog.show();
});
container.addView(dateInput);
inputViews.put(field.id, dateInput);
requiredFieldsMap.put(field.id, field.isRequired);
}
private void renderCompositeField(LinearLayout container, GravityField parentField) {
UserManager user = UserManager.getInstance();
boolean isPersonalia = (formId == ID_ANSATTEOPPLYSNINGER);
List<GravityField> inputs = new ArrayList<>(parentField.inputs);
if ("address".equals(parentField.type)) {
Collections.sort(inputs, (f1, f2) -> Integer.compare(getAddressScore(f1.label), getAddressScore(f2.label)));
}
for (GravityField subField : inputs) {
if (subField.isHidden) continue;
if ("hidden".equals(subField.visibility)) continue;
if (subField.isHidden || "hidden".equals(subField.visibility)) continue;
TextView subLabel = new TextView(getContext());
String subLabelText = subField.label;
boolean isSubRequired = parentField.isRequired;
if (parentField.type.equals("address") && subField.id.endsWith(".2")) {
if ("address".equals(parentField.type) && subField.id.endsWith(".2")) {
isSubRequired = false;
}
if (isSubRequired) subLabelText += " *";
@ -299,32 +483,47 @@ public class FormsFragment extends Fragment {
subLabel.setTextColor(Color.GRAY);
subLabel.setTextSize(12);
subLabel.setPadding(0, 10, 0, 0);
formContainer.addView(subLabel);
container.addView(subLabel);
EditText subInput = new EditText(getContext());
subInput.setPadding(30, 30, 30, 30);
subInput.setBackgroundResource(android.R.drawable.edit_text);
subInput.setInputType(InputType.TYPE_CLASS_TEXT);
if (isPersonaliaSection && lowerParentLabel.contains("navn") && !lowerParentLabel.contains("pårørende")) {
String lowerSubLabel = subField.label.toLowerCase();
if (lowerSubLabel.contains("fornavn")) {
subInput.setText(user.getFirstName());
}
else if (lowerSubLabel.contains("etternavn")) {
subInput.setText(user.getLastName());
}
if (isPersonalia && parentField.label.toLowerCase().contains("navn") && !parentField.label.toLowerCase().contains("pårørende")) {
String lowerSub = subField.label.toLowerCase();
if (lowerSub.contains("fornavn")) subInput.setText(user.getFirstName());
else if (lowerSub.contains("etternavn")) subInput.setText(user.getLastName());
}
formContainer.addView(subInput);
dynamicFields.put(subField.id, subInput);
container.addView(subInput);
inputViews.put(subField.id, subInput);
requiredFieldsMap.put(subField.id, isSubRequired);
}
if (parentField.description != null && !parentField.description.isEmpty()) {
addDescription(parentField.description);
}
private void addSectionHeader(LinearLayout container, String title, String descText) {
TextView sectionHeader = new TextView(getContext());
sectionHeader.setText(title);
sectionHeader.setTextSize(18);
sectionHeader.setTypeface(null, Typeface.BOLD);
sectionHeader.setTextColor(Color.parseColor("#0069B3"));
sectionHeader.setPadding(0, 20, 0, 5);
container.addView(sectionHeader);
if (descText != null && !descText.isEmpty()) {
TextView desc = new TextView(getContext());
desc.setText(Html.fromHtml(descText, Html.FROM_HTML_MODE_COMPACT));
desc.setTextSize(12);
desc.setTextColor(Color.GRAY);
container.addView(desc);
}
View line = new View(getContext());
line.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 2));
line.setBackgroundColor(Color.LTGRAY);
line.setPadding(0,0,0,20);
container.addView(line);
}
private int getAddressScore(String label) {
@ -338,33 +537,103 @@ public class FormsFragment extends Fragment {
return 99;
}
private void addDescription(String text) {
TextView desc = new TextView(getContext());
desc.setText(Html.fromHtml(text, Html.FROM_HTML_MODE_COMPACT));
desc.setTextSize(12);
desc.setTextColor(Color.GRAY);
desc.setPadding(5, 5, 5, 0);
formContainer.addView(desc);
// --- CONDITIONAL LOGIC ENGINE ---
private void evaluateAllConditionalLogic() {
if (currentForm == null || currentForm.fields == null) return;
for (GravityField field : currentForm.fields) {
if (field.conditionalLogic == null) {
setViewVisibility(field.id, true);
continue;
}
private void addSectionHeader(String title, String descText) {
TextView sectionHeader = new TextView(getContext());
sectionHeader.setText(title);
sectionHeader.setTextSize(18);
sectionHeader.setTypeface(null, Typeface.BOLD);
sectionHeader.setTextColor(Color.parseColor("#0069B3"));
sectionHeader.setPadding(0, 50, 0, 5);
formContainer.addView(sectionHeader);
if (descText != null && !descText.isEmpty()) {
addDescription(descText);
boolean isMatch = evaluateLogic(field.conditionalLogic);
boolean show = "show".equalsIgnoreCase(field.conditionalLogic.actionType);
boolean shouldBeVisible = (show && isMatch) || (!show && !isMatch);
setViewVisibility(field.id, shouldBeVisible);
}
}
View line = new View(getContext());
line.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 2));
line.setBackgroundColor(Color.LTGRAY);
formContainer.addView(line);
private boolean evaluateLogic(GravityField.ConditionalLogic logic) {
if (logic.rules == null || logic.rules.isEmpty()) return true;
boolean isAll = "all".equalsIgnoreCase(logic.logicType);
boolean aggregatedResult = isAll;
for (GravityField.Rule rule : logic.rules) {
String val = getInputValue(rule.fieldId);
boolean ruleMatch = checkRule(val, rule.operator, rule.value);
if (isAll) {
aggregatedResult = aggregatedResult && ruleMatch;
if (!aggregatedResult) break;
} else {
aggregatedResult = aggregatedResult || ruleMatch;
if (aggregatedResult) break;
}
}
return aggregatedResult;
}
private boolean checkRule(String actualValue, String operator, String targetValue) {
if (actualValue == null) actualValue = "";
if (targetValue == null) targetValue = "";
switch (operator.toLowerCase()) {
case "is":
return actualValue.equalsIgnoreCase(targetValue);
case "isnot":
return !actualValue.equalsIgnoreCase(targetValue);
case "contains":
return actualValue.toLowerCase().contains(targetValue.toLowerCase());
case "starts_with":
return actualValue.toLowerCase().startsWith(targetValue.toLowerCase());
case "ends_with":
return actualValue.toLowerCase().endsWith(targetValue.toLowerCase());
default:
return false;
}
}
private String getInputValue(String fieldId) {
View view = inputViews.get(fieldId);
if (view == null) return "";
if (view instanceof EditText) {
return ((EditText) view).getText().toString();
} else if (view instanceof RadioGroup) {
RadioGroup group = (RadioGroup) view;
int checkedId = group.getCheckedRadioButtonId();
if (checkedId != -1) {
View rb = group.findViewById(checkedId);
if (rb != null && rb.getTag() != null) {
return rb.getTag().toString();
}
}
} else if (view instanceof Spinner) {
Spinner spinner = (Spinner) view;
Object item = spinner.getSelectedItem();
return item != null ? item.toString() : "";
} else if (view instanceof CheckBox) {
CheckBox cb = (CheckBox) view;
// Hvis sjekket: returner teksten boksen eller "1" (for consent)
// For standard checkbox i GF brukes ofte verdien som er valgt.
// Hvis det er en enkel avkryssing (type "Bekreftelse"), er "Ja" eller "1" vanlig.
// Vi returnerer teksten hvis den er sjekket, ellers tom streng.
return cb.isChecked() ? cb.getText().toString() : "";
}
return "";
}
private void setViewVisibility(String fieldId, boolean visible) {
View wrapper = fieldWrappers.get(fieldId);
if (wrapper != null) {
wrapper.setVisibility(visible ? View.VISIBLE : View.GONE);
}
}
// --- DATA HANDLING ---
private void fetchFormEntries() {
UserManager user = UserManager.getInstance();
@ -404,8 +673,8 @@ public class FormsFragment extends Fragment {
getActivity().runOnUiThread(() -> {
showHistory(entries);
// VIKTIG: AUTO-FYLL SKJER KUN FOR SKJEMA ID 1
if (entries.length() > 0 && formId == ID_ANSATTEOPPLYSNINGER) {
// VIKTIG: AUTO-FYLL SKJER KUN FOR SKJEMA ID 1 ("Ansatteopplysninger")
if (formId == ID_ANSATTEOPPLYSNINGER && entries.length() > 0) {
try {
prefillFormFromHistory(entries.getJSONObject(0));
} catch (JSONException e) { e.printStackTrace(); }
@ -420,35 +689,74 @@ public class FormsFragment extends Fragment {
}
private void prefillFormFromHistory(JSONObject latestEntry) {
for (Map.Entry<String, EditText> mapEntry : dynamicFields.entrySet()) {
String fieldId = mapEntry.getKey();
EditText input = mapEntry.getValue();
if (latestEntry == null) return;
if (input.getText().length() > 0) continue;
for (Map.Entry<String, View> entry : inputViews.entrySet()) {
String fieldId = entry.getKey();
View view = entry.getValue();
if (latestEntry.has(fieldId)) {
String prevValue = latestEntry.optString(fieldId);
if (!prevValue.isEmpty()) {
input.setText(prevValue);
String value = latestEntry.optString(fieldId);
if (value == null || value.isEmpty()) continue;
if (view instanceof EditText) {
((EditText) view).setText(value);
} else if (view instanceof RadioGroup) {
RadioGroup group = (RadioGroup) view;
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (child instanceof RadioButton) {
Object tag = child.getTag();
if (tag != null && tag.toString().equalsIgnoreCase(value)) {
((RadioButton) child).setChecked(true);
break;
}
}
}
updateStatus("Skjemaet er forhåndsutfylt fra din forrige innsending.");
} else if (view instanceof CheckBox) {
// For Consent er value "1" = Checked.
// For vanlig checkbox kan det være verdien av valget.
if ("1".equals(value) || "true".equalsIgnoreCase(value) || ((CheckBox)view).getText().toString().equals(value)) {
((CheckBox) view).setChecked(true);
}
}
}
}
updateStatus("Skjemaet er forhåndsutfylt fra din siste innsending.");
evaluateAllConditionalLogic();
}
private void submitDynamicForm() {
JSONObject inputValues = new JSONObject();
boolean hasValues = false;
for (Map.Entry<String, EditText> entry : dynamicFields.entrySet()) {
for (Map.Entry<String, View> entry : inputViews.entrySet()) {
String fieldId = entry.getKey();
EditText input = entry.getValue();
String value = input.getText().toString().trim();
View view = entry.getValue();
// Hopp over hvis feltet er skjult av Conditional Logic
View wrapper = fieldWrappers.get(fieldId);
// Hvis wrapper er null (f.eks sub-input), sjekk parent wrapper
if (wrapper == null) {
// Forenklet sjekk: hvis selve viewet ikke vises skjermen
if (!view.isShown()) continue;
} else {
if (wrapper.getVisibility() != View.VISIBLE) continue;
}
String value = getInputValue(fieldId);
// Validering
Boolean isRequired = requiredFieldsMap.get(fieldId);
if (isRequired != null && isRequired && value.isEmpty()) {
input.setError("Må fylles ut");
input.requestFocus();
if (view instanceof EditText) {
((EditText) view).setError("Må fylles ut");
view.requestFocus();
} else if (view instanceof CheckBox) {
Toast.makeText(getContext(), "Du må bekrefte " + ((CheckBox)view).getText(), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(), "Vennligst fyll ut alle obligatoriske felt.", Toast.LENGTH_SHORT).show();
}
return;
}
@ -477,7 +785,6 @@ public class FormsFragment extends Fragment {
}
Request request = new Request.Builder().url(url).post(body).header("Cookie", cookie).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
@ -492,6 +799,9 @@ public class FormsFragment extends Fragment {
Toast.makeText(getContext(), "Skjema sendt!", Toast.LENGTH_LONG).show();
fetchFormEntries();
updateStatus("Innsending OK");
if (formId != ID_ANSATTEOPPLYSNINGER) {
clearInputs();
}
});
}
} else {
@ -501,12 +811,20 @@ public class FormsFragment extends Fragment {
});
}
private void clearInputs() {
for (View view : inputViews.values()) {
if (view instanceof EditText) ((EditText) view).setText("");
if (view instanceof RadioGroup) ((RadioGroup) view).clearCheck();
if (view instanceof Spinner) ((Spinner) view).setSelection(0);
if (view instanceof CheckBox) ((CheckBox) view).setChecked(false);
}
}
private void showHistory(JSONArray entries) {
if (historyContainer == null) return;
historyContainer.removeAllViews();
if (entries.length() == 0) {
// Skjul overskriften hvis det ikke er historikk
if (lblHistory != null) lblHistory.setVisibility(View.GONE);
return;
} else {
@ -514,19 +832,19 @@ public class FormsFragment extends Fragment {
}
try {
for (int i = 0; i < entries.length(); i++) {
int count = Math.min(entries.length(), 5);
for (int i = 0; i < count; i++) {
JSONObject entry = entries.getJSONObject(i);
String date = entry.optString("date_created");
TextView item = new TextView(getContext());
item.setText(date);
item.setText("Innsendt: " + date);
item.setPadding(10, 20, 10, 20);
// Gjør historikken klikkbar (for fremtidig funksjonalitet)
item.setBackgroundResource(android.R.drawable.list_selector_background);
View line = new View(getContext());
line.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1));
line.setBackgroundColor(Color.LTGRAY);
historyContainer.addView(item);
historyContainer.addView(line);
}

View file

@ -2,6 +2,8 @@ package com.kbs.kbsintranett;
import android.graphics.Color;
import android.os.Bundle;
import android.text.Html;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -15,8 +17,13 @@ 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.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import retrofit2.Call;
import retrofit2.Callback;
@ -24,6 +31,7 @@ import retrofit2.Response;
public class FormsListFragment extends Fragment {
private static final String TAG = "FormsListFragment";
private LinearLayout container;
private ProgressBar loadingSpinner;
private TextView txtStatus;
@ -31,23 +39,16 @@ public class FormsListFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// Vi gjenbruker samme layout som detaljvisningen, siden den er ryddig
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);
// Skjul "Tidligere innsendinger" tekst og container denne siden,
// da vi kun skal vise en meny her.
View historyTitle = view.findViewById(R.id.historyContainer); // Egentlig containeren, men vi skjuler alt under
View historyTitle = view.findViewById(R.id.historyContainer);
if (historyTitle != null) historyTitle.setVisibility(View.GONE);
// Finn og skjul "Tidligere innsendinger" overskriften hvis mulig
// (Siden vi bruker en delt XML er dette litt "hacky", men funker)
LinearLayout mainLayout = view.findViewById(R.id.main_layout);
if (mainLayout != null && mainLayout.getChildCount() > 4) {
// Skjuler elementene nederst som hører til historikk
for(int i = 0; i < mainLayout.getChildCount(); i++) {
View child = mainLayout.getChildAt(i);
if (child instanceof TextView && ((TextView)child).getText().toString().contains("Tidligere")) {
@ -70,34 +71,56 @@ public class FormsListFragment extends Fragment {
if (loadingSpinner != null) loadingSpinner.setVisibility(View.VISIBLE);
if (txtStatus != null) txtStatus.setText("");
// Hent listen over skjemaer (Map ID -> Skjema)
Log.d(TAG, "Starter henting av skjemaer...");
RetrofitClient.getApiService().getFormsListMap().enqueue(new Callback<Map<String, GravityForm>>() {
@Override
public void onResponse(Call<Map<String, GravityForm>> call, Response<Map<String, GravityForm>> response) {
if (getContext() == null) return;
// SKJUL SPINNER NÅR VI FÅR SVAR
if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE);
if (response.isSuccessful() && response.body() != null) {
Map<String, GravityForm> formMap = response.body();
Log.d(TAG, "Fant " + formMap.size() + " skjemaer totalt.");
if (formMap.isEmpty()) {
if (txtStatus != null) txtStatus.setText("Ingen skjemaer funnet.");
return;
}
// Vi sorterer listen basert ID (eller tittel) for ryddighet
Map<String, GravityForm> sortedMap = new TreeMap<>(formMap);
List<GravityForm> visibleForms = new ArrayList<>();
for (GravityForm form : formMap.values()) {
boolean isActive = form.isActive == null || !"0".equals(form.isActive);
Integer sortOrder = getSortOrderFromDescription(form);
for (Map.Entry<String, GravityForm> entry : sortedMap.entrySet()) {
GravityForm form = entry.getValue();
// Sjekk at skjemaet er aktivt og har en tittel
if (form != null && form.title != null) {
// Logg hva vi finner for hvert skjema
Log.d(TAG, "Skjema: " + form.title + " | ID: " + form.id + " | Beskrivelse: " + form.description + " | Sorteringstall funnet: " + sortOrder);
if (form != null && form.title != null && isActive && sortOrder != null) {
visibleForms.add(form);
}
}
if (visibleForms.isEmpty()) {
Log.w(TAG, "Alle skjemaer ble filtrert bort. Sjekk at beskrivelsene starter med et tall.");
if (txtStatus != null) txtStatus.setText("Ingen tilgjengelige skjemaer i appen.");
return;
}
// Sorter basert tallet vi fant
Collections.sort(visibleForms, new Comparator<GravityForm>() {
@Override
public int compare(GravityForm f1, GravityForm f2) {
return Integer.compare(getSortOrderFromDescription(f1), getSortOrderFromDescription(f2));
}
});
for (GravityForm form : visibleForms) {
addFormButton(form);
}
}
} else {
Log.e(TAG, "Feil fra server: " + response.code());
if (txtStatus != null) txtStatus.setText("Kunne ikke laste listen (Kode " + response.code() + ")");
}
}
@ -105,34 +128,59 @@ public class FormsListFragment extends Fragment {
@Override
public void onFailure(Call<Map<String, GravityForm>> call, Throwable t) {
if (getContext() == null) return;
// SKJUL SPINNER VED FEIL
if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE);
Log.e(TAG, "Nettverksfeil", t);
if (txtStatus != null) txtStatus.setText("Nettverksfeil: " + t.getMessage());
}
});
}
/**
* Leter etter et tall i starten av beskrivelsen.
* Håndterer HTML-tags og mellomrom.
*/
private Integer getSortOrderFromDescription(GravityForm form) {
if (form.description == null || form.description.isEmpty()) {
return null; // Skjules
}
// 1. Fjern HTML-tags for å ren tekst (f.eks "<p>10.</p>" -> "10.")
String cleanText = Html.fromHtml(form.description, Html.FROM_HTML_MODE_COMPACT).toString().trim();
// 2. Regex: Finn første sekvens av tall (\d+)
// ^\D* betyr "Start på linjen, ignorer ikke-tall (som bokstaver/tegn) i starten"
Pattern p = Pattern.compile("^\\D*(\\d+)");
Matcher m = p.matcher(cleanText);
if (m.find()) {
try {
return Integer.parseInt(m.group(1));
} catch (NumberFormatException e) {
return null;
}
}
return null; // Ingen tall funnet -> Skjules
}
private void addFormButton(GravityForm form) {
Button button = new Button(getContext());
button.setText(form.title.toUpperCase()); // Store bokstaver ser ofte ryddigere ut i menyer
button.setText(form.title.toUpperCase());
button.setTextColor(Color.WHITE);
button.setBackgroundColor(Color.parseColor("#0069B3")); // KBS Blå
button.setTextSize(14);
button.setPadding(30, 30, 30, 30);
// Klikk-lytter
button.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putInt("formId", form.id);
Navigation.findNavController(v).navigate(R.id.action_formsListFragment_to_formsDetailFragment, bundle);
});
// Layout parametere (Marginer)
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
params.setMargins(0, 0, 0, 20); // Avstand mellom knappene
params.setMargins(0, 0, 0, 20);
button.setLayoutParams(params);
container.addView(button);

View file

@ -14,6 +14,11 @@ public class GravityField {
@SerializedName("label")
public String label;
// --- DETTE ER FELTET SOM MANGLER ---
@SerializedName("adminLabel")
public String adminLabel;
// -----------------------------------
@SerializedName("description")
public String description;
@ -46,7 +51,7 @@ public class GravityField {
@SerializedName("conditionalLogic")
public ConditionalLogic conditionalLogic;
// NYTT: For å lese Populate Anything-regler
// For å lese Populate Anything-regler
@SerializedName("gppa-values-templates")
public java.util.Map<String, String> gppaTemplates;

View file

@ -13,6 +13,9 @@ public class GravityForm {
@SerializedName("description")
public String description;
@SerializedName("is_active")
public String isActive; // "1" = Aktiv, "0" = Inaktiv
@SerializedName("fields")
public List<GravityField> fields; // En liste med alle feltene i skjemaet
public List<GravityField> fields;
}