Før caching

This commit is contained in:
ErolHaagenrud 2025-12-18 13:27:39 +01:00
parent 91f8c2c0c7
commit 5497e68110
4 changed files with 91 additions and 86 deletions

39
app/google-services.json Normal file
View file

@ -0,0 +1,39 @@
{
"project_info": {
"project_number": "738325360287",
"project_id": "intranet-glogin-452614",
"storage_bucket": "intranet-glogin-452614.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:738325360287:android:c97082eedba86b85b43caa",
"android_client_info": {
"package_name": "com.kbs.kbsintranett"
}
},
"oauth_client": [
{
"client_id": "738325360287-cidl3plnqv9ei74vm9vm5muustj6eenb.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyC0vrXCWJNLzrgOk1ChWeFpYlqxKPqxUf0"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "738325360287-cidl3plnqv9ei74vm9vm5muustj6eenb.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}

View file

@ -43,14 +43,15 @@ import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonArray;
import com.google.gson.JsonParser;
import org.json.JSONArray;
import org.json.JSONException;
@ -102,11 +103,12 @@ public class FormsFragment extends Fragment {
private LinearLayout formContainer;
private LinearLayout historyContainer;
private View historyWrapper; // Wrapper for historikk-modulen
private View historyWrapper;
private TextView txtStatus;
private TextView lblHistory;
private ProgressBar loadingSpinner;
private ImageView btnToggleHistory;
private Toolbar toolbar; // NYTT
// --- HOVEDSKJEMA STATE ---
private Map<String, View> fieldWrappers = new HashMap<>();
@ -118,7 +120,7 @@ public class FormsFragment extends Fragment {
private Map<String, View> childInputViews = new HashMap<>();
private Map<String, Boolean> childRequiredFieldsMap = new HashMap<>();
private Map<String, Uri> childFileUploads = new HashMap<>();
// Lagring av Nested Entries
private List<NestedEntry> nestedEntries = new ArrayList<>();
private LinearLayout nestedEntriesContainer;
private TextView totalAmountView;
@ -184,12 +186,17 @@ public class FormsFragment extends Fragment {
lblHistory = view.findViewById(R.id.lbl_history);
loadingSpinner = view.findViewById(R.id.loading_spinner);
// NYTT: Finn toolbar og sett listener
toolbar = view.findViewById(R.id.forms_toolbar);
if (toolbar != null) {
toolbar.setNavigationOnClickListener(v -> Navigation.findNavController(view).navigateUp());
}
btnToggleHistory = view.findViewById(R.id.btn_toggle_history);
if (btnToggleHistory != null) {
btnToggleHistory.setOnClickListener(v -> toggleHistoryVisibility());
}
// --- FIKS FOR NULLPOINTER EXCEPTION LAYOUTTRANSITION ---
if (view instanceof ViewGroup) {
LayoutTransition transition = ((ViewGroup) view).getLayoutTransition();
if (transition == null) {
@ -198,7 +205,6 @@ public class FormsFragment extends Fragment {
}
transition.enableTransitionType(LayoutTransition.CHANGING);
}
// ----------------------------------------------------------
if (formContainer == null) {
formContainer = new LinearLayout(getContext());
@ -229,11 +235,9 @@ public class FormsFragment extends Fragment {
if (historyWrapper == null || btnToggleHistory == null) return;
if (historyWrapper.getVisibility() == View.VISIBLE) {
// Skjul historikk
historyWrapper.setVisibility(View.GONE);
btnToggleHistory.setImageResource(android.R.drawable.arrow_down_float);
} else {
// Vis historikk
historyWrapper.setVisibility(View.VISIBLE);
btnToggleHistory.setImageResource(android.R.drawable.arrow_up_float);
}
@ -300,20 +304,17 @@ public class FormsFragment extends Fragment {
nestedEntries.clear();
updateStatus("");
// Reset visibility of history on new load
// NYTT: Sett tittelen i Toolbaren i stedet for å legge til en TextView
if (toolbar != null) {
toolbar.setTitle(getCleanTitle(form.title));
}
if (historyWrapper != null) {
historyWrapper.setVisibility(View.VISIBLE);
if (btnToggleHistory != null) btnToggleHistory.setImageResource(android.R.drawable.arrow_up_float);
}
TextView title = new TextView(getContext());
title.setText(getCleanTitle(form.title));
title.setTextSize(24);
title.setTypeface(null, Typeface.BOLD);
title.setTextColor(Color.BLACK);
title.setPadding(0, 0, 0, 20);
formContainer.addView(title);
// Beskrivelse legges fortsatt inn som innhold
if (form.description != null && !form.description.isEmpty()) {
TextView formDesc = new TextView(getContext());
String cleanDesc = form.description.replaceFirst("^\\d+\\.\\s*", "");
@ -429,14 +430,13 @@ public class FormsFragment extends Fragment {
btnAdd.setBackgroundColor(Color.parseColor("#53AFE9"));
btnAdd.setTextColor(Color.WHITE);
btnAdd.setOnClickListener(v -> {
expandFormModule(); // Trigger expand
expandFormModule();
int childFormId = 18;
if (field.gpnfForm != null) {
try {
childFormId = Integer.parseInt(field.gpnfForm);
} catch (NumberFormatException e) { e.printStackTrace(); }
}
// NYTT: Sender med felt-ID fra hovedskjemaet (f.eks "25")
openChildFormDialog(childFormId, field.id);
});
container.addView(btnAdd);
@ -455,7 +455,6 @@ public class FormsFragment extends Fragment {
container.addView(totalAmountView);
}
// NY SIGNATUR: Tar imot parentFieldId
private void openChildFormDialog(int childFormId, String parentFieldId) {
if (getActivity() == null) return;
ProgressBar pBar = new ProgressBar(getContext());
@ -483,7 +482,6 @@ public class FormsFragment extends Fragment {
});
}
// NY SIGNATUR: Tar imot parentFieldId
private void showChildFormDialog(GravityForm childForm, String parentFieldId) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
childInputViews.clear();
@ -533,7 +531,6 @@ public class FormsFragment extends Fragment {
});
}
// NY SIGNATUR: Tar imot parentFieldId og sender den som meta
private void submitChildForm(int childFormId, AlertDialog dialog, String parentFieldId) {
JSONObject inputValues = new JSONObject();
for (Map.Entry<String, View> entry : childInputViews.entrySet()) {
@ -559,13 +556,9 @@ public class FormsFragment extends Fragment {
}
}
// --- HER ER FIKSEN FOR BUGGEN ---
// Vi legger ved en ekstra parameter som forteller Gravity Forms at dette
// er en nested entry som hører til et bestemt felt (f.eks "25").
if (parentFieldId != null) {
textParts.put("gpnf_entry_nested_form_field", RequestBody.create(MultipartBody.FORM, parentFieldId));
}
// ---------------------------------
for (Map.Entry<String, Uri> fileEntry : childFileUploads.entrySet()) {
String fieldId = fileEntry.getKey();
@ -585,9 +578,6 @@ public class FormsFragment extends Fragment {
JsonObject json = response.body().getAsJsonObject();
if (json.has("is_valid") && json.get("is_valid").getAsBoolean()) {
String entryId = json.has("entry_id") ? json.get("entry_id").getAsString() : "";
// NB: Tilpass ID-ene her hvis skjema 18 endres.
// ID 3 = Beskrivelse, ID 4 = Beløp
String desc = getInputValueGeneric(childInputViews.get("3"));
String price = getInputValueGeneric(childInputViews.get("4"));
addNestedEntry(entryId, desc, price);
@ -661,10 +651,9 @@ public class FormsFragment extends Fragment {
Button btnUpload = new Button(getContext());
btnUpload.setText("Velg fil / Ta bilde");
btnUpload.setOnClickListener(v -> {
// Setter state før vi viser dialog
pendingFileFieldId = field.id;
isSelectingForChild = isChild;
expandFormModule(); // Trigger expand
expandFormModule();
showFileSourceDialog();
});
TextView txtFileName = new TextView(getContext());
@ -681,23 +670,19 @@ public class FormsFragment extends Fragment {
reqMap.put(field.id, field.isRequired);
}
// Hjelpemetode for å vise dialog
private void showFileSourceDialog() {
String[] options = {"Ta bilde", "Velg fil"};
new AlertDialog.Builder(getContext())
.setTitle("Last opp vedlegg")
.setItems(options, (dialog, which) -> {
if (which == 0) {
// Ta bilde - SJEKKER PERMISSION FØRST
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED) {
openCamera();
} else {
// Spør om lov
requestPermissionLauncher.launch(Manifest.permission.CAMERA);
}
} else {
// Velg fil
if (filePickerLauncher != null) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
@ -790,7 +775,7 @@ public class FormsFragment extends Fragment {
timeInput.setClickable(true);
timeInput.setHint("00:00");
timeInput.setOnClickListener(v -> {
expandFormModule(); // Trigger expand
expandFormModule();
Calendar mcurrentTime = Calendar.getInstance();
int hour = mcurrentTime.get(Calendar.HOUR_OF_DAY);
int minute = mcurrentTime.get(Calendar.MINUTE);
@ -831,7 +816,7 @@ public class FormsFragment extends Fragment {
public void onTextChanged(CharSequence s, int start, int before, int count) {}
public void afterTextChanged(Editable s) { evaluateAllConditionalLogic(); }
});
attachInteractionListener(input); // Add listener
attachInteractionListener(input);
container.addView(input);
views.put(field.id, input);
@ -845,7 +830,7 @@ public class FormsFragment extends Fragment {
input.setMinLines(3);
input.setGravity(android.view.Gravity.TOP | android.view.Gravity.START);
attachInteractionListener(input); // Add listener
attachInteractionListener(input);
container.addView(input);
views.put(field.id, input);
@ -859,7 +844,6 @@ public class FormsFragment extends Fragment {
RadioButton rb = new RadioButton(getContext());
rb.setText(choice.text);
rb.setTag(choice.value);
// Also trigger expand on RadioButton click
rb.setOnClickListener(v -> {
expandFormModule();
evaluateAllConditionalLogic();
@ -867,7 +851,6 @@ public class FormsFragment extends Fragment {
group.addView(rb);
}
}
// Fallback listener
group.setOnCheckedChangeListener((g, i) -> {
expandFormModule();
evaluateAllConditionalLogic();
@ -879,7 +862,6 @@ public class FormsFragment extends Fragment {
private void renderSelectField(LinearLayout container, GravityField field, Map<String, View> views, Map<String, Boolean> 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();
@ -922,7 +904,6 @@ public class FormsFragment extends Fragment {
checkBox.setText(inputDef.label);
String value = "1";
// Fallback
if (field.choices != null && i < field.choices.size()) {
value = field.choices.get(i).value;
}
@ -940,24 +921,20 @@ public class FormsFragment extends Fragment {
private void renderDateField(LinearLayout container, GravityField field, Map<String, View> views, Map<String, Boolean> req) {
EditText dateInput = new EditText(getContext());
// --- DATO LØSNING (Fix for Read Only / Dagens Dato) ---
if (field.readOnly || (formId == ID_REFUSJON_UTLEGG && "28".equals(field.id))) {
// Sett dagens dato
SimpleDateFormat df = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault());
dateInput.setText(df.format(new Date()));
// Gjør den "read-only" men synlig
dateInput.setFocusable(false);
dateInput.setClickable(false);
dateInput.setEnabled(false);
dateInput.setTextColor(Color.BLACK);
} else {
// Vanlig dato-velger
dateInput.setFocusable(false);
dateInput.setClickable(true);
dateInput.setHint("dd.mm.yyyy");
dateInput.setOnClickListener(v -> {
expandFormModule(); // Trigger expand
expandFormModule();
Calendar c = Calendar.getInstance();
new DatePickerDialog(getContext(), (view, year, month, dayOfMonth) -> {
dateInput.setText(String.format("%02d.%02d.%d", dayOfMonth, month + 1, year));
@ -1361,20 +1338,17 @@ public class FormsFragment extends Fragment {
}
try {
// Vis flere oppføringer siden vi har scrolle-mulighet øverst
int count = Math.min(entries.length(), 20);
for (int i = 0; i < count; i++) {
JSONObject entry = entries.getJSONObject(i);
String date = entry.optString("date_created");
// Prøv å finne en bedre tittel enn bare dato (f.eks Prosjektnavn eller Sted)
String titleText = "Innsendt: " + date;
TextView item = new TextView(getContext());
item.setText(titleText);
item.setPadding(10, 20, 10, 20);
item.setBackgroundResource(android.R.drawable.list_selector_background);
item.setTextSize(14);
// Add click listener to show details
item.setOnClickListener(v -> showEntryDetails(entry));
View line = new View(getContext());
line.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1));
@ -1387,20 +1361,16 @@ public class FormsFragment extends Fragment {
}
}
// NY METODE: Vis detaljer i dialog med delingsknapp
private void showEntryDetails(JSONObject entry) {
// HVIS dette er Form 16 (Refusjon), vi først hente vedleggene (som ligger i Form 18)
if (formId == ID_REFUSJON_UTLEGG) {
Log.d(TAG, "Form 16 detected. Checking for child entries...");
String nestedIds = entry.optString("25"); // Felt 25 i Form 16 inneholder ID-ene til vedleggene
String nestedIds = entry.optString("25");
if (!nestedIds.isEmpty()) {
Log.d(TAG, "Nested IDs found: " + nestedIds);
List<String> ids = new ArrayList<>();
// ROBUST PARSING AV ID-LISTE
if (nestedIds.startsWith("[") && nestedIds.endsWith("]")) {
// Dette er en JSON-array streng (f.eks [101, 102])
try {
JSONArray jsonArray = new JSONArray(nestedIds);
for(int i=0; i<jsonArray.length(); i++) {
@ -1410,42 +1380,34 @@ public class FormsFragment extends Fragment {
Log.e(TAG, "Failed to parse nested IDs as JSON array", e);
}
} else {
// Dette er en komma-separert streng (f.eks "101, 102")
for (String id : nestedIds.split(",")) {
ids.add(id.trim());
}
}
if (!ids.isEmpty()) {
// Vis en "Laster..." dialog mens vi henter data
AlertDialog loadingDialog = new AlertDialog.Builder(getContext())
.setMessage("Henter vedleggsdetaljer...")
.setCancelable(false)
.show();
// Start rekursiv henting
StringBuilder accumulatedHtml = new StringBuilder();
StringBuilder accumulatedText = new StringBuilder();
// Legg først til basis-info fra hovedskjemaet
appendBasicInfo(entry, accumulatedHtml, accumulatedText);
fetchChildEntriesRecursive(ids, 0, accumulatedHtml, accumulatedText, loadingDialog);
return; // Stopp her, dialogen vises når data er hentet
return;
}
} else {
Log.d(TAG, "No nested IDs found in field 25.");
}
}
// --- STANDARD VISNING (For alle andre skjemaer) ---
StringBuilder htmlBuilder = new StringBuilder();
StringBuilder textBuilder = new StringBuilder();
appendBasicInfo(entry, htmlBuilder, textBuilder);
showFinalDialog(htmlBuilder, textBuilder);
}
// Hjelpemetode for å legge til info fra hovedskjemaet
private void appendBasicInfo(JSONObject entry, StringBuilder html, StringBuilder text) {
try {
String date = entry.optString("date_created");
@ -1455,7 +1417,6 @@ public class FormsFragment extends Fragment {
if (currentForm != null && currentForm.fields != null) {
for (GravityField field : currentForm.fields) {
if ("section".equals(field.type) || "html".equals(field.type) || "captcha".equals(field.type)) continue;
// Hopp over felt 25 (Vedleggs-IDer) i Form 16, siden vi viser det bedre senere
if (formId == ID_REFUSJON_UTLEGG && "25".equals(field.id)) continue;
String value = "";
@ -1470,8 +1431,6 @@ public class FormsFragment extends Fragment {
if (!value.trim().isEmpty()) {
if ("fileupload".equals(field.type)) {
// Håndter filopplastinger (single/multiple)
// Her antar vi enkel URL, men for sikkerhets skyld bruker vi extractUrl
String cleanUrl = extractUrl(value);
if (cleanUrl.startsWith("http")) {
html.append("<b>").append(field.label).append(":</b><br>")
@ -1491,27 +1450,20 @@ public class FormsFragment extends Fragment {
} catch (Exception e) {}
}
// Rekursiv metode for å hente barn-oppføringer (Vedlegg)
private void fetchChildEntriesRecursive(List<String> ids, int index, StringBuilder html, StringBuilder text, AlertDialog loader) {
if (index >= ids.size()) {
// Alle ferdige! Vis dialogen.
loader.dismiss();
showFinalDialog(html, text);
return;
}
String entryId = ids.get(index);
Log.d(TAG, "Fetching child entry ID: " + entryId);
RetrofitClient.getApiService().getSingleEntry(entryId).enqueue(new retrofit2.Callback<JsonElement>() {
@Override
public void onResponse(retrofit2.Call<JsonElement> call, retrofit2.Response<JsonElement> response) {
if (response.isSuccessful() && response.body() != null) {
try {
JsonObject json = response.body().getAsJsonObject();
// I Form 18: Felt 1 = Fil, Felt 3 = Beskrivelse, Felt 4 = Beløp
// Parse Description and Price
String desc = json.has("3") ? json.get("3").getAsString() : "Uten beskrivelse";
String price = json.has("4") ? json.get("4").getAsString() : "";
@ -1521,11 +1473,9 @@ public class FormsFragment extends Fragment {
html.append(desc).append(" (").append(price).append(")<br>");
text.append(desc).append(" (").append(price).append(")\n");
// Parse File Field (ID 1) - Can be multiple files!
if (json.has("1")) {
JsonElement fileEl = json.get("1");
if (fileEl.isJsonArray()) {
// It is a real JSON array
JsonArray arr = fileEl.getAsJsonArray();
for (int i = 0; i < arr.size(); i++) {
String url = arr.get(i).getAsString().replace("\\/", "/");
@ -1533,7 +1483,6 @@ public class FormsFragment extends Fragment {
text.append(url).append("\n");
}
} else {
// It is a string. Check if it's a JSON string array like "[\"http...\"]"
String rawString = fileEl.getAsString();
if (rawString.startsWith("[") && rawString.endsWith("]")) {
try {
@ -1544,13 +1493,11 @@ public class FormsFragment extends Fragment {
text.append(url).append("\n");
}
} catch (JSONException ex) {
// Fallback: simple cleanup
String clean = extractUrl(rawString);
html.append("<a href=\"").append(clean).append("\">Åpne fil</a><br>");
text.append(clean).append("\n");
}
} else {
// Just a plain URL string
String clean = extractUrl(rawString);
if(clean.startsWith("http")) {
html.append("<a href=\"").append(clean).append("\">Åpne fil</a><br>");
@ -1565,17 +1512,12 @@ public class FormsFragment extends Fragment {
} catch (Exception e) {
Log.e(TAG, "Error parsing child entry", e);
}
} else {
Log.e(TAG, "Failed to fetch child entry: " + response.code());
}
// til neste (uansett om denne feilet eller ei)
fetchChildEntriesRecursive(ids, index + 1, html, text, loader);
}
@Override
public void onFailure(retrofit2.Call<JsonElement> call, Throwable t) {
Log.e(TAG, "Network error fetching child entry", t);
// Hopp over ved feil
fetchChildEntriesRecursive(ids, index + 1, html, text, loader);
}
});
@ -1598,7 +1540,6 @@ public class FormsFragment extends Fragment {
.show();
}
// Hjelpemetode for å rydde opp i URLer fra JSON (f.eks ["http://..."] -> http://...)
private String extractUrl(String rawValue) {
if (rawValue == null) return "";
String clean = rawValue.replace("[", "")

View file

@ -8,6 +8,25 @@
android:background="#F5F5F5"
tools:context=".FormsFragment">
<!-- NYTT: Toolbar med tilbake-knapp -->
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Light">
<androidx.appcompat.widget.Toolbar
android:id="@+id/forms_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/white"
app:navigationIcon="@android:drawable/ic_menu_revert"
app:titleTextColor="@color/black"
app:title="Laster..." />
</com.google.android.material.appbar.AppBarLayout>
<!-- Resten er likt, men nå under toolbaren -->
<LinearLayout
android:id="@+id/history_wrapper"
android:layout_width="match_parent"

View file

@ -2,6 +2,7 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
@ -91,12 +92,17 @@
android:text="Av: Forfatter"/>
</LinearLayout>
<!-- BYTTET FRA TextView TIL WebView -->
<!--
WebView for innhold.
tools:ignore="WebViewLayout" hindrer feilmeldingen om wrap_content,
da dette er ønsket oppførsel inne i en NestedScrollView.
-->
<WebView
android:id="@+id/detail_webview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none" />
android:scrollbars="none"
tools:ignore="WebViewLayout" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>