diff --git a/app/src/main/java/com/kbs/kbsintranett/AddTaskBottomSheet.java b/app/src/main/java/com/kbs/kbsintranett/AddTaskBottomSheet.java index a1247f5..4f173bf 100644 --- a/app/src/main/java/com/kbs/kbsintranett/AddTaskBottomSheet.java +++ b/app/src/main/java/com/kbs/kbsintranett/AddTaskBottomSheet.java @@ -34,15 +34,13 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { private TextView txtSheetTitle, txtDatePreview, txtUsersPreview; private Calendar dueDate = Calendar.getInstance(); - private List allUsersFromApi = new ArrayList<>(); private List filteredUsers = new ArrayList<>(); private List selectedUsers = new ArrayList<>(); - - private TaskItem taskToEdit = null; // NYTT: Hold på oppgaven som skal endres + private TaskItem taskToEdit = null; public interface OnTaskAddedListener { void onTaskAdded(TaskItem task); - void onTaskUpdated(TaskItem task); // NYTT + void onTaskUpdated(TaskItem task); } private OnTaskAddedListener listener; @@ -51,7 +49,6 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { this.listener = listener; } - // NYTT: Metode for å sette oppgaven som skal redigeres public void setTaskToEdit(TaskItem task) { this.taskToEdit = task; } @@ -76,7 +73,7 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View v = inflater.inflate(R.layout.bottom_sheet_add_task, container, false); - txtSheetTitle = v.findViewById(R.id.txt_sheet_title); // Pass på at denne IDen finnes i XML + txtSheetTitle = v.findViewById(R.id.txt_sheet_title); etTitle = v.findViewById(R.id.et_task_title); etDesc = v.findViewById(R.id.et_task_desc); btnDate = v.findViewById(R.id.btn_task_date); @@ -107,24 +104,23 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { btnUsers.setOnClickListener(view -> showUserSelectionDialog()); btnSave.setOnClickListener(view -> saveTask()); - fetchUsers(); + fetchAndFilterUsers(); return v; } - private void fetchUsers() { + private void fetchAndFilterUsers() { RetrofitClient.getApiService().getUsersList().enqueue(new Callback>() { @Override public void onResponse(Call> call, Response> response) { if (response.isSuccessful() && response.body() != null) { - allUsersFromApi = response.body(); - filterUsersByHierarchy(); + // BRUKER HJELPEKLASSEN HER: + filteredUsers = UserFilterHelper.getFilteredUsers(response.body()); - // Hvis vi redigerer, må vi mappe eksisterende deltakere til selectedUsers listen if (taskToEdit != null) { selectedUsers.clear(); Map currentAssignees = taskToEdit.getAssigneeStatus(); - for (User u : allUsersFromApi) { + for (User u : filteredUsers) { if (currentAssignees.containsKey(u.getEmail())) { selectedUsers.add(u); } @@ -138,46 +134,6 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { }); } - private void filterUsersByHierarchy() { - filteredUsers.clear(); - UserManager me = UserManager.getInstance(); - List myRoles = new ArrayList<>(); - User self = null; - - for (User u : allUsersFromApi) { - if (u.getEmail().equalsIgnoreCase(me.getUserEmail())) { - self = u; - for (String r : u.getRoles()) myRoles.add(r.toLowerCase()); - break; - } - } - - if (me.isEditorOrAbove()) { - filteredUsers.addAll(allUsersFromApi); - return; - } - - for (User u : allUsersFromApi) { - if (u.getEmail().equalsIgnoreCase(me.getUserEmail())) { - if (!filteredUsers.contains(u)) filteredUsers.add(u); - continue; - } - boolean hasAccess = false; - for (String role : u.getRoles()) { - String r = role.toLowerCase(); - if ((r.equals("serviceavdelingen") && myRoles.contains("serviceavdelingen")) || - (r.equals("automasjonsavdelingen") && myRoles.contains("automasjonsavdelingen")) || - (r.equals("prosjektavdelingen") && myRoles.contains("prosjektavdelingen")) || - (r.equals("administrasjonen") && myRoles.contains("administrasjonen")) || - (r.equals("kbs_alle") && myRoles.contains("kbs_alle"))) { - hasAccess = true; - break; - } - } - if (hasAccess && !filteredUsers.contains(u)) filteredUsers.add(u); - } - } - private void showUserSelectionDialog() { if (filteredUsers.isEmpty()) { Toast.makeText(getContext(), "Henter tilgjengelige personer...", Toast.LENGTH_SHORT).show(); @@ -187,7 +143,6 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { boolean[] checked = new boolean[filteredUsers.size()]; for (int i = 0; i < filteredUsers.size(); i++) { names[i] = filteredUsers.get(i).getName(); - // Sjekk om e-posten finnes i de valgte brukernes liste boolean isSelected = false; for(User su : selectedUsers) { if(su.getEmail().equalsIgnoreCase(filteredUsers.get(i).getEmail())) { @@ -202,13 +157,9 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { .setMultiChoiceItems(names, checked, (dialog, which, isChecked) -> { User user = filteredUsers.get(which); if (isChecked) { - boolean alreadyIn = false; - for(User su : selectedUsers) if(su.getEmail().equalsIgnoreCase(user.getEmail())) alreadyIn = true; - if(!alreadyIn) selectedUsers.add(user); + selectedUsers.add(user); } else { - User toRemove = null; - for(User su : selectedUsers) if(su.getEmail().equalsIgnoreCase(user.getEmail())) toRemove = su; - if(toRemove != null) selectedUsers.remove(toRemove); + selectedUsers.removeIf(u -> u.getEmail().equalsIgnoreCase(user.getEmail())); } }) .setPositiveButton("OK", (dialog, which) -> updateUsersPreview()) @@ -234,12 +185,6 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { } if (taskToEdit != null) { - // REDIGER MODUS: Oppdater eksisterende objekt - // Vi må beholde ID, Creator osv, men oppdatere innhold og deltakere - // OBS: Hvis vi fjerner deltakere som allerede hadde gjort oppgaven, forsvinner deres status. - // Dette er akseptabel oppførsel for enkelthets skyld. - - // Lag en kopi av gamle statuser for å bevare "Fullført" for de som fortsatt er med Map oldStatus = taskToEdit.getAssigneeStatus(); taskToEdit.getAssigneeStatus().clear(); @@ -255,25 +200,11 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { } } } - - // Sjekk om tittelen eller beskrivelsen faktisk har endret seg for å sette titlene på nytt - // (Vi bruker reflection her eller bare setter verdiene direkte siden de er private i TaskItem) - // Siden jeg ikke endret TaskItem til å ha public settlere, antar jeg at du legger til disse - // eller at vi bare lager en ny TaskItem med samme ID. - - // Vi simulerer oppdatering av feltene (Sørg for at TaskItem har disse setterene!) - // Hvis du ikke har setttere, legg dem til i TaskItem.java: - // public void setTitle(String t) { this.title = t; } - // public void setDescription(String d) { this.description = d; } - // public void setDueDate(long d) { this.dueDate = d; } - - // Jeg skriver logikken slik at den forventer setttere: - updateTaskFields(taskToEdit, title, etDesc.getText().toString(), dueDate.getTimeInMillis()); - + taskToEdit.setTitle(title); + taskToEdit.setDescription(etDesc.getText().toString()); + taskToEdit.setDueDate(dueDate.getTimeInMillis()); if (listener != null) listener.onTaskUpdated(taskToEdit); - } else { - // NY OPPGAVE MODUS TaskItem newTask = new TaskItem(title, etDesc.getText().toString(), dueDate.getTimeInMillis()); if (selectedUsers.isEmpty()) { newTask.addAssignee(UserManager.getInstance().getUserEmail()); @@ -284,13 +215,4 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { } dismiss(); } - - // Hjelpemetode (forutsetter at du legger til disse tre setterne i TaskItem.java) - private void updateTaskFields(TaskItem t, String title, String desc, long date) { - // Her kaller vi setterne vi nå legger til i TaskItem - // Se punkt 5 nedenfor for oppdatert TaskItem.java - t.setTitle(title); - t.setDescription(desc); - t.setDueDate(date); - } } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java b/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java index 068fc03..fc0a77f 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java @@ -32,7 +32,6 @@ import com.google.gson.JsonElement; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; -import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; @@ -57,14 +56,10 @@ public class CreateEventFragment extends Fragment { private String selectedRRule = null; private boolean isCustomRecurrence = false; - // Deltakere - private List allUsers = new ArrayList<>(); + // BRUKERLISTER private List filteredUsers = new ArrayList<>(); - // Arrangør-sporing (for å ikke overskrive opprinnelig arrangør ved redigering) private String originalOrganizer = null; - - // EDIT MODE private CalendarEvent eventToEdit = null; @Nullable @@ -77,29 +72,26 @@ public class CreateEventFragment extends Fragment { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + // Initialisering av Views etTitle = view.findViewById(R.id.et_title); etDesc = view.findViewById(R.id.et_desc); etLocation = view.findViewById(R.id.et_location); switchAllDay = view.findViewById(R.id.switch_all_day); - spinnerCalendar = view.findViewById(R.id.spinner_calendar); spinnerRecurrence = view.findViewById(R.id.spinner_recurrence); chipGroupReminders = view.findViewById(R.id.chip_group_reminders); - txtPreview = view.findViewById(R.id.txt_time_preview); txtSelectedParticipants = view.findViewById(R.id.txt_selected_participants); - btnStartDate = view.findViewById(R.id.btn_start_date); btnStartTime = view.findViewById(R.id.btn_start_time); btnEndDate = view.findViewById(R.id.btn_end_date); btnEndTime = view.findViewById(R.id.btn_end_time); btnSave = view.findViewById(R.id.btn_save_event); btnSelectParticipants = view.findViewById(R.id.btn_select_participants); - rbAll = view.findViewById(R.id.rb_visibility_all); rbSpecific = view.findViewById(R.id.rb_visibility_specific); - // Initialiser tid (neste hele time) + // Standardtidspunkt (neste time) startCal.add(Calendar.HOUR_OF_DAY, 1); startCal.set(Calendar.MINUTE, 0); endCal.setTime(startCal.getTime()); @@ -109,7 +101,7 @@ public class CreateEventFragment extends Fragment { setupReminderChips(); fetchUsers(); - // SJEKK OM VI ER I REDIGERINGS-MODUS + // Sjekk om vi redigerer en eksisterende hendelse if (getArguments() != null && getArguments().containsKey("edit_event")) { eventToEdit = (CalendarEvent) getArguments().getSerializable("edit_event"); prefillForm(eventToEdit); @@ -121,16 +113,12 @@ public class CreateEventFragment extends Fragment { // Listeners switchAllDay.setOnCheckedChangeListener((btn, isChecked) -> updateUI()); - btnStartDate.setOnClickListener(v -> pickDate(startCal, true)); btnEndDate.setOnClickListener(v -> pickDate(endCal, false)); - btnStartTime.setOnClickListener(v -> pickTime(startCal)); btnEndTime.setOnClickListener(v -> pickTime(endCal)); - btnSave.setOnClickListener(v -> submitEvent()); - // Vis/Skjul deltaker-knapp rbAll.setOnCheckedChangeListener((buttonView, isChecked) -> { if (isChecked) { btnSelectParticipants.setVisibility(View.GONE); @@ -150,7 +138,6 @@ public class CreateEventFragment extends Fragment { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { if (eventToEdit != null && position == 0 && selectedRRule != null) return; - String selected = parent.getItemAtPosition(position).toString(); if (selected.equals("Egendefinert...")) { showCustomRecurrenceDialog(); @@ -162,15 +149,6 @@ public class CreateEventFragment extends Fragment { } @Override public void onNothingSelected(AdapterView parent) {} }); - - spinnerCalendar.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - String calName = (String) parent.getItemAtPosition(position); - updateFilteredUsers(calName); - } - @Override public void onNothingSelected(AdapterView parent) {} - }); } private void fetchUsers() { @@ -179,11 +157,8 @@ public class CreateEventFragment extends Fragment { public void onResponse(Call> call, Response> response) { if (!isAdded()) return; if (response.isSuccessful() && response.body() != null) { - allUsers = response.body(); - - if (spinnerCalendar.getSelectedItem() != null) { - updateFilteredUsers(spinnerCalendar.getSelectedItem().toString()); - } + // BRUKER DEN SENTRALE HJELPEKLASSEN FOR FILTRERING + filteredUsers = UserFilterHelper.getFilteredUsers(response.body()); if (eventToEdit != null) { parseParticipantsFromDescription(eventToEdit.getDescription()); @@ -195,42 +170,9 @@ public class CreateEventFragment extends Fragment { }); } - private void updateFilteredUsers(String calendarName) { - if (allUsers == null || allUsers.isEmpty()) return; - - filteredUsers.clear(); - - String requiredRole = ""; - - if (calendarName.equals("Serviceavdelingen")) requiredRole = "serviceavdelingen"; - else if (calendarName.equals("Automasjonsavdelingen")) requiredRole = "automasjonsavdelingen"; - else if (calendarName.equals("Prosjektavdelingen")) requiredRole = "prosjektavdelingen"; - else if (calendarName.equals("Administrasjonen")) requiredRole = "administrasjonen"; - else if (calendarName.equals("Felles")) requiredRole = "kbs_alle"; - - for (User user : allUsers) { - List rawRoles = user.getRoles(); - List roles = new ArrayList<>(); - for (String r : rawRoles) roles.add(r.toLowerCase()); - - boolean hasAccess = false; - - if (roles.contains("administrator")) { - hasAccess = true; - } - else if (!requiredRole.isEmpty() && roles.contains(requiredRole.toLowerCase())) { - hasAccess = true; - } - - if (hasAccess) { - filteredUsers.add(user); - } - } - } - private void showUserSelectionDialog() { if (filteredUsers.isEmpty()) { - Toast.makeText(getContext(), "Ingen personer med riktig tilgang funnet.", Toast.LENGTH_SHORT).show(); + Toast.makeText(getContext(), "Ingen personer funnet.", Toast.LENGTH_SHORT).show(); return; } @@ -255,7 +197,7 @@ public class CreateEventFragment extends Fragment { private void updateParticipantPreview() { StringBuilder sb = new StringBuilder("Valgte: "); int count = 0; - for (User u : allUsers) { + for (User u : filteredUsers) { if (u.isSelected()) { if (count > 0) sb.append(", "); sb.append(u.getName()); @@ -274,11 +216,10 @@ public class CreateEventFragment extends Fragment { if (m.find()) { rbSpecific.setChecked(true); String[] emails = m.group(1).split(","); - for (String email : emails) { - for (int i = 0; i < allUsers.size(); i++) { - if (allUsers.get(i).getEmail().equalsIgnoreCase(email.trim())) { - allUsers.get(i).setSelected(true); + for (User u : filteredUsers) { + if (u.getEmail().equalsIgnoreCase(email.trim())) { + u.setSelected(true); } } } @@ -290,19 +231,15 @@ public class CreateEventFragment extends Fragment { private void prefillForm(CalendarEvent event) { etTitle.setText(event.getTitle()); - - // Hent ut opprinnelig arrangør hvis den finnes if (event.getDescription() != null) { Matcher m = Pattern.compile("#arrangor:(.+)").matcher(event.getDescription()); - if (m.find()) { - originalOrganizer = m.group(1).trim(); - } + if (m.find()) originalOrganizer = m.group(1).trim(); } String cleanDesc = event.getDescription() .replaceAll("#varsel:[\\d,]+", "") .replaceAll("#deltakere:[^\\s]+", "") - .replaceAll("#arrangor:.+", "") // Fjern også arrangør fra tekstfeltet + .replaceAll("#arrangor:.+", "") .trim(); etDesc.setText(cleanDesc); etLocation.setText(event.getLocation()); @@ -310,36 +247,29 @@ public class CreateEventFragment extends Fragment { ArrayAdapter adapter = (ArrayAdapter) spinnerCalendar.getAdapter(); if (adapter != null) { int position = adapter.getPosition(event.getCalendarName()); - if (position >= 0) { - spinnerCalendar.setSelection(position); - } + if (position >= 0) spinnerCalendar.setSelection(position); } spinnerCalendar.setEnabled(false); try { SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); - String start = event.getRawDate(); if (start != null) { if (start.length() == 10) { switchAllDay.setChecked(true); - Date d = simpleFormat.parse(start); - startCal.setTime(d); - + startCal.setTime(simpleFormat.parse(start)); if (event.getRawEndDate() != null) { - Date e = simpleFormat.parse(event.getRawEndDate()); Calendar c = Calendar.getInstance(); - c.setTime(e); + c.setTime(simpleFormat.parse(event.getRawEndDate())); c.add(Calendar.DAY_OF_MONTH, -1); endCal.setTime(c.getTime()); } else { - endCal.setTime(d); + endCal.setTime(startCal.getTime()); } } else if (start.contains("T")) { if (start.length() > 19) start = start.substring(0, 19); startCal.setTime(isoFormat.parse(start)); - String end = event.getRawEndDate(); if (end != null && end.contains("T")) { if (end.length() > 19) end = end.substring(0, 19); @@ -350,130 +280,96 @@ public class CreateEventFragment extends Fragment { } } } - List existingReminders = event.getReminders(); if (!existingReminders.isEmpty()) { for (int i = 0; i < chipGroupReminders.getChildCount(); i++) { - ((Chip) chipGroupReminders.getChildAt(i)).setChecked(false); - } - for (int min : existingReminders) { - checkChipByMinutes(min); + Chip chip = (Chip) chipGroupReminders.getChildAt(i); + chip.setChecked(existingReminders.contains((Integer) chip.getTag())); } } - } catch (Exception e) { Log.e("KBS_EDIT", "Feil ved prefill", e); } } - private void checkChipByMinutes(int minutes) { - for (int i = 0; i < chipGroupReminders.getChildCount(); i++) { - Chip chip = (Chip) chipGroupReminders.getChildAt(i); - if ((int)chip.getTag() == minutes) { - chip.setChecked(true); - } + private void setupCalendarSpinner() { + List calendars = UserManager.getInstance().getWriteableCalendars(); + if (calendars.isEmpty()) { + calendars = new ArrayList<>(); + calendars.add("Felles"); } + + ArrayAdapter adapter = new ArrayAdapter(getContext(), android.R.layout.simple_spinner_item, calendars) { + @NonNull @Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + TextView view = (TextView) super.getView(position, convertView, parent); + view.setBackgroundColor(Color.parseColor(getCalendarColor(getItem(position)))); + view.setTextColor(Color.WHITE); + view.setTypeface(null, Typeface.BOLD); + return view; + } + @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { + TextView view = (TextView) super.getDropDownView(position, convertView, parent); + view.setTextColor(Color.parseColor(getCalendarColor(getItem(position)))); + view.setTypeface(null, Typeface.BOLD); + return view; + } + }; + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinnerCalendar.setAdapter(adapter); } private String getCalendarColor(String name) { + if (name == null) return "#888888"; switch (name) { - case "Felles": return "#0069B3"; + case "Felles": return "#0069B3"; // KBS Blå + case "AMU/HMS/Miljø": return "#2E7D32"; // Mørk grønn (Miljø-profil) case "Administrasjonen": return "#607D8B"; case "Serviceavdelingen": return "#E65100"; - case "Automasjonsavdelingen": return "#2E7D32"; + case "Automasjonsavdelingen": return "#2E7D32"; // Merk: Denne er lik Miljø nå, du kan bytte til f.eks #1B5E20 hvis ønskelig case "Prosjektavdelingen": return "#7B1FA2"; default: return "#888888"; } } - private void setupCalendarSpinner() { - List calendars = UserManager.getInstance().getWriteableCalendars(); - if (calendars.isEmpty()) calendars.add("Felles"); - - ArrayAdapter adapter = new ArrayAdapter(getContext(), android.R.layout.simple_spinner_item, calendars) { - @NonNull - @Override - public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { - TextView view = (TextView) super.getView(position, convertView, parent); - String calName = getItem(position); - String colorHex = getCalendarColor(calName); - view.setBackgroundColor(Color.parseColor(colorHex)); - view.setTextColor(Color.WHITE); - view.setTypeface(null, Typeface.BOLD); - return view; - } - - @Override - public View getDropDownView(int position, View convertView, ViewGroup parent) { - TextView view = (TextView) super.getDropDownView(position, convertView, parent); - String calName = getItem(position); - String colorHex = getCalendarColor(calName); - view.setBackgroundColor(Color.WHITE); - view.setTextColor(Color.parseColor(colorHex)); - view.setTypeface(null, Typeface.BOLD); - return view; - } - }; - - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinnerCalendar.setAdapter(adapter); - } - private void setupReminderChips() { - addChip("Ved start", 0); - addChip("5 min", 5); - addChip("10 min", 10); - addChip("15 min", 15); - addChip("30 min", 30); - addChip("1 time", 60); - addChip("2 timer", 120); - addChip("1 dag", 1440); - addChip("2 dager", 2880); - addChip("1 uke", 10080); + chipGroupReminders.removeAllViews(); + addReminderChip("Ved start", 0); + addReminderChip("5 min", 5); + addReminderChip("10 min", 10); + addReminderChip("15 min", 15); + addReminderChip("30 min", 30); + addReminderChip("1 t", 60); + addReminderChip("2 t", 120); + addReminderChip("1 d", 1440); + addReminderChip("2 d", 2880); + addReminderChip("1 u", 10080); - if (eventToEdit == null) checkChipByMinutes(15); + if (eventToEdit == null) { + for (int i=0; i getSelectedReminders() { - List selected = new ArrayList<>(); - for (int i = 0; i < chipGroupReminders.getChildCount(); i++) { - Chip chip = (Chip) chipGroupReminders.getChildAt(i); - if (chip.isChecked()) { - selected.add((Integer) chip.getTag()); - } - } - return selected; - } - private void updateRecurrenceSpinner() { String dayName = new SimpleDateFormat("EEEE", new Locale("no")).format(startCal.getTime()); int dayOfMonth = startCal.get(Calendar.DAY_OF_MONTH); String monthName = new SimpleDateFormat("MMMM", new Locale("no")).format(startCal.getTime()); - int weekNo = (startCal.get(Calendar.DAY_OF_MONTH) - 1) / 7 + 1; - String nthDayString = "månedlig den " + weekNo + ". " + dayName + "en"; - List options = new ArrayList<>(); - options.add("Ikke gjenta"); - options.add("Daglig"); - options.add("Ukentlig på " + dayName); - options.add("Månedlig den " + dayOfMonth + "."); - options.add(capitalize(nthDayString)); - options.add("Årlig den " + dayOfMonth + ". " + monthName); - options.add("Hver ukedag (man-fre)"); - options.add("Egendefinert..."); - - ArrayAdapter adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, options); - spinnerRecurrence.setAdapter(adapter); + options.add("Ikke gjenta"); options.add("Daglig"); options.add("Ukentlig på " + dayName); + options.add("Månedlig den " + dayOfMonth + "."); options.add("Månedlig den " + weekNo + ". " + dayName + "en"); + options.add("Årlig den " + dayOfMonth + ". " + monthName); options.add("Hver ukedag (man-fre)"); options.add("Egendefinert..."); + spinnerRecurrence.setAdapter(new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, options)); } private void generateStandardRRule(int position) { @@ -481,112 +377,59 @@ public class CreateEventFragment extends Fragment { case 1: selectedRRule = "RRULE:FREQ=DAILY"; break; case 2: selectedRRule = "RRULE:FREQ=WEEKLY"; break; case 3: selectedRRule = "RRULE:FREQ=MONTHLY"; break; - case 4: - int weekNo = (startCal.get(Calendar.DAY_OF_MONTH) - 1) / 7 + 1; - String dayCode = getDayCode(startCal.get(Calendar.DAY_OF_WEEK)); - selectedRRule = "RRULE:FREQ=MONTHLY;BYDAY=" + weekNo + dayCode; - break; + case 4: selectedRRule = "RRULE:FREQ=MONTHLY;BYDAY=" + ((startCal.get(Calendar.DAY_OF_MONTH)-1)/7+1) + getDayCode(startCal.get(Calendar.DAY_OF_WEEK)); break; case 5: selectedRRule = "RRULE:FREQ=YEARLY"; break; case 6: selectedRRule = "RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR"; break; default: selectedRRule = null; } } + private String getDayCode(int calendarDay) { + switch (calendarDay) { + case Calendar.MONDAY: return "MO"; case Calendar.TUESDAY: return "TU"; case Calendar.WEDNESDAY: return "WE"; + case Calendar.THURSDAY: return "TH"; case Calendar.FRIDAY: return "FR"; case Calendar.SATURDAY: return "SA"; + case Calendar.SUNDAY: return "SU"; default: return "MO"; + } + } + private void showCustomRecurrenceDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_custom_recurrence, null); - - EditText etInterval = view.findViewById(R.id.et_interval); + EditText etInterval = view.findViewById(R.id.et_interval), etCount = view.findViewById(R.id.et_count); Spinner spinnerFreq = view.findViewById(R.id.spinner_freq); View layoutWeekdays = view.findViewById(R.id.layout_weekdays); - - ToggleButton[] toggles = { - view.findViewById(R.id.tg_mon), view.findViewById(R.id.tg_tue), - view.findViewById(R.id.tg_wed), view.findViewById(R.id.tg_thu), - view.findViewById(R.id.tg_fri), view.findViewById(R.id.tg_sat), - view.findViewById(R.id.tg_sun) - }; + ToggleButton[] toggles = {view.findViewById(R.id.tg_mon), view.findViewById(R.id.tg_tue), view.findViewById(R.id.tg_wed), view.findViewById(R.id.tg_thu), view.findViewById(R.id.tg_fri), view.findViewById(R.id.tg_sat), view.findViewById(R.id.tg_sun)}; String[] labels = {"M", "T", "O", "T", "F", "L", "S"}; for(int i=0; i<7; i++) { toggles[i].setText(labels[i]); toggles[i].setTextOn(labels[i]); toggles[i].setTextOff(labels[i]); } - - RadioGroup rgEnd = view.findViewById(R.id.rg_end); - Button btnEndDatePicker = view.findViewById(R.id.btn_end_date_picker); - EditText etCount = view.findViewById(R.id.et_count); - Calendar customEndCal = Calendar.getInstance(); - customEndCal.add(Calendar.MONTH, 1); - - SimpleDateFormat sdf = new SimpleDateFormat("d. MMM yyyy", Locale.getDefault()); - btnEndDatePicker.setText(sdf.format(customEndCal.getTime())); - + RadioGroup rgEnd = view.findViewById(R.id.rg_end); Button btnEndDatePicker = view.findViewById(R.id.btn_end_date_picker); + Calendar customEndCal = Calendar.getInstance(); customEndCal.add(Calendar.MONTH, 1); + btnEndDatePicker.setText(new SimpleDateFormat("d. MMM yyyy", Locale.getDefault()).format(customEndCal.getTime())); btnEndDatePicker.setOnClickListener(v -> { rgEnd.check(R.id.rb_date); - new DatePickerDialog(getContext(), (p, y, m, d) -> { - customEndCal.set(y, m, d); - btnEndDatePicker.setText(sdf.format(customEndCal.getTime())); - }, customEndCal.get(Calendar.YEAR), customEndCal.get(Calendar.MONTH), customEndCal.get(Calendar.DAY_OF_MONTH)).show(); + new DatePickerDialog(getContext(), (p, y, m, d) -> { customEndCal.set(y, m, d); btnEndDatePicker.setText(new SimpleDateFormat("d. MMM yyyy", Locale.getDefault()).format(customEndCal.getTime())); }, customEndCal.get(Calendar.YEAR), customEndCal.get(Calendar.MONTH), customEndCal.get(Calendar.DAY_OF_MONTH)).show(); }); - spinnerFreq.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - layoutWeekdays.setVisibility(position == 1 ? View.VISIBLE : View.GONE); - } - @Override public void onNothingSelected(AdapterView parent) {} + @Override public void onItemSelected(AdapterView p, View v, int pos, long id) { layoutWeekdays.setVisibility(pos == 1 ? View.VISIBLE : View.GONE); } + @Override public void onNothingSelected(AdapterView p) {} }); - - builder.setView(view); - AlertDialog dialog = builder.create(); - - view.findViewById(R.id.btn_cancel).setOnClickListener(v -> { - dialog.dismiss(); - spinnerRecurrence.setSelection(0); - }); - + builder.setView(view); AlertDialog dialog = builder.create(); + view.findViewById(R.id.btn_cancel).setOnClickListener(v -> { dialog.dismiss(); spinnerRecurrence.setSelection(0); }); view.findViewById(R.id.btn_done).setOnClickListener(v -> { - StringBuilder rrule = new StringBuilder("RRULE:"); - int freqPos = spinnerFreq.getSelectedItemPosition(); - String freq = ""; - switch (freqPos) { - case 0: freq = "DAILY"; break; - case 1: freq = "WEEKLY"; break; - case 2: freq = "MONTHLY"; break; - case 3: freq = "YEARLY"; break; + StringBuilder rrule = new StringBuilder("RRULE:FREQ="); + rrule.append(new String[]{"DAILY", "WEEKLY", "MONTHLY", "YEARLY"}[spinnerFreq.getSelectedItemPosition()]); + if (!etInterval.getText().toString().isEmpty() && !etInterval.getText().toString().equals("1")) rrule.append(";INTERVAL=").append(etInterval.getText().toString()); + if (spinnerFreq.getSelectedItemPosition() == 1) { + List days = new ArrayList<>(); String[] codes = {"MO", "TU", "WE", "TH", "FR", "SA", "SU"}; + for (int i=0; i<7; i++) if (toggles[i].isChecked()) days.add(codes[i]); + if (!days.isEmpty()) rrule.append(";BYDAY=").append(String.join(",", days)); } - rrule.append("FREQ=").append(freq); - - String interval = etInterval.getText().toString(); - if (!interval.isEmpty() && !interval.equals("1")) { - rrule.append(";INTERVAL=").append(interval); - } - - if (freq.equals("WEEKLY")) { - List days = new ArrayList<>(); - String[] codes = {"MO", "TU", "WE", "TH", "FR", "SA", "SU"}; - for (int i=0; i<7; i++) { - if (toggles[i].isChecked()) days.add(codes[i]); - } - if (!days.isEmpty()) { - rrule.append(";BYDAY=").append(String.join(",", days)); - } - } - - int checkedId = rgEnd.getCheckedRadioButtonId(); - if (checkedId == R.id.rb_date) { - customEndCal.set(Calendar.HOUR_OF_DAY, 23); - customEndCal.set(Calendar.MINUTE, 59); - customEndCal.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); - SimpleDateFormat utc = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US); - utc.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); + if (rgEnd.getCheckedRadioButtonId() == R.id.rb_date) { + customEndCal.set(Calendar.HOUR_OF_DAY, 23); customEndCal.set(Calendar.MINUTE, 59); + SimpleDateFormat utc = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US); utc.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); rrule.append(";UNTIL=").append(utc.format(customEndCal.getTime())); - } else if (checkedId == R.id.rb_count) { - rrule.append(";COUNT=").append(etCount.getText().toString()); - } - - selectedRRule = rrule.toString(); - isCustomRecurrence = true; - dialog.dismiss(); + } else if (rgEnd.getCheckedRadioButtonId() == R.id.rb_count) rrule.append(";COUNT=").append(etCount.getText().toString()); + selectedRRule = rrule.toString(); isCustomRecurrence = true; dialog.dismiss(); }); - dialog.show(); } @@ -594,12 +437,8 @@ public class CreateEventFragment extends Fragment { new DatePickerDialog(getContext(), (view, y, m, d) -> { cal.set(y, m, d); if (isStart) { - if (endCal.before(startCal)) { - endCal.setTime(startCal.getTime()); - if (!switchAllDay.isChecked()) endCal.add(Calendar.HOUR_OF_DAY, 1); - } - updateRecurrenceSpinner(); - if (!isCustomRecurrence) spinnerRecurrence.setSelection(0); + if (endCal.before(startCal)) { endCal.setTime(startCal.getTime()); if (!switchAllDay.isChecked()) endCal.add(Calendar.HOUR_OF_DAY, 1); } + updateRecurrenceSpinner(); if (!isCustomRecurrence) spinnerRecurrence.setSelection(0); } updateUI(); }, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).show(); @@ -607,165 +446,57 @@ public class CreateEventFragment extends Fragment { private void pickTime(Calendar cal) { new TimePickerDialog(getContext(), (view, h, m) -> { - cal.set(Calendar.HOUR_OF_DAY, h); - cal.set(Calendar.MINUTE, m); - if (cal == startCal && endCal.before(startCal)) { - endCal.setTime(startCal.getTime()); - endCal.add(Calendar.HOUR_OF_DAY, 1); - } + cal.set(Calendar.HOUR_OF_DAY, h); cal.set(Calendar.MINUTE, m); + if (cal == startCal && endCal.before(startCal)) { endCal.setTime(startCal.getTime()); endCal.add(Calendar.HOUR_OF_DAY, 1); } updateUI(); }, cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), true).show(); } private void updateUI() { boolean isAllDay = switchAllDay.isChecked(); - btnStartTime.setVisibility(isAllDay ? View.GONE : View.VISIBLE); btnEndTime.setVisibility(isAllDay ? View.GONE : View.VISIBLE); - - SimpleDateFormat dateFmt = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()); - SimpleDateFormat timeFmt = new SimpleDateFormat("HH:mm", Locale.getDefault()); - - btnStartDate.setText(dateFmt.format(startCal.getTime())); - btnEndDate.setText(dateFmt.format(endCal.getTime())); - btnStartTime.setText(timeFmt.format(startCal.getTime())); - btnEndTime.setText(timeFmt.format(endCal.getTime())); - - String preview = dateFmt.format(startCal.getTime()); - if (!isAllDay) preview += " " + timeFmt.format(startCal.getTime()); - preview += " - "; - - if (isSameDay(startCal, endCal)) { - if (isAllDay) preview += " (Samme dag)"; - else preview += timeFmt.format(endCal.getTime()); - } else { - preview += dateFmt.format(endCal.getTime()); - if (!isAllDay) preview += " " + timeFmt.format(endCal.getTime()); - } - txtPreview.setText(preview); - } - - private boolean isSameDay(Calendar c1, Calendar c2) { - return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR) && - c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR); - } - - private String capitalize(String s) { - if (s == null || s.isEmpty()) return s; - return s.substring(0, 1).toUpperCase() + s.substring(1); - } - - private String getDayCode(int calendarDay) { - switch (calendarDay) { - case Calendar.MONDAY: return "MO"; - case Calendar.TUESDAY: return "TU"; - case Calendar.WEDNESDAY: return "WE"; - case Calendar.THURSDAY: return "TH"; - case Calendar.FRIDAY: return "FR"; - case Calendar.SATURDAY: return "SA"; - case Calendar.SUNDAY: return "SU"; - default: return "MO"; - } - } - - private String getCalendarSlug() { - if (spinnerCalendar.getSelectedItem() != null) { - return spinnerCalendar.getSelectedItem().toString(); - } - return "Felles"; + SimpleDateFormat dFmt = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()), tFmt = new SimpleDateFormat("HH:mm", Locale.getDefault()); + btnStartDate.setText(dFmt.format(startCal.getTime())); btnEndDate.setText(dFmt.format(endCal.getTime())); + btnStartTime.setText(tFmt.format(startCal.getTime())); btnEndTime.setText(tFmt.format(endCal.getTime())); + String p = dFmt.format(startCal.getTime()) + (isAllDay ? "" : " " + tFmt.format(startCal.getTime())) + " - "; + if (startCal.get(Calendar.YEAR) == endCal.get(Calendar.YEAR) && startCal.get(Calendar.DAY_OF_YEAR) == endCal.get(Calendar.DAY_OF_YEAR)) { + p += (isAllDay ? "(Samme dag)" : tFmt.format(endCal.getTime())); + } else p += dFmt.format(endCal.getTime()) + (isAllDay ? "" : " " + tFmt.format(endCal.getTime())); + txtPreview.setText(p); } private void submitEvent() { String title = etTitle.getText().toString().trim(); - if (title.isEmpty()) { - etTitle.setError("Mangler tittel"); - return; + if (title.isEmpty()) { etTitle.setError("Mangler tittel"); return; } + SimpleDateFormat sdf = new SimpleDateFormat(switchAllDay.isChecked() ? "yyyy-MM-dd" : "yyyy-MM-dd'T'HH:mm", Locale.getDefault()); + List reminders = new ArrayList<>(); + for (int i=0; i reminders = getSelectedReminders(); - - if (rbSpecific.isChecked() && allUsers != null) { - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (User u : allUsers) { - if (u.isSelected()) { - if (!first) sb.append(","); - sb.append(u.getEmail()); - first = false; - } - } - if (sb.length() > 0) { - description += "\n\n#deltakere:" + sb.toString(); - } + String desc = etDesc.getText().toString(); + if (rbSpecific.isChecked()) { + StringBuilder sb = new StringBuilder(); boolean first = true; + for (User u : filteredUsers) if (u.isSelected()) { if (!first) sb.append(","); sb.append(u.getEmail()); first = false; } + if (sb.length() > 0) desc += "\n\n#deltakere:" + sb.toString(); } + desc += "\n#arrangor:" + (originalOrganizer != null ? originalOrganizer : UserManager.getInstance().getUserDisplayName()); - // NYTT: Legg til arrangør - String organizerTag; - if (originalOrganizer != null) { - organizerTag = originalOrganizer; // Behold opprinnelig ved redigering - } else { - organizerTag = UserManager.getInstance().getUserDisplayName(); // Ny hendelse - } - description += "\n#arrangor:" + organizerTag; + String calSlug = spinnerCalendar.getSelectedItem() != null ? spinnerCalendar.getSelectedItem().toString() : "Felles"; - CreateEventRequest req = new CreateEventRequest( - title, description, location, startTimeStr, endTimeStr, - getCalendarSlug(), reminders, isAllDay, selectedRRule - ); - - if (eventToEdit != null) { - req.id = eventToEdit.getId(); - } - - Toast.makeText(getContext(), eventToEdit != null ? "Oppdaterer..." : "Oppretter...", Toast.LENGTH_SHORT).show(); - - final Context appContext = requireContext().getApplicationContext(); - - Call call; - if (eventToEdit != null) { - call = RetrofitClient.getApiService().updateCalendarEvent(req); - } else { - call = RetrofitClient.getApiService().createCalendarEvent(req); - } + CreateEventRequest req = new CreateEventRequest(title, desc, etLocation.getText().toString(), sdf.format(startCal.getTime()), sdf.format(endCal.getTime()), calSlug, reminders, switchAllDay.isChecked(), selectedRRule); + if (eventToEdit != null) req.id = eventToEdit.getId(); + Call call = (eventToEdit != null) ? RetrofitClient.getApiService().updateCalendarEvent(req) : RetrofitClient.getApiService().createCalendarEvent(req); call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { + @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { - Toast.makeText(getContext(), eventToEdit != null ? "Hendelse oppdatert!" : "Hendelse opprettet!", Toast.LENGTH_LONG).show(); - fetchCalendarAndSchedule(appContext); + Toast.makeText(getContext(), "Lagret!", Toast.LENGTH_LONG).show(); Navigation.findNavController(getView()).navigateUp(); - } else { - Toast.makeText(getContext(), "Feil (" + response.code() + ")", Toast.LENGTH_LONG).show(); } } - - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), "Nettverksfeil", Toast.LENGTH_SHORT).show(); - } + @Override public void onFailure(Call call, Throwable t) { Toast.makeText(getContext(), "Feil!", Toast.LENGTH_SHORT).show(); } }); } - - private void fetchCalendarAndSchedule(Context context) { - new Thread(() -> { - try { - if (context == null) return; - Response> response = RetrofitClient.getApiService().getCalendarEvents().execute(); - if (response.isSuccessful() && response.body() != null) { - AlarmScheduler.scheduleAlarmsForEvents(context, response.body()); - } - } catch (Exception e) { - Log.e("CreateEvent", "Kunne ikke oppdatere alarmer", e); - } - }).start(); - } } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/UserFilterHelper.java b/app/src/main/java/com/kbs/kbsintranett/UserFilterHelper.java new file mode 100644 index 0000000..475d0c8 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/UserFilterHelper.java @@ -0,0 +1,80 @@ +package com.kbs.kbsintranett; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class UserFilterHelper { + + private static final List EXCLUDED_IDS = Arrays.asList(50, 51); // felles@kbs.no og kbs@kbs.no + private static final String REQUIRED_DOMAIN = "@kbs.no"; + + public static List getFilteredUsers(List allUsers) { + if (allUsers == null) return new ArrayList<>(); + + UserManager me = UserManager.getInstance(); + String myEmail = me.getUserEmail(); + + List sanitizedList = new ArrayList<>(); + for (User u : allUsers) { + String email = u.getEmail() != null ? u.getEmail().toLowerCase() : ""; + if (!email.endsWith(REQUIRED_DOMAIN)) continue; + if (EXCLUDED_IDS.contains(u.getId())) continue; + if (u.getRoles() == null || u.getRoles().isEmpty()) continue; + + sanitizedList.add(u); + } + + if (me.isEditorOrAbove()) { + return sanitizedList; + } + + List finalResult = new ArrayList<>(); + List myRoles = getRolesForEmail(sanitizedList, myEmail); + + // NY ROLLE LAGT TIL I LISTEN: amuhmsmiljogruppa + List deptRoles = Arrays.asList( + "serviceavdelingen", + "automasjonsavdelingen", + "prosjektavdelingen", + "administrasjonen", + "kbs_alle", + "amuhmsmiljogruppa" + ); + + for (User u : sanitizedList) { + if (u.getEmail().equalsIgnoreCase(myEmail)) { + finalResult.add(u); + continue; + } + + boolean sharesDepartment = false; + for (String role : u.getRoles()) { + String r = role.toLowerCase(); + if (deptRoles.contains(r) && myRoles.contains(r)) { + sharesDepartment = true; + break; + } + } + + if (sharesDepartment) { + finalResult.add(u); + } + } + + return finalResult; + } + + private static List getRolesForEmail(List users, String email) { + List roles = new ArrayList<>(); + for (User u : users) { + if (u.getEmail().equalsIgnoreCase(email)) { + if (u.getRoles() != null) { + for (String r : u.getRoles()) roles.add(r.toLowerCase()); + } + break; + } + } + return roles; + } +} \ No newline at end of file diff --git a/hele_prosjektet.txt b/hele_prosjektet.txt deleted file mode 100644 index 9acc00c..0000000 --- a/hele_prosjektet.txt +++ /dev/null @@ -1,9621 +0,0 @@ -Dette er kildekoden til et Android Studio-prosjekt. -Hver fil er separert med overskrifter. - - -============================================================ -FILSTI: build.gradle.kts -============================================================ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -plugins { - alias(libs.plugins.android.application) apply false - // NY LINJE: Legg til Google Services plugin her - id("com.google.gms.google-services") version "4.4.2" apply false -} - - -============================================================ -FILSTI: settings.gradle.kts -============================================================ -pluginManagement { - repositories { - google { - content { - includeGroupByRegex("com\\.android.*") - includeGroupByRegex("com\\.google.*") - includeGroupByRegex("androidx.*") - } - } - mavenCentral() - gradlePluginPortal() - } -} -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - google() - mavenCentral() - } -} - -rootProject.name = "KBS Intranett" -include(":app") - - -============================================================ -FILSTI: app\build.gradle.kts -============================================================ -plugins { - alias(libs.plugins.android.application) - // NY LINJE: Aktiver Google Services plugin her - id("com.google.gms.google-services") -} - -android { - namespace = "com.kbs.kbsintranett" - compileSdk = 34 - - defaultConfig { - applicationId = "com.kbs.kbsintranett" - minSdk = 28 - targetSdk = 34 - versionCode = 4 - versionName = "1.5.1" - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - // NYTT: Dette må til for å kunne bruke BuildConfig.DEBUG i koden - buildFeatures { - buildConfig = true - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } -} - -dependencies { - implementation(libs.appcompat) - implementation(libs.material) - implementation(libs.activity) - implementation(libs.constraintlayout) - testImplementation(libs.junit) - androidTestImplementation(libs.ext.junit) - androidTestImplementation(libs.espresso.core) - - // Nettverk og JSON-håndtering - implementation("com.squareup.retrofit2:retrofit:2.9.0") - implementation("com.squareup.retrofit2:converter-gson:2.9.0") - implementation("com.google.code.gson:gson:2.10.1") - - // Navigation Component - val navVersion = "2.8.5" - implementation("androidx.navigation:navigation-fragment:$navVersion") - implementation("androidx.navigation:navigation-ui:$navVersion") - - implementation("com.google.android.gms:play-services-auth:20.7.0") - implementation("com.github.bumptech.glide:glide:4.16.0") - implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") - - implementation("androidx.work:work-runtime:2.9.0") - - // Swipe Refresh Layout - implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") - - // NY LINJE: Firebase BOM (Bill of Materials) styrer versjoner - implementation(platform("com.google.firebase:firebase-bom:33.1.2")) - - // NY LINJE: (Valgfritt, men lurt for statistikk) - implementation("com.google.firebase:firebase-analytics") - - // NYTT: Firebase Cloud Messaging lagt til her - implementation("com.google.firebase:firebase-messaging") -} - -============================================================ -FILSTI: app\proguard-rules.pro -============================================================ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile - -============================================================ -FILSTI: app\src\androidTest\java\com\kbs\kbsintranett\ExampleInstrumentedTest.java -============================================================ -package com.kbs.kbsintranett; - -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - assertEquals("com.kbs.kbsintranett", appContext.getPackageName()); - } -} - -============================================================ -FILSTI: app\src\main\AndroidManifest.xml -============================================================ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\AlarmReceiver.java -============================================================ -package com.kbs.kbsintranett; - -import android.Manifest; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.os.Build; -import androidx.core.app.ActivityCompat; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; -import androidx.core.content.ContextCompat; // <-- Denne manglet - -public class AlarmReceiver extends BroadcastReceiver { - - private static final String CHANNEL_ID = "kbs_calendar_channel"; - private static final String CHANNEL_NAME = "KBS Kalendervarsler"; - - @Override - public void onReceive(Context context, Intent intent) { - String title = intent.getStringExtra("TITLE"); - String message = intent.getStringExtra("MESSAGE"); - int notificationId = intent.getIntExtra("ID", 0); - - createNotificationChannel(context); - showNotification(context, title, message, notificationId); - } - - private void showNotification(Context context, String title, String message, int notificationId) { - // Sjekk rettigheter for Android 13+ - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - // Vi kan ikke vise varsel uten rettighet. - return; - } - } - - // Intent for hva som skjer når man trykker på varselet (åpne appen) - Intent tapIntent = new Intent(context, MainActivity.class); - tapIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - PendingIntent pendingIntent = PendingIntent.getActivity( - context, - 0, - tapIntent, - PendingIntent.FLAG_IMMUTABLE - ); - - NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_stat_kbs) - .setColor(ContextCompat.getColor(context, R.color.kbs_logo_blue)) // Setter KBS-blå farge på ikonet - .setContentTitle(title) - .setContentText(message) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setContentIntent(pendingIntent) - .setAutoCancel(true); - - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); - notificationManager.notify(notificationId, builder.build()); - } - - private void createNotificationChannel(Context context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - int importance = NotificationManager.IMPORTANCE_HIGH; - NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, importance); - channel.setDescription("Varsler for kalenderhendelser i KBS Intranett"); - - NotificationManager notificationManager = context.getSystemService(NotificationManager.class); - if (notificationManager != null) { - notificationManager.createNotificationChannel(channel); - } - } - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\AlarmScheduler.java -============================================================ -package com.kbs.kbsintranett; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Build; -import android.util.Log; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; -import java.util.Locale; - -public class AlarmScheduler { - - private static final String TAG = "AlarmScheduler"; - private static final String PREFS_NAME = "kbs_alarm_history"; - - /** - * Denne metoden går gjennom en liste hendelser og setter alarmer for dem. - */ - public static void scheduleAlarmsForEvents(Context context, List events) { - AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - - // Sjekk rettigheter for Android 12+ (Exact Alarm) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) { - Log.w(TAG, "Mangler rettighet til å sette nøyaktige alarmer."); - return; - } - - SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - long now = System.currentTimeMillis(); - SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); - - // Vi ser etter hendelser 30 dager frem i tid - long futureWindow = now + (30L * 24 * 60 * 60 * 1000L); - - for (CalendarEvent event : events) { - try { - // Hopp over hvis ingen dato eller heldags (uten tidspunkt) - if (event.getRawDate() == null || event.getRawDate().length() == 10) continue; - - Date eventDate = null; - if (event.getRawDate().contains("T")) { - String raw = event.getRawDate(); - if (raw.length() > 19) raw = raw.substring(0, 19); - eventDate = isoFormat.parse(raw); - } - - if (eventDate == null) continue; - - // Loop gjennom alle varsler (f.eks. 15 min før, 60 min før) - for (int minutesBefore : event.getReminders()) { - if (minutesBefore < 0) continue; - - long triggerTime = eventDate.getTime() - (minutesBefore * 60 * 1000L); - String alarmKey = "alarm_" + event.getId() + "_" + triggerTime; - - // Hvis tidspunktet er i fremtiden (og innenfor vinduet) - if (triggerTime > now && triggerTime < futureWindow) { - - // Sjekk om vi allerede har satt denne alarmen for å unngå dobbeltarbeid - if (prefs.getBoolean(alarmKey, false)) { - continue; - } - - int alarmId = alarmKey.hashCode(); // Unik ID basert på hendelse+tid - - Intent intent = new Intent(context, AlarmReceiver.class); - intent.putExtra("TITLE", event.getTitle()); - String timeStr = new SimpleDateFormat("HH:mm", Locale.getDefault()).format(eventDate); - intent.putExtra("MESSAGE", "Starter kl " + timeStr); - intent.putExtra("ID", alarmId); - - PendingIntent pendingIntent = PendingIntent.getBroadcast( - context, - alarmId, - intent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE - ); - - // Sett alarmen - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); - } else { - alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); - } - - // Marker som satt - prefs.edit().putBoolean(alarmKey, true).apply(); - Log.d(TAG, "Alarm satt for " + event.getTitle() + " om " + minutesBefore + " min."); - } - } - } catch (Exception e) { - Log.e(TAG, "Feil ved setting av alarm", e); - } - } - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\AuthRepository.java -============================================================ -// FILSTI: app\src\main\java\com\kbs\kbsintranett\AuthRepository.java -package com.kbs.kbsintranett; - -import android.util.Log; -import com.google.gson.JsonElement; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class AuthRepository { - - private static final String TAG = "AuthRepository"; - - // Interface for å gi beskjed tilbake til Activity/Fragment - public interface AuthCallback { - void onSuccess(String role); - void onError(String message); - } - - /** - * Utfører selve API-kallet mot WordPress. - * Denne brukes nå av både MainActivity (Silent Sign-In) og LoginFragment (Manuell). - */ - public static void loginToWordPress(String googleIdToken, String displayName, String email, String photoUrl, AuthCallback callback) { - - // 1. Lagre Google-info midlertidig - UserManager.getInstance().setUserData(displayName, email, googleIdToken, photoUrl); - - // 2. Gjør klar request - LoginRequest request = new LoginRequest(googleIdToken); - - // 3. Send til WordPress - RetrofitClient.getApiService().googleLogin(request).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful() && response.body() != null && response.body().success) { - // SUKSESS! - String cookie = response.body().fullCookie; - String role = response.body().role; - - // NYTT: Hent utvidet info fra responsen - int userId = response.body().userId; - String fName = response.body().firstName; - String lName = response.body().lastName; - String stilling = response.body().stilling; - String mobil = response.body().mobiltelefon; - - Log.d(TAG, "WordPress Login suksess! Rolle: " + role + ", UserID: " + userId); - - // Lagre cookie, rolle og ID - UserManager.getInstance().setCookie(cookie); - UserManager.getInstance().setUserRole(role); - UserManager.getInstance().setUserId(userId); - - // Lagre utvidet info i UserManager - UserManager.getInstance().setExtendedUserInfo(fName, lName, stilling, mobil); - - // Lagre listen over skrivbare kalendere - UserManager.getInstance().setWriteableCalendars(response.body().writeableCalendars); - - // NYTT: Hvis vi har en ventende FCM-token, send den nå som vi er logget inn - String pendingToken = UserManager.getInstance().getFcmToken(); - if (pendingToken != null && !pendingToken.isEmpty()) { - updateDeviceToken(pendingToken); - } - - callback.onSuccess(role); - - } else { - Log.e(TAG, "WordPress Login nektet. Kode: " + response.code()); - callback.onError("Kunne ikke logge inn på Intranettet (Kode: " + response.code() + ")"); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.e(TAG, "Nettverksfeil mot WP", t); - callback.onError("Nettverksfeil: " + t.getMessage()); - } - }); - } - - /** - * Sender FCM-token til WordPress for å registrere enheten for push-varsler. - */ - public static void updateDeviceToken(String token) { - if (!UserManager.getInstance().isLoggedIn()) { - // Hvis ikke logget inn, bare lagre den til senere - UserManager.getInstance().setFcmToken(token); - return; - } - - // Send til server - RegisterDeviceRequest request = new RegisterDeviceRequest(token); - RetrofitClient.getApiService().registerDevice(request).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - Log.d(TAG, "FCM Token registrert på server OK."); - } else { - Log.e(TAG, "Feil ved registrering av FCM Token. Kode: " + response.code()); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.e(TAG, "Nettverksfeil ved sending av FCM token", t); - } - }); - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\CacheManager.java -============================================================ -package com.kbs.kbsintranett; - -import android.content.Context; -import android.util.Log; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStreamReader; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; - -public class CacheManager { - private static final String FILE_CALENDAR = "cache_calendar.json"; - private static final String FILE_NEWS = "cache_news.json"; - private static final String FILE_HANDBOOK = "cache_handbook.json"; - - private static final String TAG = "CacheManager"; - private static final Gson gson = new Gson(); - - // --- KALENDER --- - public static void saveCalendarEvents(Context context, List events) { - saveList(context, FILE_CALENDAR, events); - } - - public static List getCachedCalendarEvents(Context context) { - Type type = new TypeToken>() {}.getType(); - List list = loadList(context, FILE_CALENDAR, type); - return list != null ? list : new ArrayList<>(); - } - - // --- NYHETER --- - public static void saveNewsPosts(Context context, List posts) { - saveList(context, FILE_NEWS, posts); - } - - public static List getCachedNewsPosts(Context context) { - Type type = new TypeToken>() {}.getType(); - List list = loadList(context, FILE_NEWS, type); - return list != null ? list : new ArrayList<>(); - } - - // --- HÅNDBOK --- - public static void saveHandbookItems(Context context, List items) { - saveList(context, FILE_HANDBOOK, items); - } - - public static List getCachedHandbookItems(Context context) { - Type type = new TypeToken>() {}.getType(); - List list = loadList(context, FILE_HANDBOOK, type); - return list != null ? list : new ArrayList<>(); - } - - // --- GENERISKE HJELPEMETODER --- - - private static void saveList(Context context, String filename, List list) { - if (context == null || list == null) return; - new Thread(() -> { - try { - String json = gson.toJson(list); - FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE); - fos.write(json.getBytes()); - fos.close(); - Log.d(TAG, "Lagret cache til " + filename); - } catch (Exception e) { - Log.e(TAG, "Feil ved lagring av cache: " + filename, e); - } - }).start(); - } - - private static List loadList(Context context, String filename, Type type) { - if (context == null) return null; - File file = new File(context.getFilesDir(), filename); - if (!file.exists()) return null; - - try { - FileInputStream fis = context.openFileInput(filename); - InputStreamReader isr = new InputStreamReader(fis); - BufferedReader bufferedReader = new BufferedReader(isr); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = bufferedReader.readLine()) != null) { - sb.append(line); - } - fis.close(); - return gson.fromJson(sb.toString(), type); - } catch (Exception e) { - Log.e(TAG, "Feil ved lesing av cache: " + filename, e); - return null; - } - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarAdapter.java -============================================================ -package com.kbs.kbsintranett; - -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; -import java.util.List; - -public class CalendarAdapter extends RecyclerView.Adapter { - - private List events; - private final OnItemClickListener listener; - - // Farge for individuelle/private hendelser (Deep Purple) - private static final String PRIVATE_EVENT_COLOR = "#673AB7"; - - public interface OnItemClickListener { - void onItemClick(CalendarEvent event); - } - - public CalendarAdapter(List events, OnItemClickListener listener) { - this.events = events; - this.listener = listener; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_calendar, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - CalendarEvent event = events.get(position); - holder.day.setText(event.getDay()); - holder.month.setText(event.getMonth()); - holder.time.setText(event.getTime()); - holder.title.setText(event.getTitle()); - - // NYTT: Sjekk om hendelsen er "privat" (har deltakere) - boolean isPrivate = event.getDescription() != null && event.getDescription().contains("#deltakere:"); - - try { - int color; - if (isPrivate) { - // Bruk privat farge - color = Color.parseColor(PRIVATE_EVENT_COLOR); - } else { - // Bruk kalenderens standardfarge - color = Color.parseColor(event.getCalendarColor()); - } - holder.dateBox.setBackgroundTintList(ColorStateList.valueOf(color)); - } catch (Exception e) { - // Fallback til standard blå hvis fargekoden er ugyldig - holder.dateBox.setBackgroundTintList(ColorStateList.valueOf(Color.parseColor("#0069B3"))); - } - - holder.itemView.setOnClickListener(v -> { - if (listener != null) { - listener.onItemClick(event); - } - }); - } - - @Override - public int getItemCount() { - return events.size(); - } - - public static class ViewHolder extends RecyclerView.ViewHolder { - TextView day, month, title, time; - LinearLayout dateBox; - - public ViewHolder(View view) { - super(view); - day = view.findViewById(R.id.cal_day); - month = view.findViewById(R.id.cal_month); - title = view.findViewById(R.id.cal_title); - time = view.findViewById(R.id.cal_time); - dateBox = view.findViewById(R.id.date_box_background); - } - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarDetailsBottomSheet.java -============================================================ -package com.kbs.kbsintranett; - -import android.app.AlertDialog; -import android.content.Intent; -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import android.text.Html; -import android.text.method.LinkMovementMethod; -import android.text.util.Linkify; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.navigation.fragment.NavHostFragment; -import com.google.android.material.bottomsheet.BottomSheetDialogFragment; -import com.google.gson.JsonElement; -import java.util.ArrayList; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class CalendarDetailsBottomSheet extends BottomSheetDialogFragment { - private CalendarEvent event; - private OnEventChangeListener changeListener; - - private static final String PRIVATE_EVENT_COLOR = "#673AB7"; - - public interface OnEventChangeListener { - void onEventChanged(); - } - - public CalendarDetailsBottomSheet(CalendarEvent event) { - this.event = event; - } - - public void setOnEventChangeListener(OnEventChangeListener listener) { - this.changeListener = listener; - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.bottom_sheet_calendar_details, container, false); - - TextView title = view.findViewById(R.id.sheet_title); - TextView time = view.findViewById(R.id.sheet_time); - TextView desc = view.findViewById(R.id.sheet_desc); - TextView loc = view.findViewById(R.id.sheet_location); - TextView calName = view.findViewById(R.id.sheet_calendar_name); - TextView participantsView = view.findViewById(R.id.sheet_participants); - TextView organizerView = view.findViewById(R.id.sheet_organizer); // NYTT - - LinearLayout adminLayout = view.findViewById(R.id.layout_admin_buttons); - Button btnDelete = view.findViewById(R.id.btn_delete); - Button btnEdit = view.findViewById(R.id.btn_edit); - - title.setText(event.getTitle()); - time.setText(event.getTime() + " (" + event.getDay() + ". " + event.getMonth() + ")"); - - // Sjekk om privat - boolean isPrivate = event.getDescription() != null && event.getDescription().contains("#deltakere:"); - - if (isPrivate) { - calName.setText(event.getCalendarName().toUpperCase() + " (BEGRENSET INNSYN)"); - try { - calName.setBackgroundTintList(ColorStateList.valueOf(Color.parseColor(PRIVATE_EVENT_COLOR))); - } catch (Exception e) {} - - showParticipants(event.getDescription(), participantsView); - - } else { - calName.setText(event.getCalendarName().toUpperCase()); - try { - int color = Color.parseColor(event.getCalendarColor()); - calName.setBackgroundTintList(ColorStateList.valueOf(color)); - } catch (Exception e) {} - - participantsView.setVisibility(View.GONE); - } - - // VIS ARRANGØR ALLTID - showOrganizer(event.getDescription(), organizerView); - - // --- BESKRIVELSE OG LENKER --- - if (!event.getDescription().isEmpty()) { - String cleanDesc = event.getDescription() - .replaceAll("#varsel:[\\d,]+", "") - .replaceAll("#deltakere:[^\\s]+", "") - .replaceAll("#arrangor:.+", "") // Fjern arrangør fra brødtekst - .trim(); - - if (!cleanDesc.isEmpty()) { - desc.setText(Html.fromHtml(cleanDesc, Html.FROM_HTML_MODE_COMPACT)); - Linkify.addLinks(desc, Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS); - desc.setMovementMethod(LinkMovementMethod.getInstance()); - desc.setVisibility(View.VISIBLE); - } else { - desc.setVisibility(View.GONE); - } - } else { - desc.setVisibility(View.GONE); - } - - // --- ADRESSE OG KART --- - if (!event.getLocation().isEmpty()) { - loc.setText(event.getLocation()); - loc.setVisibility(View.VISIBLE); - loc.setOnClickListener(v -> { - String location = event.getLocation(); - Uri gmmIntentUri = Uri.parse("geo:0,0?q=" + Uri.encode(location)); - Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri); - mapIntent.setPackage("com.google.android.apps.maps"); - try { - startActivity(mapIntent); - } catch (Exception e) { - mapIntent.setPackage(null); - try { - startActivity(mapIntent); - } catch (Exception ex) { - Toast.makeText(getContext(), "Fant ingen kart-app", Toast.LENGTH_SHORT).show(); - } - } - }); - } else { - loc.setVisibility(View.GONE); - } - - // Sjekk admin-rettigheter - if (UserManager.getInstance().isEditorOrAbove()) { - boolean canEdit = UserManager.getInstance().getWriteableCalendars().contains(event.getCalendarName()) - || UserManager.getInstance().isAdmin(); - - if (canEdit) { - adminLayout.setVisibility(View.VISIBLE); - btnDelete.setOnClickListener(v -> confirmDelete()); - btnEdit.setOnClickListener(v -> { - Bundle bundle = new Bundle(); - bundle.putSerializable("edit_event", event); - NavHostFragment.findNavController(this).navigate(R.id.navigation_create_event, bundle); - dismiss(); - }); - } else { - adminLayout.setVisibility(View.GONE); - } - } - - return view; - } - - private void showParticipants(String rawDescription, TextView view) { - if (rawDescription == null) return; - Matcher m = Pattern.compile("#deltakere:([^\\s]+)").matcher(rawDescription); - if (m.find()) { - String allEmails = m.group(1); - String[] emails = allEmails.split(","); - StringBuilder sb = new StringBuilder(); - sb.append("Synlig for:
"); - for (String email : emails) { - sb.append("• ").append(email.trim()).append("
"); - } - view.setText(Html.fromHtml(sb.toString(), Html.FROM_HTML_MODE_COMPACT)); - view.setVisibility(View.VISIBLE); - } - } - - // NYTT: Vis arrangør - private void showOrganizer(String rawDescription, TextView view) { - if (rawDescription == null) return; - Matcher m = Pattern.compile("#arrangor:(.+)").matcher(rawDescription); - if (m.find()) { - String organizer = m.group(1).trim(); - view.setText("Invitert av: " + organizer); - view.setVisibility(View.VISIBLE); - } else { - view.setVisibility(View.GONE); - } - } - - private void confirmDelete() { - new AlertDialog.Builder(getContext()) - .setTitle("Slett hendelse") - .setMessage("Er du sikker på at du vil slette '" + event.getTitle() + "'?") - .setPositiveButton("Slett", (dialog, which) -> deleteEvent()) - .setNegativeButton("Avbryt", null) - .show(); - } - - private void deleteEvent() { - CreateEventRequest req = new CreateEventRequest( - "", "", "", "", "", event.getCalendarName(), new ArrayList<>(), false, "" - ); - req.id = event.getId(); - - RetrofitClient.getApiService().deleteCalendarEvent(req).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - Toast.makeText(getContext(), "Slettet!", Toast.LENGTH_SHORT).show(); - if (changeListener != null) { - changeListener.onEventChanged(); - } - dismiss(); - } else { - Toast.makeText(getContext(), "Kunne ikke slette", Toast.LENGTH_LONG).show(); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), "Nettverksfeil", Toast.LENGTH_SHORT).show(); - } - }); - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarEvent.java -============================================================ -package com.kbs.kbsintranett; - -import com.google.gson.annotations.SerializedName; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -public class CalendarEvent implements Serializable { - @SerializedName("id") - private String id; - - @SerializedName("title") - private String title; - - @SerializedName("start_date") - private String rawDate; - - @SerializedName("end_date") - private String rawEndDate; - - @SerializedName("description") - private String description; - - @SerializedName("location") - private String location; - - @SerializedName("reminders") - private List reminders = new ArrayList<>(); - - // NYE FELTER V12.2 - @SerializedName("calendar_name") - private String calendarName; - - @SerializedName("calendar_color") - private String calendarColor; - - // UI-hjelpefelter - private String day; - private String month; - private String time; - - // Konstruktør - public CalendarEvent(String title, String rawDate, String rawEndDate, String description, String location) { - this.title = title; - this.rawDate = rawDate; - this.rawEndDate = rawEndDate; - this.description = description; - this.location = location; - } - - public String getId() { return id; } - public String getTitle() { return title; } - public String getRawDate() { return rawDate; } - public String getRawEndDate() { return rawEndDate; } - public String getDescription() { return description != null ? description : ""; } - public String getLocation() { return location != null ? location : ""; } - - public List getReminders() { - return reminders != null ? reminders : new ArrayList<>(); - } - - // NYE GETTERS/SETTERS - public String getCalendarName() { return calendarName != null ? calendarName : "Ukjent"; } - public void setCalendarName(String name) { this.calendarName = name; } - - public String getCalendarColor() { return calendarColor != null ? calendarColor : "#888888"; } - public void setCalendarColor(String color) { this.calendarColor = color; } - - // --- KOMPATIBILITETS-METODER --- - - // Denne brukes for enkle varsler - public void setReminderMinutes(int minutes) { - this.reminders = new ArrayList<>(); - if (minutes > 0) { - this.reminders.add(minutes); - } - } - - // NY METODE (Den som manglet og forårsaket krasj) - // Lar oss sette hele listen med varsler på en gang - public void setRemindersList(List reminders) { - this.reminders = reminders != null ? new ArrayList<>(reminders) : new ArrayList<>(); - } - - public int getReminderMinutes() { - if (reminders != null && !reminders.isEmpty()) { - return reminders.get(0); - } - return 0; - } - - // --- UI SETTERS/GETTERS --- - public String getDay() { return day; } - public void setDay(String day) { this.day = day; } - public String getMonth() { return month; } - public void setMonth(String month) { this.month = month; } - public String getTime() { return time; } - public void setTime(String time) { this.time = time; } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarFullFragment.java -============================================================ -package com.kbs.kbsintranett; - -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.navigation.Navigation; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class CalendarFullFragment extends Fragment { - - private RecyclerView recyclerView; - private ProgressBar progressBar; - private TextView emptyView; - private LinearLayoutManager layoutManager; - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_calendar_full, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - recyclerView = view.findViewById(R.id.recycler_full_calendar); - progressBar = view.findViewById(R.id.loading_full_calendar); - emptyView = view.findViewById(R.id.empty_view_calendar); - ImageView backBtn = view.findViewById(R.id.btn_back_calendar); - - layoutManager = new LinearLayoutManager(getContext()); - recyclerView.setLayoutManager(layoutManager); - backBtn.setOnClickListener(v -> Navigation.findNavController(view).navigateUp()); - } - - @Override - public void onResume() { - super.onResume(); - fetchAllEvents(); - } - - private void fetchAllEvents() { - progressBar.setVisibility(View.VISIBLE); - - new Thread(() -> { - // HER ER ENDRINGEN: isPreview = false (Hent alt) - List deviceEvents = CalendarManager.getDeviceEvents(getContext(), false); - new Handler(Looper.getMainLooper()).post(() -> fetchApiEvents(deviceEvents)); - }).start(); - } - - private void fetchApiEvents(List deviceEvents) { - RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (!isAdded()) return; - progressBar.setVisibility(View.GONE); - - List apiEvents = new ArrayList<>(); - if (response.isSuccessful() && response.body() != null) { - apiEvents = response.body(); - for (CalendarEvent e : apiEvents) { - CalendarManager.formatEventForUI(e); - } - } - - List allEvents = CalendarManager.mergeAndSort(apiEvents, deviceEvents); - - if (allEvents.isEmpty()) { - emptyView.setVisibility(View.VISIBLE); - recyclerView.setVisibility(View.GONE); - } else { - emptyView.setVisibility(View.GONE); - recyclerView.setVisibility(View.VISIBLE); - - CalendarAdapter adapter = new CalendarAdapter(allEvents, event -> { - CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event); - sheet.setOnEventChangeListener(CalendarFullFragment.this::fetchAllEvents); - sheet.show(getParentFragmentManager(), "CalendarDetails"); - }); - recyclerView.setAdapter(adapter); - - scrollToToday(allEvents); - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - if (!isAdded()) return; - progressBar.setVisibility(View.GONE); - - if (!deviceEvents.isEmpty()) { - CalendarAdapter adapter = new CalendarAdapter(deviceEvents, event -> { - CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event); - sheet.show(getParentFragmentManager(), "CalendarDetails"); - }); - recyclerView.setAdapter(adapter); - scrollToToday(deviceEvents); - } else { - emptyView.setText("Ingen hendelser funnet."); - emptyView.setVisibility(View.VISIBLE); - } - } - }); - } - - private void scrollToToday(List events) { - String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date()); - int scrollIndex = 0; - - for (int i = 0; i < events.size(); i++) { - String raw = events.get(i).getRawDate(); - if (raw != null && raw.compareTo(today) >= 0) { - scrollIndex = i; - break; - } - } - - if (scrollIndex > 0) { - layoutManager.scrollToPositionWithOffset(scrollIndex, 0); - } - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarManager.java -============================================================ -package com.kbs.kbsintranett; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.provider.CalendarContract; -import androidx.core.content.ContextCompat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import java.util.TimeZone; - -public class CalendarManager { - private static List getKbsCalendarIds(Context context) { - List ids = new ArrayList<>(); - if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) { - return ids; - } - String[] projection = new String[] { - CalendarContract.Calendars._ID, - CalendarContract.Calendars.ACCOUNT_NAME - }; - String selection = CalendarContract.Calendars.ACCOUNT_NAME + " LIKE ?"; - String[] selectionArgs = new String[] {"%@kbs.no"}; - - try (Cursor cursor = context.getContentResolver().query( - CalendarContract.Calendars.CONTENT_URI, - projection, - selection, - selectionArgs, - null - )) { - if (cursor != null) { - while (cursor.moveToNext()) { - ids.add(cursor.getString(0)); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - return ids; - } - - /** - * Henter hendelser fra enheten. - * @param context App Context - * @param isPreview Hvis true: Henter kun kommende måned (Raskt). Hvis false: Henter -1 til +6 mnd (Tregere). - * @return Liste med events - */ - public static List getDeviceEvents(Context context, boolean isPreview) { - List deviceEvents = new ArrayList<>(); - if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR) - != PackageManager.PERMISSION_GRANTED) { - return deviceEvents; - } - - List kbsCalendarIds = getKbsCalendarIds(context); - if (kbsCalendarIds.isEmpty()) { - return deviceEvents; - } - - long now = System.currentTimeMillis(); - long startMillis; - long endMillis; - - if (isPreview) { - // FORSIDEN: Optimalisert for hastighet. - // Henter fra NÅ og 30 dager frem i tid. - startMillis = now; - endMillis = now + (30L * 24 * 60 * 60 * 1000); - } else { - // FULL VISNING: Bredere tidsvindu. - // 1 mnd tilbake til 6 mnd frem. - startMillis = now - (30L * 24 * 60 * 60 * 1000); - endMillis = now + (180L * 24 * 60 * 60 * 1000); - } - - String[] projection = new String[]{ - CalendarContract.Events.TITLE, - CalendarContract.Events.DTSTART, - CalendarContract.Events.DTEND, - CalendarContract.Events.DESCRIPTION, - CalendarContract.Events.EVENT_LOCATION, - CalendarContract.Events.ALL_DAY - }; - - StringBuilder selection = new StringBuilder("("); - List selectionArgsList = new ArrayList<>(); - - for (int i = 0; i < kbsCalendarIds.size(); i++) { - selection.append(CalendarContract.Events.CALENDAR_ID).append(" = ?"); - selectionArgsList.add(kbsCalendarIds.get(i)); - if (i < kbsCalendarIds.size() - 1) { - selection.append(" OR "); - } - } - selection.append(") AND "); - selection.append(CalendarContract.Events.DTSTART).append(" >= ? AND "); - selection.append(CalendarContract.Events.DTSTART).append(" <= ?"); - - selectionArgsList.add(String.valueOf(startMillis)); - selectionArgsList.add(String.valueOf(endMillis)); - - String[] selectionArgs = selectionArgsList.toArray(new String[0]); - - try (Cursor cursor = context.getContentResolver().query( - CalendarContract.Events.CONTENT_URI, - projection, - selection.toString(), - selectionArgs, - CalendarContract.Events.DTSTART + " ASC" - )) { - if (cursor != null) { - SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); - while (cursor.moveToNext()) { - String title = cursor.getString(0); - long dtStart = cursor.getLong(1); - long dtEnd = cursor.getLong(2); - String desc = cursor.getString(3); - String loc = cursor.getString(4); - int allDay = cursor.getInt(5); - - String rawStart; - String rawEnd; - - if (allDay == 1) { - SimpleDateFormat shortFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); - rawStart = shortFormat.format(new Date(dtStart)); - rawEnd = shortFormat.format(new Date(dtEnd)); - } else { - rawStart = isoFormat.format(new Date(dtStart)); - rawEnd = isoFormat.format(new Date(dtEnd)); - } - - CalendarEvent event = new CalendarEvent(title, rawStart, rawEnd, desc, loc); - event.setReminderMinutes(0); - - event.setCalendarColor("#888888"); - event.setCalendarName("Min Kalender (Lokal)"); - - formatEventForUI(event); - deviceEvents.add(event); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - return deviceEvents; - } - - public static void formatEventForUI(CalendarEvent event) { - if (event.getRawDate() == null) return; - - SimpleDateFormat outputDay = new SimpleDateFormat("dd", Locale.getDefault()); - SimpleDateFormat outputMonth = new SimpleDateFormat("MMM", new Locale("no", "NO")); - SimpleDateFormat outputTime = new SimpleDateFormat("HH:mm", Locale.getDefault()); - - outputMonth.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); - outputTime.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); - - try { - Date date = null; - Date endDate = null; - boolean isAllDay = false; - - String raw = event.getRawDate(); - - if (raw.length() == 10 && !raw.contains("T") && !raw.contains(" ")) { - SimpleDateFormat shortFmt = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); - date = shortFmt.parse(raw); - isAllDay = true; - if (event.getRawEndDate() != null && event.getRawEndDate().length() == 10) { - endDate = shortFmt.parse(event.getRawEndDate()); - } - } - else if (raw.contains("T")) { - SimpleDateFormat isoFmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); - isoFmt.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); - date = isoFmt.parse(raw); - if (event.getRawEndDate() != null && event.getRawEndDate().contains("T")) { - endDate = isoFmt.parse(event.getRawEndDate()); - } - } - else { - SimpleDateFormat sqlFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); - sqlFmt.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); - date = sqlFmt.parse(raw); - if (event.getRawEndDate() != null) { - endDate = sqlFmt.parse(event.getRawEndDate()); - } - } - - if (date != null) { - event.setDay(outputDay.format(date)); - event.setMonth(outputMonth.format(date).toUpperCase()); - - if (isAllDay) { - event.setTime("Hele dagen"); - } else { - String timeStr = outputTime.format(date); - if (endDate != null) { - timeStr += " - " + outputTime.format(endDate); - } - event.setTime("Kl. " + timeStr); - } - } - - } catch (Exception e) { - e.printStackTrace(); - event.setDay("??"); - event.setMonth("???"); - event.setTime("Feil dato"); - } - } - - public static List mergeAndSort(List apiEvents, List deviceEvents) { - List all = new ArrayList<>(); - Set uniqueKeys = new HashSet<>(); - - // 1. Legg til alle API-events først (Prioritert) - for (CalendarEvent apiEvent : apiEvents) { - all.add(apiEvent); - uniqueKeys.add(generateKey(apiEvent)); - } - - // 2. Legg til Device-events KUN hvis nøkkelen ikke finnes - for (CalendarEvent deviceEvent : deviceEvents) { - String key = generateKey(deviceEvent); - if (!uniqueKeys.contains(key)) { - all.add(deviceEvent); - } - } - - // 3. Sorter alt kronologisk - Collections.sort(all, (e1, e2) -> { - String d1 = e1.getRawDate() != null ? e1.getRawDate() : ""; - String d2 = e2.getRawDate() != null ? e2.getRawDate() : ""; - return d1.compareTo(d2); - }); - - return all; - } - - private static String generateKey(CalendarEvent event) { - String title = event.getTitle() != null ? event.getTitle().toLowerCase().trim() : ""; - String datePart = ""; - - if (event.getRawDate() != null) { - String digits = event.getRawDate().replaceAll("[^0-9]", ""); - if (digits.length() >= 12) { - datePart = digits.substring(0, 12); - } else if (digits.length() >= 8) { - datePart = digits.substring(0, 8); - } else { - datePart = digits; - } - } - return title + "_" + datePart; - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\CategoryAdapter.java -============================================================ -package com.kbs.kbsintranett; - -import android.graphics.Color; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; -import androidx.recyclerview.widget.RecyclerView; -import java.util.List; - -public class CategoryAdapter extends RecyclerView.Adapter { - - private List categories; - private String selectedCategory = "Alle"; // Standardvalg - private OnCategoryClickListener listener; - - public interface OnCategoryClickListener { - void onCategoryClick(String category); - } - - public CategoryAdapter(List categories, OnCategoryClickListener listener) { - this.categories = categories; - this.listener = listener; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_category, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - String category = categories.get(position); - holder.name.setText(category); - - if (category.equals(selectedCategory)) { - // Valgt stil (Blå bakgrunn, hvit tekst) - holder.name.setBackgroundResource(R.drawable.bg_category_selected); - holder.name.setTextColor(Color.WHITE); - } else { - // Ikke valgt stil (Hvit bakgrunn, mørk tekst) - holder.name.setBackgroundResource(R.drawable.bg_category_unselected); - holder.name.setTextColor(Color.parseColor("#333333")); - } - - holder.itemView.setOnClickListener(v -> { - selectedCategory = category; - notifyDataSetChanged(); // Oppdater alle for å flytte markering - listener.onCategoryClick(category); - }); - } - - @Override - public int getItemCount() { - return categories.size(); - } - - public static class ViewHolder extends RecyclerView.ViewHolder { - TextView name; - public ViewHolder(View view) { - super(view); - name = view.findViewById(R.id.category_name); - } - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\ChoicesAdapter.java -============================================================ -package com.kbs.kbsintranett; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; - -public class ChoicesAdapter implements JsonDeserializer> { - @Override - public List deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - // Hvis feltet er "null" eller en tom tekststreng, returner en tom liste - if (json.isJsonNull() || (json.isJsonPrimitive() && json.getAsString().isEmpty())) { - return new ArrayList<>(); - } - - // Hvis det faktisk er en liste (Array), les den som vanlig - if (json.isJsonArray()) { - List list = new ArrayList<>(); - for (JsonElement e : json.getAsJsonArray()) { - list.add(context.deserialize(e, GravityField.Choice.class)); - } - return list; - } - - // Hvis vi får noe annet rart (f.eks. en tekst som ikke er tom), ignorer det for å unngå krasj - return new ArrayList<>(); - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\ConditionalLogicAdapter.java -============================================================ -package com.kbs.kbsintranett; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import java.lang.reflect.Type; - -public class ConditionalLogicAdapter implements JsonDeserializer { - @Override - public GravityField.ConditionalLogic deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - // Hvis feltet er en streng (f.eks tom streng ""), returner null - if (json.isJsonPrimitive() && json.getAsJsonPrimitive().isString()) { - return null; - } - - // Hvis det er et objekt, bruk standard deserialisering - if (json.isJsonObject()) { - // Vi må manuelt deserialisere for å unngå uendelig løkke hvis vi bare kaller context.deserialize på samme type - // Enkleste måte er å la Gson gjøre jobben på innholdet - return new com.google.gson.Gson().fromJson(json, GravityField.ConditionalLogic.class); - } - - return null; - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\CreateEventFragment.java -============================================================ -package com.kbs.kbsintranett; - -import android.app.AlertDialog; -import android.app.DatePickerDialog; -import android.app.TimePickerDialog; -import android.content.Context; -import android.graphics.Color; -import android.graphics.Typeface; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.EditText; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.Spinner; -import android.widget.Switch; -import android.widget.TextView; -import android.widget.Toast; -import android.widget.ToggleButton; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.navigation.Navigation; -import com.google.android.material.chip.Chip; -import com.google.android.material.chip.ChipGroup; -import com.google.gson.JsonElement; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class CreateEventFragment extends Fragment { - private EditText etTitle, etDesc, etLocation; - private Spinner spinnerCalendar, spinnerRecurrence; - private ChipGroup chipGroupReminders; - private Switch switchAllDay; - private TextView txtPreview, txtSelectedParticipants; - private Button btnStartDate, btnStartTime, btnEndDate, btnEndTime, btnSave, btnSelectParticipants; - private RadioButton rbAll, rbSpecific; - - private Calendar startCal = Calendar.getInstance(); - private Calendar endCal = Calendar.getInstance(); - - private String selectedRRule = null; - private boolean isCustomRecurrence = false; - - // Deltakere - private List allUsers = new ArrayList<>(); - private List filteredUsers = new ArrayList<>(); - - // Arrangør-sporing (for å ikke overskrive opprinnelig arrangør ved redigering) - private String originalOrganizer = null; - - // EDIT MODE - private CalendarEvent eventToEdit = null; - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_create_event, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - etTitle = view.findViewById(R.id.et_title); - etDesc = view.findViewById(R.id.et_desc); - etLocation = view.findViewById(R.id.et_location); - switchAllDay = view.findViewById(R.id.switch_all_day); - - spinnerCalendar = view.findViewById(R.id.spinner_calendar); - spinnerRecurrence = view.findViewById(R.id.spinner_recurrence); - chipGroupReminders = view.findViewById(R.id.chip_group_reminders); - - txtPreview = view.findViewById(R.id.txt_time_preview); - txtSelectedParticipants = view.findViewById(R.id.txt_selected_participants); - - btnStartDate = view.findViewById(R.id.btn_start_date); - btnStartTime = view.findViewById(R.id.btn_start_time); - btnEndDate = view.findViewById(R.id.btn_end_date); - btnEndTime = view.findViewById(R.id.btn_end_time); - btnSave = view.findViewById(R.id.btn_save_event); - btnSelectParticipants = view.findViewById(R.id.btn_select_participants); - - rbAll = view.findViewById(R.id.rb_visibility_all); - rbSpecific = view.findViewById(R.id.rb_visibility_specific); - - // Initialiser tid (neste hele time) - startCal.add(Calendar.HOUR_OF_DAY, 1); - startCal.set(Calendar.MINUTE, 0); - endCal.setTime(startCal.getTime()); - endCal.add(Calendar.HOUR_OF_DAY, 1); - - setupCalendarSpinner(); - setupReminderChips(); - fetchUsers(); - - // SJEKK OM VI ER I REDIGERINGS-MODUS - if (getArguments() != null && getArguments().containsKey("edit_event")) { - eventToEdit = (CalendarEvent) getArguments().getSerializable("edit_event"); - prefillForm(eventToEdit); - btnSave.setText("Oppdater Hendelse"); - } - - updateRecurrenceSpinner(); - updateUI(); - - // Listeners - switchAllDay.setOnCheckedChangeListener((btn, isChecked) -> updateUI()); - - btnStartDate.setOnClickListener(v -> pickDate(startCal, true)); - btnEndDate.setOnClickListener(v -> pickDate(endCal, false)); - - btnStartTime.setOnClickListener(v -> pickTime(startCal)); - btnEndTime.setOnClickListener(v -> pickTime(endCal)); - - btnSave.setOnClickListener(v -> submitEvent()); - - // Vis/Skjul deltaker-knapp - rbAll.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (isChecked) { - btnSelectParticipants.setVisibility(View.GONE); - txtSelectedParticipants.setVisibility(View.GONE); - } - }); - rbSpecific.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (isChecked) { - btnSelectParticipants.setVisibility(View.VISIBLE); - txtSelectedParticipants.setVisibility(View.VISIBLE); - } - }); - - btnSelectParticipants.setOnClickListener(v -> showUserSelectionDialog()); - - spinnerRecurrence.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (eventToEdit != null && position == 0 && selectedRRule != null) return; - - String selected = parent.getItemAtPosition(position).toString(); - if (selected.equals("Egendefinert...")) { - showCustomRecurrenceDialog(); - } else if (selected.startsWith("Ikke gjenta")) { - selectedRRule = null; - } else { - generateStandardRRule(position); - } - } - @Override public void onNothingSelected(AdapterView parent) {} - }); - - spinnerCalendar.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - String calName = (String) parent.getItemAtPosition(position); - updateFilteredUsers(calName); - } - @Override public void onNothingSelected(AdapterView parent) {} - }); - } - - private void fetchUsers() { - RetrofitClient.getApiService().getUsersList().enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (!isAdded()) return; - if (response.isSuccessful() && response.body() != null) { - allUsers = response.body(); - - if (spinnerCalendar.getSelectedItem() != null) { - updateFilteredUsers(spinnerCalendar.getSelectedItem().toString()); - } - - if (eventToEdit != null) { - parseParticipantsFromDescription(eventToEdit.getDescription()); - } - } - } - @Override - public void onFailure(Call> call, Throwable t) {} - }); - } - - private void updateFilteredUsers(String calendarName) { - if (allUsers == null || allUsers.isEmpty()) return; - - filteredUsers.clear(); - - String requiredRole = ""; - - if (calendarName.equals("Serviceavdelingen")) requiredRole = "serviceavdelingen"; - else if (calendarName.equals("Automasjonsavdelingen")) requiredRole = "automasjonsavdelingen"; - else if (calendarName.equals("Prosjektavdelingen")) requiredRole = "prosjektavdelingen"; - else if (calendarName.equals("Administrasjonen")) requiredRole = "administrasjonen"; - else if (calendarName.equals("Felles")) requiredRole = "kbs_alle"; - - for (User user : allUsers) { - List rawRoles = user.getRoles(); - List roles = new ArrayList<>(); - for (String r : rawRoles) roles.add(r.toLowerCase()); - - boolean hasAccess = false; - - if (roles.contains("administrator")) { - hasAccess = true; - } - else if (!requiredRole.isEmpty() && roles.contains(requiredRole.toLowerCase())) { - hasAccess = true; - } - - if (hasAccess) { - filteredUsers.add(user); - } - } - } - - private void showUserSelectionDialog() { - if (filteredUsers.isEmpty()) { - Toast.makeText(getContext(), "Ingen personer med riktig tilgang funnet.", Toast.LENGTH_SHORT).show(); - return; - } - - String[] userNames = new String[filteredUsers.size()]; - boolean[] checkedItems = new boolean[filteredUsers.size()]; - - for (int i = 0; i < filteredUsers.size(); i++) { - userNames[i] = filteredUsers.get(i).getName(); - checkedItems[i] = filteredUsers.get(i).isSelected(); - } - - new AlertDialog.Builder(getContext()) - .setTitle("Velg deltakere") - .setMultiChoiceItems(userNames, checkedItems, (dialog, which, isChecked) -> { - filteredUsers.get(which).setSelected(isChecked); - }) - .setPositiveButton("OK", (dialog, which) -> updateParticipantPreview()) - .setNegativeButton("Avbryt", null) - .show(); - } - - private void updateParticipantPreview() { - StringBuilder sb = new StringBuilder("Valgte: "); - int count = 0; - for (User u : allUsers) { - if (u.isSelected()) { - if (count > 0) sb.append(", "); - sb.append(u.getName()); - count++; - } - } - if (count == 0) txtSelectedParticipants.setText("Ingen valgt"); - else txtSelectedParticipants.setText(sb.toString()); - } - - private void parseParticipantsFromDescription(String desc) { - if (desc == null) return; - Pattern p = Pattern.compile("#deltakere:([^\\s]+)"); - Matcher m = p.matcher(desc); - - if (m.find()) { - rbSpecific.setChecked(true); - String[] emails = m.group(1).split(","); - - for (String email : emails) { - for (int i = 0; i < allUsers.size(); i++) { - if (allUsers.get(i).getEmail().equalsIgnoreCase(email.trim())) { - allUsers.get(i).setSelected(true); - } - } - } - updateParticipantPreview(); - } else { - rbAll.setChecked(true); - } - } - - private void prefillForm(CalendarEvent event) { - etTitle.setText(event.getTitle()); - - // Hent ut opprinnelig arrangør hvis den finnes - if (event.getDescription() != null) { - Matcher m = Pattern.compile("#arrangor:(.+)").matcher(event.getDescription()); - if (m.find()) { - originalOrganizer = m.group(1).trim(); - } - } - - String cleanDesc = event.getDescription() - .replaceAll("#varsel:[\\d,]+", "") - .replaceAll("#deltakere:[^\\s]+", "") - .replaceAll("#arrangor:.+", "") // Fjern også arrangør fra tekstfeltet - .trim(); - etDesc.setText(cleanDesc); - etLocation.setText(event.getLocation()); - - ArrayAdapter adapter = (ArrayAdapter) spinnerCalendar.getAdapter(); - if (adapter != null) { - int position = adapter.getPosition(event.getCalendarName()); - if (position >= 0) { - spinnerCalendar.setSelection(position); - } - } - spinnerCalendar.setEnabled(false); - - try { - SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); - SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); - - String start = event.getRawDate(); - if (start != null) { - if (start.length() == 10) { - switchAllDay.setChecked(true); - Date d = simpleFormat.parse(start); - startCal.setTime(d); - - if (event.getRawEndDate() != null) { - Date e = simpleFormat.parse(event.getRawEndDate()); - Calendar c = Calendar.getInstance(); - c.setTime(e); - c.add(Calendar.DAY_OF_MONTH, -1); - endCal.setTime(c.getTime()); - } else { - endCal.setTime(d); - } - } else if (start.contains("T")) { - if (start.length() > 19) start = start.substring(0, 19); - startCal.setTime(isoFormat.parse(start)); - - String end = event.getRawEndDate(); - if (end != null && end.contains("T")) { - if (end.length() > 19) end = end.substring(0, 19); - endCal.setTime(isoFormat.parse(end)); - } else { - endCal.setTime(startCal.getTime()); - endCal.add(Calendar.HOUR_OF_DAY, 1); - } - } - } - - List existingReminders = event.getReminders(); - if (!existingReminders.isEmpty()) { - for (int i = 0; i < chipGroupReminders.getChildCount(); i++) { - ((Chip) chipGroupReminders.getChildAt(i)).setChecked(false); - } - for (int min : existingReminders) { - checkChipByMinutes(min); - } - } - - } catch (Exception e) { - Log.e("KBS_EDIT", "Feil ved prefill", e); - } - } - - private void checkChipByMinutes(int minutes) { - for (int i = 0; i < chipGroupReminders.getChildCount(); i++) { - Chip chip = (Chip) chipGroupReminders.getChildAt(i); - if ((int)chip.getTag() == minutes) { - chip.setChecked(true); - } - } - } - - private String getCalendarColor(String name) { - switch (name) { - case "Felles": return "#0069B3"; - case "Administrasjonen": return "#607D8B"; - case "Serviceavdelingen": return "#E65100"; - case "Automasjonsavdelingen": return "#2E7D32"; - case "Prosjektavdelingen": return "#7B1FA2"; - default: return "#888888"; - } - } - - private void setupCalendarSpinner() { - List calendars = UserManager.getInstance().getWriteableCalendars(); - if (calendars.isEmpty()) calendars.add("Felles"); - - ArrayAdapter adapter = new ArrayAdapter(getContext(), android.R.layout.simple_spinner_item, calendars) { - @NonNull - @Override - public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { - TextView view = (TextView) super.getView(position, convertView, parent); - String calName = getItem(position); - String colorHex = getCalendarColor(calName); - view.setBackgroundColor(Color.parseColor(colorHex)); - view.setTextColor(Color.WHITE); - view.setTypeface(null, Typeface.BOLD); - return view; - } - - @Override - public View getDropDownView(int position, View convertView, ViewGroup parent) { - TextView view = (TextView) super.getDropDownView(position, convertView, parent); - String calName = getItem(position); - String colorHex = getCalendarColor(calName); - view.setBackgroundColor(Color.WHITE); - view.setTextColor(Color.parseColor(colorHex)); - view.setTypeface(null, Typeface.BOLD); - return view; - } - }; - - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinnerCalendar.setAdapter(adapter); - } - - private void setupReminderChips() { - addChip("Ved start", 0); - addChip("5 min", 5); - addChip("10 min", 10); - addChip("15 min", 15); - addChip("30 min", 30); - addChip("1 time", 60); - addChip("2 timer", 120); - addChip("1 dag", 1440); - addChip("2 dager", 2880); - addChip("1 uke", 10080); - - if (eventToEdit == null) checkChipByMinutes(15); - } - - private void addChip(String text, int minutes) { - Chip chip = new Chip(getContext()); - chip.setText(text); - chip.setTag(minutes); - chip.setCheckable(true); - chip.setClickable(true); - chipGroupReminders.addView(chip); - } - - private List getSelectedReminders() { - List selected = new ArrayList<>(); - for (int i = 0; i < chipGroupReminders.getChildCount(); i++) { - Chip chip = (Chip) chipGroupReminders.getChildAt(i); - if (chip.isChecked()) { - selected.add((Integer) chip.getTag()); - } - } - return selected; - } - - private void updateRecurrenceSpinner() { - String dayName = new SimpleDateFormat("EEEE", new Locale("no")).format(startCal.getTime()); - int dayOfMonth = startCal.get(Calendar.DAY_OF_MONTH); - String monthName = new SimpleDateFormat("MMMM", new Locale("no")).format(startCal.getTime()); - - int weekNo = (startCal.get(Calendar.DAY_OF_MONTH) - 1) / 7 + 1; - String nthDayString = "månedlig den " + weekNo + ". " + dayName + "en"; - - List options = new ArrayList<>(); - options.add("Ikke gjenta"); - options.add("Daglig"); - options.add("Ukentlig på " + dayName); - options.add("Månedlig den " + dayOfMonth + "."); - options.add(capitalize(nthDayString)); - options.add("Årlig den " + dayOfMonth + ". " + monthName); - options.add("Hver ukedag (man-fre)"); - options.add("Egendefinert..."); - - ArrayAdapter adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, options); - spinnerRecurrence.setAdapter(adapter); - } - - private void generateStandardRRule(int position) { - switch (position) { - case 1: selectedRRule = "RRULE:FREQ=DAILY"; break; - case 2: selectedRRule = "RRULE:FREQ=WEEKLY"; break; - case 3: selectedRRule = "RRULE:FREQ=MONTHLY"; break; - case 4: - int weekNo = (startCal.get(Calendar.DAY_OF_MONTH) - 1) / 7 + 1; - String dayCode = getDayCode(startCal.get(Calendar.DAY_OF_WEEK)); - selectedRRule = "RRULE:FREQ=MONTHLY;BYDAY=" + weekNo + dayCode; - break; - case 5: selectedRRule = "RRULE:FREQ=YEARLY"; break; - case 6: selectedRRule = "RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR"; break; - default: selectedRRule = null; - } - } - - private void showCustomRecurrenceDialog() { - AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); - View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_custom_recurrence, null); - - EditText etInterval = view.findViewById(R.id.et_interval); - Spinner spinnerFreq = view.findViewById(R.id.spinner_freq); - View layoutWeekdays = view.findViewById(R.id.layout_weekdays); - - ToggleButton[] toggles = { - view.findViewById(R.id.tg_mon), view.findViewById(R.id.tg_tue), - view.findViewById(R.id.tg_wed), view.findViewById(R.id.tg_thu), - view.findViewById(R.id.tg_fri), view.findViewById(R.id.tg_sat), - view.findViewById(R.id.tg_sun) - }; - String[] labels = {"M", "T", "O", "T", "F", "L", "S"}; - for(int i=0; i<7; i++) { toggles[i].setText(labels[i]); toggles[i].setTextOn(labels[i]); toggles[i].setTextOff(labels[i]); } - - RadioGroup rgEnd = view.findViewById(R.id.rg_end); - Button btnEndDatePicker = view.findViewById(R.id.btn_end_date_picker); - EditText etCount = view.findViewById(R.id.et_count); - Calendar customEndCal = Calendar.getInstance(); - customEndCal.add(Calendar.MONTH, 1); - - SimpleDateFormat sdf = new SimpleDateFormat("d. MMM yyyy", Locale.getDefault()); - btnEndDatePicker.setText(sdf.format(customEndCal.getTime())); - - btnEndDatePicker.setOnClickListener(v -> { - rgEnd.check(R.id.rb_date); - new DatePickerDialog(getContext(), (p, y, m, d) -> { - customEndCal.set(y, m, d); - btnEndDatePicker.setText(sdf.format(customEndCal.getTime())); - }, customEndCal.get(Calendar.YEAR), customEndCal.get(Calendar.MONTH), customEndCal.get(Calendar.DAY_OF_MONTH)).show(); - }); - - spinnerFreq.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - layoutWeekdays.setVisibility(position == 1 ? View.VISIBLE : View.GONE); - } - @Override public void onNothingSelected(AdapterView parent) {} - }); - - builder.setView(view); - AlertDialog dialog = builder.create(); - - view.findViewById(R.id.btn_cancel).setOnClickListener(v -> { - dialog.dismiss(); - spinnerRecurrence.setSelection(0); - }); - - view.findViewById(R.id.btn_done).setOnClickListener(v -> { - StringBuilder rrule = new StringBuilder("RRULE:"); - int freqPos = spinnerFreq.getSelectedItemPosition(); - String freq = ""; - switch (freqPos) { - case 0: freq = "DAILY"; break; - case 1: freq = "WEEKLY"; break; - case 2: freq = "MONTHLY"; break; - case 3: freq = "YEARLY"; break; - } - rrule.append("FREQ=").append(freq); - - String interval = etInterval.getText().toString(); - if (!interval.isEmpty() && !interval.equals("1")) { - rrule.append(";INTERVAL=").append(interval); - } - - if (freq.equals("WEEKLY")) { - List days = new ArrayList<>(); - String[] codes = {"MO", "TU", "WE", "TH", "FR", "SA", "SU"}; - for (int i=0; i<7; i++) { - if (toggles[i].isChecked()) days.add(codes[i]); - } - if (!days.isEmpty()) { - rrule.append(";BYDAY=").append(String.join(",", days)); - } - } - - int checkedId = rgEnd.getCheckedRadioButtonId(); - if (checkedId == R.id.rb_date) { - customEndCal.set(Calendar.HOUR_OF_DAY, 23); - customEndCal.set(Calendar.MINUTE, 59); - customEndCal.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); - SimpleDateFormat utc = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US); - utc.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); - rrule.append(";UNTIL=").append(utc.format(customEndCal.getTime())); - } else if (checkedId == R.id.rb_count) { - rrule.append(";COUNT=").append(etCount.getText().toString()); - } - - selectedRRule = rrule.toString(); - isCustomRecurrence = true; - dialog.dismiss(); - }); - - dialog.show(); - } - - private void pickDate(Calendar cal, boolean isStart) { - new DatePickerDialog(getContext(), (view, y, m, d) -> { - cal.set(y, m, d); - if (isStart) { - if (endCal.before(startCal)) { - endCal.setTime(startCal.getTime()); - if (!switchAllDay.isChecked()) endCal.add(Calendar.HOUR_OF_DAY, 1); - } - updateRecurrenceSpinner(); - if (!isCustomRecurrence) spinnerRecurrence.setSelection(0); - } - updateUI(); - }, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).show(); - } - - private void pickTime(Calendar cal) { - new TimePickerDialog(getContext(), (view, h, m) -> { - cal.set(Calendar.HOUR_OF_DAY, h); - cal.set(Calendar.MINUTE, m); - if (cal == startCal && endCal.before(startCal)) { - endCal.setTime(startCal.getTime()); - endCal.add(Calendar.HOUR_OF_DAY, 1); - } - updateUI(); - }, cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), true).show(); - } - - private void updateUI() { - boolean isAllDay = switchAllDay.isChecked(); - - btnStartTime.setVisibility(isAllDay ? View.GONE : View.VISIBLE); - btnEndTime.setVisibility(isAllDay ? View.GONE : View.VISIBLE); - - SimpleDateFormat dateFmt = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()); - SimpleDateFormat timeFmt = new SimpleDateFormat("HH:mm", Locale.getDefault()); - - btnStartDate.setText(dateFmt.format(startCal.getTime())); - btnEndDate.setText(dateFmt.format(endCal.getTime())); - btnStartTime.setText(timeFmt.format(startCal.getTime())); - btnEndTime.setText(timeFmt.format(endCal.getTime())); - - String preview = dateFmt.format(startCal.getTime()); - if (!isAllDay) preview += " " + timeFmt.format(startCal.getTime()); - preview += " - "; - - if (isSameDay(startCal, endCal)) { - if (isAllDay) preview += " (Samme dag)"; - else preview += timeFmt.format(endCal.getTime()); - } else { - preview += dateFmt.format(endCal.getTime()); - if (!isAllDay) preview += " " + timeFmt.format(endCal.getTime()); - } - txtPreview.setText(preview); - } - - private boolean isSameDay(Calendar c1, Calendar c2) { - return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR) && - c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR); - } - - private String capitalize(String s) { - if (s == null || s.isEmpty()) return s; - return s.substring(0, 1).toUpperCase() + s.substring(1); - } - - private String getDayCode(int calendarDay) { - switch (calendarDay) { - case Calendar.MONDAY: return "MO"; - case Calendar.TUESDAY: return "TU"; - case Calendar.WEDNESDAY: return "WE"; - case Calendar.THURSDAY: return "TH"; - case Calendar.FRIDAY: return "FR"; - case Calendar.SATURDAY: return "SA"; - case Calendar.SUNDAY: return "SU"; - default: return "MO"; - } - } - - private String getCalendarSlug() { - if (spinnerCalendar.getSelectedItem() != null) { - return spinnerCalendar.getSelectedItem().toString(); - } - return "Felles"; - } - - private void submitEvent() { - String title = etTitle.getText().toString().trim(); - if (title.isEmpty()) { - etTitle.setError("Mangler tittel"); - return; - } - - boolean isAllDay = switchAllDay.isChecked(); - String format = isAllDay ? "yyyy-MM-dd" : "yyyy-MM-dd'T'HH:mm"; - SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault()); - - String startTimeStr = sdf.format(startCal.getTime()); - String endTimeStr = sdf.format(endCal.getTime()); - String location = etLocation.getText().toString(); - String description = etDesc.getText().toString(); - List reminders = getSelectedReminders(); - - if (rbSpecific.isChecked() && allUsers != null) { - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (User u : allUsers) { - if (u.isSelected()) { - if (!first) sb.append(","); - sb.append(u.getEmail()); - first = false; - } - } - if (sb.length() > 0) { - description += "\n\n#deltakere:" + sb.toString(); - } - } - - // NYTT: Legg til arrangør - String organizerTag; - if (originalOrganizer != null) { - organizerTag = originalOrganizer; // Behold opprinnelig ved redigering - } else { - organizerTag = UserManager.getInstance().getUserDisplayName(); // Ny hendelse - } - description += "\n#arrangor:" + organizerTag; - - CreateEventRequest req = new CreateEventRequest( - title, description, location, startTimeStr, endTimeStr, - getCalendarSlug(), reminders, isAllDay, selectedRRule - ); - - if (eventToEdit != null) { - req.id = eventToEdit.getId(); - } - - Toast.makeText(getContext(), eventToEdit != null ? "Oppdaterer..." : "Oppretter...", Toast.LENGTH_SHORT).show(); - - final Context appContext = requireContext().getApplicationContext(); - - Call call; - if (eventToEdit != null) { - call = RetrofitClient.getApiService().updateCalendarEvent(req); - } else { - call = RetrofitClient.getApiService().createCalendarEvent(req); - } - - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - Toast.makeText(getContext(), eventToEdit != null ? "Hendelse oppdatert!" : "Hendelse opprettet!", Toast.LENGTH_LONG).show(); - fetchCalendarAndSchedule(appContext); - Navigation.findNavController(getView()).navigateUp(); - } else { - Toast.makeText(getContext(), "Feil (" + response.code() + ")", Toast.LENGTH_LONG).show(); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), "Nettverksfeil", Toast.LENGTH_SHORT).show(); - } - }); - } - - private void fetchCalendarAndSchedule(Context context) { - new Thread(() -> { - try { - if (context == null) return; - Response> response = RetrofitClient.getApiService().getCalendarEvents().execute(); - if (response.isSuccessful() && response.body() != null) { - AlarmScheduler.scheduleAlarmsForEvents(context, response.body()); - } - } catch (Exception e) { - Log.e("CreateEvent", "Kunne ikke oppdatere alarmer", e); - } - }).start(); - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\CreateEventRequest.java -============================================================ -package com.kbs.kbsintranett; - -import com.google.gson.annotations.SerializedName; -import java.util.List; -public class CreateEventRequest { - @SerializedName("id") - public String id; - @SerializedName("title") - public String title; - - @SerializedName("description") - public String description; - - @SerializedName("location") - public String location; - - @SerializedName("start_time") - public String startTime; - - @SerializedName("end_time") - public String endTime; - - @SerializedName("calendar_type") - public String calendarType; - - @SerializedName("reminders") - public List reminders; // Liste, ikke int - - @SerializedName("is_all_day") - public boolean isAllDay; - - @SerializedName("recurrence") - public String recurrence; - - // Oppdatert konstruktør som tar imot List - public CreateEventRequest(String title, String description, String location, String startTime, String endTime, String calendarType, List reminders, boolean isAllDay, String recurrence) { - this.title = title; - this.description = description; - this.location = location; - this.startTime = startTime; - this.endTime = endTime; - this.calendarType = calendarType; - this.reminders = reminders; - this.isAllDay = isAllDay; - this.recurrence = recurrence; - } -} - - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\FormsFragment.java -============================================================ -package com.kbs.kbsintranett; - -import android.Manifest; -import android.animation.LayoutTransition; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.DatePickerDialog; -import android.app.TimePickerDialog; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.graphics.Color; -import android.graphics.Typeface; -import android.net.Uri; -import android.os.Bundle; -import android.os.Environment; -import android.provider.OpenableColumns; -import android.text.Editable; -import android.text.Html; -import android.text.InputType; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.ScrollView; -import android.widget.Spinner; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.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 org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import okhttp3.MediaType; -import okhttp3.MultipartBody; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okio.BufferedSink; -import okio.Okio; -import okio.Source; - -public class FormsFragment extends Fragment { - - private static final String TAG = "FormsFragment"; - private static final String BASE_URL_GF = "https://intranet.kbs.no/wp-json/gf/v2"; - - // SKJEMA ID-er - private static final int ID_ANSATTEOPPLYSNINGER = 1; - private static final int ID_RUH = 4; - private static final int ID_SIKKERHETSKURS = 9; - private static final int ID_HMS_BEKREFTELSE = 10; - private static final int ID_EGENMELDING = 11; - private static final int ID_SJA = 14; - private static final int ID_FRAVARSVARSEL = 15; - private static final int ID_REFUSJON_UTLEGG = 16; - - private int formId = 1; - - private LinearLayout formContainer; - private LinearLayout historyContainer; - private View historyWrapper; - private TextView txtStatus; - private TextView lblHistory; - private ProgressBar loadingSpinner; - private ImageView btnToggleHistory; - private Toolbar toolbar; // NYTT - - // --- HOVEDSKJEMA STATE --- - private Map fieldWrappers = new HashMap<>(); - private Map inputViews = new HashMap<>(); - private Map requiredFieldsMap = new HashMap<>(); - private Map fileUploads = new HashMap<>(); - - // --- NESTED FORM (BARN) STATE --- - private Map childInputViews = new HashMap<>(); - private Map childRequiredFieldsMap = new HashMap<>(); - private Map childFileUploads = new HashMap<>(); - - private List nestedEntries = new ArrayList<>(); - private LinearLayout nestedEntriesContainer; - private TextView totalAmountView; - - // --- FILOPPLASTING & KAMERA --- - private String pendingFileFieldId = null; - private boolean isSelectingForChild = false; - private Uri currentPhotoUri = null; - - private ActivityResultLauncher filePickerLauncher; - private ActivityResultLauncher takePictureLauncher; - private ActivityResultLauncher requestPermissionLauncher; - - private GravityForm currentForm; - private final OkHttpClient client = new OkHttpClient(); - - private static final Pattern TITLE_PATTERN = Pattern.compile("^(\\d+)[.\\s-]+\\s*(.*)"); - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - filePickerLauncher = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { - Uri uri = result.getData().getData(); - if (uri != null && pendingFileFieldId != null) { - handleFileSelection(pendingFileFieldId, uri, isSelectingForChild); - } - } - } - ); - takePictureLauncher = registerForActivityResult( - new ActivityResultContracts.TakePicture(), - success -> { - if (success && currentPhotoUri != null && pendingFileFieldId != null) { - handleFileSelection(pendingFileFieldId, currentPhotoUri, isSelectingForChild); - } else if (!success) { - currentPhotoUri = null; - } - } - ); - requestPermissionLauncher = registerForActivityResult( - new ActivityResultContracts.RequestPermission(), - isGranted -> { - if (isGranted) { - openCamera(); - } else { - Toast.makeText(getContext(), "Kameratillatelse er påkrevd for å ta bilde.", Toast.LENGTH_LONG).show(); - } - } - ); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_forms, container, false); - formContainer = view.findViewById(R.id.form_container); - historyContainer = view.findViewById(R.id.historyContainer); - historyWrapper = view.findViewById(R.id.history_wrapper); - txtStatus = view.findViewById(R.id.txt_status); - lblHistory = view.findViewById(R.id.lbl_history); - loadingSpinner = view.findViewById(R.id.loading_spinner); - - // 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()); - } - - if (view instanceof ViewGroup) { - LayoutTransition transition = ((ViewGroup) view).getLayoutTransition(); - if (transition == null) { - transition = new LayoutTransition(); - ((ViewGroup) view).setLayoutTransition(transition); - } - transition.enableTransitionType(LayoutTransition.CHANGING); - } - - if (formContainer == null) { - formContainer = new LinearLayout(getContext()); - } - - if (getArguments() != null) { - int argId = getArguments().getInt("formId", 0); - if (argId != 0) formId = argId; - } - - fetchFormStructure(); - - return view; - } - - // --- UI LOGIKK FOR DELT SKJERM --- - - private void expandFormModule() { - if (historyWrapper != null && historyWrapper.getVisibility() == View.VISIBLE) { - historyWrapper.setVisibility(View.GONE); - if (btnToggleHistory != null) { - btnToggleHistory.setImageResource(android.R.drawable.arrow_down_float); - } - } - } - - private void toggleHistoryVisibility() { - if (historyWrapper == null || btnToggleHistory == null) return; - - if (historyWrapper.getVisibility() == View.VISIBLE) { - historyWrapper.setVisibility(View.GONE); - btnToggleHistory.setImageResource(android.R.drawable.arrow_down_float); - } else { - historyWrapper.setVisibility(View.VISIBLE); - btnToggleHistory.setImageResource(android.R.drawable.arrow_up_float); - } - } - - private void attachInteractionListener(View view) { - if (view == null) return; - view.setOnTouchListener((v, event) -> { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - expandFormModule(); - } - return false; - }); - view.setOnFocusChangeListener((v, hasFocus) -> { - if (hasFocus) { - expandFormModule(); - } - }); - if (view.isClickable()) { - view.setOnClickListener(v -> expandFormModule()); - } - } - - // ---------------------------------- - - private void fetchFormStructure() { - if (loadingSpinner != null) loadingSpinner.setVisibility(View.VISIBLE); - updateStatus("Laster skjema..."); - - WordPressApiService api = RetrofitClient.getApiService(); - api.getForm(formId).enqueue(new retrofit2.Callback() { - @Override - public void onResponse(retrofit2.Call call, retrofit2.Response response) { - if (response.isSuccessful() && response.body() != null) { - currentForm = response.body(); - if (getActivity() != null) { - getActivity().runOnUiThread(() -> { - if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE); - renderDynamicForm(currentForm); - fetchFormEntries(); - }); - } - } else { - updateStatus("Feil ved lasting av skjema."); - if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE); - } - } - - @Override - public void onFailure(retrofit2.Call call, Throwable t) { - updateStatus("Nettverksfeil (Skjema): " + t.getMessage()); - if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE); - } - }); - } - - private void renderDynamicForm(GravityForm form) { - if (formContainer == null) return; - formContainer.removeAllViews(); - fieldWrappers.clear(); - inputViews.clear(); - requiredFieldsMap.clear(); - fileUploads.clear(); - nestedEntries.clear(); - updateStatus(""); - - // 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); - } - - // 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*", ""); - formDesc.setText(cleanDesc); - formDesc.setPadding(0, 0, 0, 40); - formContainer.addView(formDesc); - } - - if (form.fields == null) return; - for (GravityField field : form.fields) { - if ("hidden".equals(field.type) || field.isHidden || "hidden".equals(field.visibility)) { - continue; - } - - LinearLayout fieldWrapper = new LinearLayout(getContext()); - fieldWrapper.setOrientation(LinearLayout.VERTICAL); - fieldWrapper.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - fieldWrapper.setPadding(0, 10, 0, 20); - - fieldWrappers.put(String.valueOf(field.id), fieldWrapper); - - if ("section".equals(field.type)) { - addSectionHeader(fieldWrapper, field.label, field.description); - formContainer.addView(fieldWrapper); - continue; - } - - if ("html".equals(field.type)) { - if (field.content != null && !field.content.isEmpty()) { - TextView htmlView = new TextView(getContext()); - htmlView.setText(Html.fromHtml(field.content, Html.FROM_HTML_MODE_COMPACT)); - fieldWrapper.addView(htmlView); - } - formContainer.addView(fieldWrapper); - continue; - } - - TextView label = new TextView(getContext()); - String labelText = field.label; - if (field.isRequired) labelText += " *"; - label.setText(labelText); - label.setTextColor(Color.DKGRAY); - label.setTypeface(null, Typeface.BOLD); - label.setPadding(0, 10, 0, 5); - fieldWrapper.addView(label); - - if ("form".equals(field.type)) { - renderNestedFormField(fieldWrapper, field); - } - else if ("product".equals(field.type) && "calculation".equals(field.inputType)) { - renderTotalSumField(fieldWrapper, field); - } - else if ("time".equals(field.type)) { - renderTimeField(fieldWrapper, field, inputViews, requiredFieldsMap); - } else if ("fileupload".equals(field.type)) { - renderFileUploadField(fieldWrapper, field, inputViews, requiredFieldsMap, false); - } else if (field.inputs != null && !field.inputs.isEmpty()) { - if ("consent".equals(field.type)) { - renderConsentField(fieldWrapper, field, inputViews, requiredFieldsMap); - } else if ("checkbox".equals(field.type) || "multi_choice".equals(field.type)) { - renderCheckboxField(fieldWrapper, field, inputViews, requiredFieldsMap); - } else { - renderCompositeField(fieldWrapper, field, inputViews, requiredFieldsMap); - } - } else if ("radio".equals(field.type)) { - renderRadioField(fieldWrapper, field, inputViews, requiredFieldsMap); - } else if ("select".equals(field.type)) { - renderSelectField(fieldWrapper, field, inputViews, requiredFieldsMap); - } else if ("textarea".equals(field.type)) { - renderTextAreaField(fieldWrapper, field, inputViews, requiredFieldsMap); - } else if ("date".equals(field.type)) { - renderDateField(fieldWrapper, field, inputViews, requiredFieldsMap); - } else if ("consent".equals(field.type)) { - renderConsentField(fieldWrapper, field, inputViews, requiredFieldsMap); - } else { - renderTextField(fieldWrapper, field, inputViews, requiredFieldsMap); - } - - if (field.description != null && !field.description.isEmpty()) { - TextView desc = new TextView(getContext()); - desc.setText(Html.fromHtml(field.description, Html.FROM_HTML_MODE_COMPACT)); - desc.setTextSize(12); - desc.setTextColor(Color.GRAY); - fieldWrapper.addView(desc); - } - - formContainer.addView(fieldWrapper); - } - - Button dynamicSubmit = new Button(getContext()); - dynamicSubmit.setText("Send inn skjema"); - dynamicSubmit.setTextColor(Color.WHITE); - dynamicSubmit.setBackgroundColor(Color.parseColor("#0069B3")); // KBS Blå - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - params.setMargins(0, 60, 0, 20); - dynamicSubmit.setLayoutParams(params); - dynamicSubmit.setOnClickListener(v -> submitDynamicForm()); - formContainer.addView(dynamicSubmit); - - evaluateAllConditionalLogic(); - } - - // --- NESTED FORM LOGIKK --- - - private void renderNestedFormField(LinearLayout container, GravityField field) { - nestedEntriesContainer = new LinearLayout(getContext()); - nestedEntriesContainer.setOrientation(LinearLayout.VERTICAL); - nestedEntriesContainer.setPadding(0, 10, 0, 10); - container.addView(nestedEntriesContainer); - - Button btnAdd = new Button(getContext()); - btnAdd.setText("Legg til vedlegg"); - btnAdd.setBackgroundColor(Color.parseColor("#53AFE9")); - btnAdd.setTextColor(Color.WHITE); - btnAdd.setOnClickListener(v -> { - expandFormModule(); - int childFormId = 18; - if (field.gpnfForm != null) { - try { - childFormId = Integer.parseInt(field.gpnfForm); - } catch (NumberFormatException e) { e.printStackTrace(); } - } - openChildFormDialog(childFormId, field.id); - }); - container.addView(btnAdd); - - EditText hiddenIds = new EditText(getContext()); - hiddenIds.setVisibility(View.GONE); - inputViews.put(field.id, hiddenIds); - } - - private void renderTotalSumField(LinearLayout container, GravityField field) { - totalAmountView = new TextView(getContext()); - totalAmountView.setText("Kr 0,00"); - totalAmountView.setTextSize(18); - totalAmountView.setTypeface(null, Typeface.BOLD); - totalAmountView.setPadding(10, 10, 10, 10); - container.addView(totalAmountView); - } - - private void openChildFormDialog(int childFormId, String parentFieldId) { - if (getActivity() == null) return; - ProgressBar pBar = new ProgressBar(getContext()); - AlertDialog loadingDialog = new AlertDialog.Builder(getContext()) - .setView(pBar) - .setMessage("Laster skjema...") - .setCancelable(false) - .show(); - RetrofitClient.getApiService().getForm(childFormId).enqueue(new retrofit2.Callback() { - @Override - public void onResponse(retrofit2.Call call, retrofit2.Response response) { - loadingDialog.dismiss(); - if (response.isSuccessful() && response.body() != null) { - showChildFormDialog(response.body(), parentFieldId); - } else { - Toast.makeText(getContext(), "Kunne ikke hente underskjema", Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void onFailure(retrofit2.Call call, Throwable t) { - loadingDialog.dismiss(); - Toast.makeText(getContext(), "Feil: " + t.getMessage(), Toast.LENGTH_SHORT).show(); - } - }); - } - - private void showChildFormDialog(GravityForm childForm, String parentFieldId) { - AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); - childInputViews.clear(); - childRequiredFieldsMap.clear(); - childFileUploads.clear(); - - ScrollView scrollView = new ScrollView(getContext()); - LinearLayout layout = new LinearLayout(getContext()); - layout.setOrientation(LinearLayout.VERTICAL); - layout.setPadding(30, 30, 30, 30); - scrollView.addView(layout); - for (GravityField field : childForm.fields) { - if ("hidden".equals(field.type) || field.isHidden) continue; - LinearLayout wrapper = new LinearLayout(getContext()); - wrapper.setOrientation(LinearLayout.VERTICAL); - wrapper.setPadding(0, 10, 0, 20); - - TextView label = new TextView(getContext()); - String lText = field.label; - if (field.isRequired) lText += " *"; - label.setText(lText); - label.setTypeface(null, Typeface.BOLD); - wrapper.addView(label); - if ("fileupload".equals(field.type)) { - renderFileUploadField(wrapper, field, childInputViews, childRequiredFieldsMap, true); - } else if ("product".equals(field.type)) { - EditText input = new EditText(getContext()); - input.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL); - input.setHint("Kr 0.00"); - wrapper.addView(input); - childInputViews.put(field.id, input); - childRequiredFieldsMap.put(field.id, field.isRequired); - } else { - renderTextField(wrapper, field, childInputViews, childRequiredFieldsMap); - } - layout.addView(wrapper); - } - - builder.setView(scrollView); - builder.setPositiveButton("Legg til vedlegg", null); - builder.setNegativeButton("Avbryt", (d, w) -> d.dismiss()); - AlertDialog dialog = builder.create(); - dialog.show(); - - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> { - submitChildForm(childForm.id, dialog, parentFieldId); - }); - } - - private void submitChildForm(int childFormId, AlertDialog dialog, String parentFieldId) { - JSONObject inputValues = new JSONObject(); - for (Map.Entry entry : childInputViews.entrySet()) { - String val = getInputValueGeneric(entry.getValue()); - if (!val.isEmpty()) { - try { - inputValues.put("input_" + entry.getKey(), val); - } catch (JSONException e) { e.printStackTrace(); } - } - } - - if (!childFileUploads.isEmpty()) { - List fileParts = new ArrayList<>(); - Map textParts = new HashMap<>(); - - try { - JSONArray names = inputValues.names(); - if (names != null) { - for (int i = 0; i < names.length(); i++) { - String key = names.getString(i); - String val = inputValues.getString(key); - textParts.put(key, RequestBody.create(MultipartBody.FORM, val)); - } - } - - if (parentFieldId != null) { - textParts.put("gpnf_entry_nested_form_field", RequestBody.create(MultipartBody.FORM, parentFieldId)); - } - - for (Map.Entry fileEntry : childFileUploads.entrySet()) { - String fieldId = fileEntry.getKey(); - Uri uri = fileEntry.getValue(); - if (uri != null) { - MultipartBody.Part part = getFilePart("input_" + fieldId, uri); - if (part != null) fileParts.add(part); - } - } - - Toast.makeText(getContext(), "Laster opp vedlegg...", Toast.LENGTH_SHORT).show(); - RetrofitClient.getApiService().submitMultipartForm(childFormId, textParts, fileParts).enqueue(new retrofit2.Callback() { - @Override - public void onResponse(retrofit2.Call call, retrofit2.Response response) { - if (response.isSuccessful() && response.body() != null) { - try { - JsonObject json = response.body().getAsJsonObject(); - if (json.has("is_valid") && json.get("is_valid").getAsBoolean()) { - String entryId = json.has("entry_id") ? json.get("entry_id").getAsString() : ""; - String desc = getInputValueGeneric(childInputViews.get("3")); - String price = getInputValueGeneric(childInputViews.get("4")); - addNestedEntry(entryId, desc, price); - dialog.dismiss(); - } else { - Toast.makeText(getContext(), "Ugyldig respons fra server", Toast.LENGTH_SHORT).show(); - } - } catch (Exception e) { - e.printStackTrace(); - Toast.makeText(getContext(), "Feil ved parsing av svar", Toast.LENGTH_SHORT).show(); - } - } else { - Toast.makeText(getContext(), "Feil ved opplasting", Toast.LENGTH_SHORT).show(); - } - } - @Override - public void onFailure(retrofit2.Call call, Throwable t) { - Toast.makeText(getContext(), "Nettverksfeil", Toast.LENGTH_SHORT).show(); - } - }); - } catch (Exception e) { e.printStackTrace(); } - } - } - - private void addNestedEntry(String entryId, String description, String price) { - nestedEntries.add(new NestedEntry(entryId, description, price)); - refreshNestedList(); - } - - private void refreshNestedList() { - if (nestedEntriesContainer == null) return; - nestedEntriesContainer.removeAllViews(); - - double total = 0; - List ids = new ArrayList<>(); - for (NestedEntry entry : nestedEntries) { - ids.add(entry.id); - String cleanPrice = entry.price.replaceAll("[^0-9,.]", "").replace(",", "."); - try { - if (!cleanPrice.isEmpty()) total += Double.parseDouble(cleanPrice); - } catch (NumberFormatException e) { } - - LinearLayout row = new LinearLayout(getContext()); - row.setOrientation(LinearLayout.HORIZONTAL); - row.setPadding(10, 10, 10, 10); - - TextView txt = new TextView(getContext()); - txt.setText(entry.description + " (" + entry.price + ")"); - txt.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1)); - - row.addView(txt); - nestedEntriesContainer.addView(row); - } - - if (totalAmountView != null) { - totalAmountView.setText("Totalt: Kr " + String.format("%.2f", total)); - } - - View hiddenField = inputViews.get("25"); - if (hiddenField instanceof EditText) { - ((EditText)hiddenField).setText(TextUtils.join(",", ids)); - } - } - - // --- FELLES METODER (FILE UPLOAD M/ CAMERA STØTTE) --- - - private void renderFileUploadField(LinearLayout container, GravityField field, Map viewsMap, Map reqMap, boolean isChild) { - LinearLayout fileLayout = new LinearLayout(getContext()); - fileLayout.setOrientation(LinearLayout.HORIZONTAL); - - Button btnUpload = new Button(getContext()); - btnUpload.setText("Velg fil / Ta bilde"); - btnUpload.setOnClickListener(v -> { - pendingFileFieldId = field.id; - isSelectingForChild = isChild; - expandFormModule(); - showFileSourceDialog(); - }); - TextView txtFileName = new TextView(getContext()); - txtFileName.setText("Ingen fil valgt"); - txtFileName.setPadding(20, 0, 0, 0); - txtFileName.setTextColor(Color.GRAY); - btnUpload.setTag(txtFileName); - - fileLayout.addView(btnUpload); - fileLayout.addView(txtFileName); - container.addView(fileLayout); - - viewsMap.put(field.id, btnUpload); - reqMap.put(field.id, field.isRequired); - } - - private void showFileSourceDialog() { - String[] options = {"Ta bilde", "Velg fil"}; - new AlertDialog.Builder(getContext()) - .setTitle("Last opp vedlegg") - .setItems(options, (dialog, which) -> { - if (which == 0) { - if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) - == PackageManager.PERMISSION_GRANTED) { - openCamera(); - } else { - requestPermissionLauncher.launch(Manifest.permission.CAMERA); - } - } else { - if (filePickerLauncher != null) { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("*/*"); - String[] mimeTypes = {"image/jpeg", "image/png", "application/pdf"}; - intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); - filePickerLauncher.launch(intent); - } - } - }) - .show(); - } - - private void openCamera() { - currentPhotoUri = createImageUri(); - if (currentPhotoUri != null && takePictureLauncher != null) { - try { - takePictureLauncher.launch(currentPhotoUri); - } catch (Exception e) { - Toast.makeText(getContext(), "Kunne ikke starte kamera: " + e.getMessage(), Toast.LENGTH_SHORT).show(); - Log.e(TAG, "Camera launch failed", e); - } - } else { - Toast.makeText(getContext(), "Kunne ikke opprette bildefil", Toast.LENGTH_SHORT).show(); - } - } - - private Uri createImageUri() { - try { - String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); - String imageFileName = "JPEG_" + timeStamp + "_"; - File storageDir = requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES); - File image = File.createTempFile(imageFileName, ".jpg", storageDir); - return FileProvider.getUriForFile(requireContext(), "com.kbs.kbsintranett.fileprovider", image); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } - - private void handleFileSelection(String fieldId, Uri uri, boolean isChild) { - if (isChild) { - childFileUploads.put(fieldId, uri); - } else { - fileUploads.put(fieldId, uri); - } - - Map targetMap = isChild ? childInputViews : inputViews; - View view = targetMap.get(fieldId); - if (view instanceof Button) { - TextView txtView = (TextView) view.getTag(); - if (txtView != null) { - txtView.setText(getFileName(uri)); - txtView.setTextColor(Color.BLACK); - } - } - } - - private String getFileName(Uri uri) { - String result = null; - if (uri.getScheme().equals("content")) { - try (Cursor cursor = getContext().getContentResolver().query(uri, null, null, null, null)) { - if (cursor != null && cursor.moveToFirst()) { - int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); - if(index >= 0) result = cursor.getString(index); - } - } catch (Exception e) {} - } - if (result == null) { - result = uri.getPath(); - int cut = result.lastIndexOf('/'); - if (cut != -1) result = result.substring(cut + 1); - } - return result; - } - - private String getCleanTitle(String title) { - if (title == null) return ""; - Matcher m = TITLE_PATTERN.matcher(title.trim()); - if (m.find()) { - return m.group(2); - } - return title; - } - - // --- STANDARD RENDER METODER --- - - private void renderTimeField(LinearLayout container, GravityField field, Map views, Map req) { - EditText timeInput = new EditText(getContext()); - timeInput.setFocusable(false); - timeInput.setClickable(true); - timeInput.setHint("00:00"); - timeInput.setOnClickListener(v -> { - expandFormModule(); - Calendar mcurrentTime = Calendar.getInstance(); - int hour = mcurrentTime.get(Calendar.HOUR_OF_DAY); - int minute = mcurrentTime.get(Calendar.MINUTE); - new TimePickerDialog(getContext(), (timePicker, selectedHour, selectedMinute) -> { - timeInput.setText(String.format("%02d:%02d", selectedHour, selectedMinute)); - evaluateAllConditionalLogic(); - }, hour, minute, true).show(); - }); - container.addView(timeInput); - views.put(field.id, timeInput); - req.put(field.id, field.isRequired); - } - - private void renderTextField(LinearLayout container, GravityField field, Map views, Map req) { - EditText input = new EditText(getContext()); - input.setPadding(30, 30, 30, 30); - input.setBackgroundResource(android.R.drawable.edit_text); - - if ("number".equals(field.type) || "phone".equals(field.type)) { - input.setInputType(InputType.TYPE_CLASS_PHONE); - } else if ("email".equals(field.type)) { - input.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); - } else { - input.setInputType(InputType.TYPE_CLASS_TEXT); - } - - if (views == inputViews) { - UserManager user = UserManager.getInstance(); - String lowerLabel = field.label.toLowerCase(); - if (lowerLabel.contains("e-post")) input.setText(user.getUserEmail()); - if (lowerLabel.contains("navn") || lowerLabel.contains("melder")) input.setText(user.getUserDisplayName()); - if (lowerLabel.contains("stilling")) input.setText(user.getStilling()); - if (lowerLabel.contains("mobil")) input.setText(user.getMobiltelefon()); - } - - input.addTextChangedListener(new TextWatcher() { - public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - public void onTextChanged(CharSequence s, int start, int before, int count) {} - public void afterTextChanged(Editable s) { evaluateAllConditionalLogic(); } - }); - attachInteractionListener(input); - - container.addView(input); - views.put(field.id, input); - req.put(field.id, field.isRequired); - } - - private void renderTextAreaField(LinearLayout container, GravityField field, Map views, Map req) { - EditText input = new EditText(getContext()); - input.setBackgroundResource(android.R.drawable.edit_text); - input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE); - input.setMinLines(3); - input.setGravity(android.view.Gravity.TOP | android.view.Gravity.START); - - attachInteractionListener(input); - - container.addView(input); - views.put(field.id, input); - req.put(field.id, field.isRequired); - } - - private void renderRadioField(LinearLayout container, GravityField field, Map views, Map req) { - RadioGroup group = new RadioGroup(getContext()); - if (field.choices != null) { - for (GravityField.Choice choice : field.choices) { - RadioButton rb = new RadioButton(getContext()); - rb.setText(choice.text); - rb.setTag(choice.value); - rb.setOnClickListener(v -> { - expandFormModule(); - evaluateAllConditionalLogic(); - }); - group.addView(rb); - } - } - group.setOnCheckedChangeListener((g, i) -> { - expandFormModule(); - evaluateAllConditionalLogic(); - }); - container.addView(group); - views.put(field.id, group); - req.put(field.id, field.isRequired); - } - - private void renderSelectField(LinearLayout container, GravityField field, Map views, Map req) { - Spinner spinner = new Spinner(getContext()); - spinner.setOnTouchListener((v, event) -> { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - expandFormModule(); - } - return false; - }); - List labels = new ArrayList<>(); - labels.add("- Velg -"); - if (field.choices != null) { - for (GravityField.Choice c : field.choices) labels.add(c.text); - } - ArrayAdapter adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, labels); - spinner.setAdapter(adapter); - container.addView(spinner); - views.put(field.id, spinner); - req.put(field.id, field.isRequired); - } - - private void renderConsentField(LinearLayout container, GravityField field, Map views, Map req) { - CheckBox checkBox = new CheckBox(getContext()); - String cbText = (field.checkboxLabel != null && !field.checkboxLabel.isEmpty()) ? field.checkboxLabel : field.label; - checkBox.setText(cbText); - String inputId = (field.inputs != null && !field.inputs.isEmpty()) ? field.inputs.get(0).id : field.id; - - checkBox.setTag("1"); - checkBox.setOnCheckedChangeListener((b, c) -> { - expandFormModule(); - evaluateAllConditionalLogic(); - }); - container.addView(checkBox); - views.put(inputId, checkBox); - req.put(inputId, field.isRequired); - } - - private void renderCheckboxField(LinearLayout container, GravityField field, Map views, Map req) { - if (field.inputs != null) { - for (int i = 0; i < field.inputs.size(); i++) { - GravityField inputDef = field.inputs.get(i); - CheckBox checkBox = new CheckBox(getContext()); - checkBox.setText(inputDef.label); - - String value = "1"; - if (field.choices != null && i < field.choices.size()) { - value = field.choices.get(i).value; - } - checkBox.setTag(value); - checkBox.setOnCheckedChangeListener((b, c) -> { - expandFormModule(); - evaluateAllConditionalLogic(); - }); - container.addView(checkBox); - views.put(inputDef.id, checkBox); - req.put(inputDef.id, false); - } - } - } - - private void renderDateField(LinearLayout container, GravityField field, Map views, Map req) { - EditText dateInput = new EditText(getContext()); - if (field.readOnly || (formId == ID_REFUSJON_UTLEGG && "28".equals(field.id))) { - SimpleDateFormat df = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()); - dateInput.setText(df.format(new Date())); - - dateInput.setFocusable(false); - dateInput.setClickable(false); - dateInput.setEnabled(false); - dateInput.setTextColor(Color.BLACK); - } else { - dateInput.setFocusable(false); - dateInput.setClickable(true); - dateInput.setHint("dd.mm.yyyy"); - dateInput.setOnClickListener(v -> { - expandFormModule(); - Calendar c = Calendar.getInstance(); - new DatePickerDialog(getContext(), (view, year, month, dayOfMonth) -> { - dateInput.setText(String.format("%02d.%02d.%d", dayOfMonth, month + 1, year)); - evaluateAllConditionalLogic(); - }, c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH)).show(); - }); - } - - dateInput.setPadding(30, 30, 30, 30); - dateInput.setBackgroundResource(android.R.drawable.edit_text); - - container.addView(dateInput); - views.put(field.id, dateInput); - req.put(field.id, field.isRequired); - } - - private void renderCompositeField(LinearLayout container, GravityField parentField, Map views, Map req) { - UserManager user = UserManager.getInstance(); - boolean isPersonalia = (formId == ID_ANSATTEOPPLYSNINGER); - - List inputs = new ArrayList<>(parentField.inputs); - if ("address".equals(parentField.type)) { - Collections.sort(inputs, (f1, f2) -> Integer.compare(getAddressScore(f1.label), getAddressScore(f2.label))); - } - - for (GravityField subField : inputs) { - if (subField.isHidden || "hidden".equals(subField.visibility)) continue; - TextView subLabel = new TextView(getContext()); - String subLabelText = subField.label; - boolean isSubRequired = parentField.isRequired; - if ("address".equals(parentField.type) && subField.id.endsWith(".2")) { - isSubRequired = false; - } - if (isSubRequired) subLabelText += " *"; - - subLabel.setText(subLabelText); - subLabel.setTextColor(Color.GRAY); - subLabel.setTextSize(12); - subLabel.setPadding(0, 10, 0, 0); - container.addView(subLabel); - - EditText subInput = new EditText(getContext()); - subInput.setPadding(30, 30, 30, 30); - subInput.setBackgroundResource(android.R.drawable.edit_text); - subInput.setInputType(InputType.TYPE_CLASS_TEXT); - if (isPersonalia && parentField.label.toLowerCase().contains("navn") && !parentField.label.toLowerCase().contains("pårørende")) { - String lowerSub = subField.label.toLowerCase(); - if (lowerSub.contains("fornavn")) subInput.setText(user.getFirstName()); - else if (lowerSub.contains("etternavn")) subInput.setText(user.getLastName()); - } - - attachInteractionListener(subInput); - container.addView(subInput); - views.put(subField.id, subInput); - req.put(subField.id, isSubRequired); - } - } - - private void addSectionHeader(LinearLayout container, String title, String descText) { - TextView sectionHeader = new TextView(getContext()); - sectionHeader.setText(title); - sectionHeader.setTextSize(18); - sectionHeader.setTypeface(null, Typeface.BOLD); - sectionHeader.setTextColor(Color.parseColor("#0069B3")); - sectionHeader.setPadding(0, 20, 0, 5); - container.addView(sectionHeader); - if (descText != null && !descText.isEmpty()) { - TextView desc = new TextView(getContext()); - desc.setText(Html.fromHtml(descText, Html.FROM_HTML_MODE_COMPACT)); - desc.setTextSize(12); - desc.setTextColor(Color.GRAY); - container.addView(desc); - } - - View line = new View(getContext()); - line.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 2)); - line.setBackgroundColor(Color.LTGRAY); - line.setPadding(0,0,0,20); - container.addView(line); - } - - private int getAddressScore(String label) { - if (label == null) return 99; - String l = label.toLowerCase(); - if (l.contains("adresselinje 1")) return 1; - if (l.contains("adresselinje 2")) return 2; - if (l.contains("postnummer") || l.contains("zip")) return 3; - if (l.contains("poststed") || l.contains("city")) return 4; - if (l.contains("land") || l.contains("country")) return 5; - return 99; - } - - private void evaluateAllConditionalLogic() { - if (currentForm == null || currentForm.fields == null) return; - for (GravityField field : currentForm.fields) { - if (field.conditionalLogic == null) { - setViewVisibility(field.id, true); - continue; - } - - boolean isMatch = evaluateLogic(field.conditionalLogic); - boolean show = "show".equalsIgnoreCase(field.conditionalLogic.actionType); - boolean shouldBeVisible = (show && isMatch) || (!show && !isMatch); - setViewVisibility(field.id, shouldBeVisible); - } - } - - private boolean evaluateLogic(GravityField.ConditionalLogic logic) { - if (logic.rules == null || logic.rules.isEmpty()) return true; - boolean isAll = "all".equalsIgnoreCase(logic.logicType); - boolean aggregatedResult = isAll; - - for (GravityField.Rule rule : logic.rules) { - String val = getInputValue(rule.fieldId); - boolean ruleMatch = checkRule(val, rule.operator, rule.value); - - if (isAll) { - aggregatedResult = aggregatedResult && ruleMatch; - if (!aggregatedResult) break; - } else { - aggregatedResult = aggregatedResult || - ruleMatch; - if (aggregatedResult) break; - } - } - return aggregatedResult; - } - - private boolean checkRule(String actualValue, String operator, String targetValue) { - if (actualValue == null) actualValue = ""; - if (targetValue == null) targetValue = ""; - - switch (operator.toLowerCase()) { - case "is": return actualValue.equalsIgnoreCase(targetValue); - case "isnot": return !actualValue.equalsIgnoreCase(targetValue); - case "contains": return actualValue.toLowerCase().contains(targetValue.toLowerCase()); - case "starts_with": return actualValue.toLowerCase().startsWith(targetValue.toLowerCase()); - case "ends_with": return actualValue.toLowerCase().endsWith(targetValue.toLowerCase()); - default: return false; - } - } - - private String getInputValue(String fieldId) { - View view = inputViews.get(fieldId); - return getInputValueGeneric(view); - } - - private String getInputValueGeneric(View view) { - if (view == null) return ""; - if (view instanceof EditText) return ((EditText) view).getText().toString(); - if (view instanceof RadioGroup) { - int id = ((RadioGroup) view).getCheckedRadioButtonId(); - if (id != -1) { - View rb = view.findViewById(id); - if (rb != null && rb.getTag() != null) return rb.getTag().toString(); - } - } - if (view instanceof Spinner) { - if (((Spinner) view).getSelectedItemPosition() == 0) return ""; - Object item = ((Spinner) view).getSelectedItem(); - return item != null ? item.toString() : ""; - } - if (view instanceof CheckBox) { - CheckBox cb = (CheckBox) view; - if (cb.isChecked()) { - return cb.getTag() != null ? - cb.getTag().toString() : "1"; - } - return ""; - } - return ""; - } - - private void setViewVisibility(String fieldId, boolean visible) { - View wrapper = fieldWrappers.get(fieldId); - if (wrapper != null) { - wrapper.setVisibility(visible ? View.VISIBLE : View.GONE); - } - } - - // --- SUBMISSION --- - - private void submitDynamicForm() { - JSONObject inputValues = new JSONObject(); - boolean hasValues = false; - - Log.d(TAG, "submitDynamicForm: Starting validation..."); - - for (Map.Entry entry : inputViews.entrySet()) { - String fieldId = entry.getKey(); - View view = entry.getValue(); - - View wrapper = fieldWrappers.get(fieldId); - if (wrapper == null) { - if (!view.isShown()) continue; - } else { - if (wrapper.getVisibility() != View.VISIBLE) continue; - } - - String val = getInputValueGeneric(view); - Boolean req = requiredFieldsMap.get(fieldId); - if (req != null && req && val.isEmpty() && !(view instanceof Button)) { - Log.d(TAG, "Validation failed for field " + fieldId); - if (view instanceof EditText) { - ((EditText)view).setError("Må fylles ut"); - view.requestFocus(); - } else { - Toast.makeText(getContext(), "Fyll ut alle felt", Toast.LENGTH_SHORT).show(); - } - return; - } - if (!val.isEmpty()) { - try { - GravityField fieldDef = getGravityFieldById(fieldId); - if (fieldDef != null && "date".equals(fieldDef.type)) { - val = formatDateForApi(val); - } - - inputValues.put("input_" + fieldId, val); - hasValues = true; - } catch (JSONException e) {} - } - } - - if (!hasValues && fileUploads.isEmpty()) { - Log.d(TAG, "Submit aborted: Form is empty"); - Toast.makeText(getContext(), "Skjemaet er tomt", Toast.LENGTH_SHORT).show(); - return; - } - - updateStatus("Sender inn..."); - String cookie = UserManager.getInstance().getCookie(); - Log.d(TAG, "Preparing submission payload: " + inputValues.toString()); - - if (!fileUploads.isEmpty()) { - Log.d(TAG, "Submitting as Multipart..."); - sendMultipart(inputValues); - } else { - Log.d(TAG, "Submitting as JSON..."); - RequestBody body = RequestBody.create(MediaType.parse("application/json"), inputValues.toString()); - String url = BASE_URL_GF + "/forms/" + formId + "/submissions"; - Request request = new Request.Builder().url(url).post(body).header("Cookie", cookie).build(); - client.newCall(request).enqueue(new okhttp3.Callback() { - public void onFailure(okhttp3.Call call, IOException e) { - Log.e(TAG, "JSON submit failed", e); - updateStatus("Feil: " + e.getMessage()); - } - public void onResponse(okhttp3.Call call, Response response) { - Log.d(TAG, "JSON response code: " + response.code()); - if (response.isSuccessful()) { - if (getActivity() != null) getActivity().runOnUiThread(() -> { - Toast.makeText(getContext(), "Sendt!", Toast.LENGTH_LONG).show(); - fetchFormEntries(); - updateStatus("OK"); - clearInputs(); - }); - } else { - try { - String errBody = response.body() != null ? response.body().string() : "No body"; - Log.e(TAG, "Server error body: " + errBody); - updateStatus("Feil (" + response.code() + "): " + errBody); - } catch(Exception e){} - } - } - }); - } - } - - private String formatDateForApi(String dateStr) { - if (dateStr == null || dateStr.isEmpty()) return ""; - try { - SimpleDateFormat displayFormat = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()); - Date date = displayFormat.parse(dateStr); - SimpleDateFormat apiFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); - return apiFormat.format(date); - } catch (Exception e) { - return dateStr; - } - } - - private GravityField getGravityFieldById(String id) { - if (currentForm == null || currentForm.fields == null) return null; - for (GravityField f : currentForm.fields) { - if (f.id.equals(id)) return f; - if (f.inputs != null) { - for (GravityField sub : f.inputs) { - if (sub.id.equals(id)) return sub; - } - } - } - return null; - } - - private void sendMultipart(JSONObject inputValues) { - List fileParts = new ArrayList<>(); - Map textParts = new HashMap<>(); - try { - JSONArray names = inputValues.names(); - if (names != null) { - for(int i=0; i entry : fileUploads.entrySet()) { - MultipartBody.Part part = getFilePart("input_" + entry.getKey(), entry.getValue()); - if (part != null) fileParts.add(part); - } - RetrofitClient.getApiService().submitMultipartForm(formId, textParts, fileParts).enqueue(new retrofit2.Callback() { - public void onResponse(retrofit2.Call call, retrofit2.Response response) { - if (response.isSuccessful()) { - if (getActivity() != null) getActivity().runOnUiThread(() -> { - Toast.makeText(getContext(), "Sendt!", Toast.LENGTH_LONG).show(); - fetchFormEntries(); - updateStatus("OK"); - clearInputs(); - }); - } else { - updateStatus("Feil: " + response.code()); - } - } - public void onFailure(retrofit2.Call call, Throwable t) { updateStatus("Feil: " + t.getMessage()); } - }); - } catch (Exception e) {} - } - - private MultipartBody.Part getFilePart(String partName, Uri uri) { - try { - InputStream inputStream = getContext().getContentResolver().openInputStream(uri); - String fileName = getFileName(uri); - RequestBody requestBody = new RequestBody() { - @Override public MediaType contentType() { return MediaType.parse("application/octet-stream"); - } - @Override public void writeTo(BufferedSink sink) throws IOException { - try (Source source = Okio.source(inputStream)) { sink.writeAll(source); - } - } - }; - return MultipartBody.Part.createFormData(partName, fileName, requestBody); - } catch (Exception e) { return null; - } - } - - private void fetchFormEntries() { - UserManager user = UserManager.getInstance(); - String cookie = user.getCookie(); - int userId = user.getUserId(); - - if (cookie == null) return; - String searchJson = "{\"field_filters\":[{\"key\":\"created_by\",\"value\":\"" + userId + "\"}]}"; - String encodedSearch = ""; - try { - encodedSearch = URLEncoder.encode(searchJson, "UTF-8"); - } catch (UnsupportedEncodingException e) { e.printStackTrace(); } - - String url = BASE_URL_GF + "/entries?form_ids=" + formId + "&search=" + encodedSearch; - Request request = new Request.Builder().url(url).header("Cookie", cookie).build(); - - client.newCall(request).enqueue(new okhttp3.Callback() { - @Override - public void onFailure(@NonNull okhttp3.Call call, @NonNull IOException e) { - Log.e(TAG, "Kunne ikke hente historikk", e); - } - - @Override - public void onResponse(@NonNull okhttp3.Call call, @NonNull Response response) throws IOException { - if (response.isSuccessful()) { - String jsonStr = response.body().string(); - try { - JSONObject json = new JSONObject(jsonStr); - if (json.has("entries")) { - JSONArray entries = json.getJSONArray("entries"); - if (getActivity() != null) { - getActivity().runOnUiThread(() -> { - showHistory(entries); - if (formId == ID_ANSATTEOPPLYSNINGER && entries.length() > 0) - { - try { - prefillFormFromHistory(entries.getJSONObject(0)); - } catch (JSONException e) { e.printStackTrace(); } - } - }); - } - } - } catch (JSONException e) { e.printStackTrace(); - } - } - } - }); - } - - private void showHistory(JSONArray entries) { - if (historyContainer == null) return; - historyContainer.removeAllViews(); - - if (entries.length() == 0) { - if (lblHistory != null) lblHistory.setVisibility(View.GONE); - return; - } else { - if (lblHistory != null) lblHistory.setVisibility(View.VISIBLE); - } - - try { - 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"); - - 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); - item.setOnClickListener(v -> showEntryDetails(entry)); - View line = new View(getContext()); - line.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1)); - line.setBackgroundColor(Color.LTGRAY); - - historyContainer.addView(item); - historyContainer.addView(line); - } - } catch (JSONException e) { e.printStackTrace(); - } - } - - private void showEntryDetails(JSONObject entry) { - if (formId == ID_REFUSJON_UTLEGG) { - Log.d(TAG, "Form 16 detected. Checking for child entries..."); - String nestedIds = entry.optString("25"); - - if (!nestedIds.isEmpty()) { - Log.d(TAG, "Nested IDs found: " + nestedIds); - List ids = new ArrayList<>(); - - if (nestedIds.startsWith("[") && nestedIds.endsWith("]")) { - try { - JSONArray jsonArray = new JSONArray(nestedIds); - for(int i=0; iInnsendt: ").append(date).append("

"); - text.append("Innsendt: ").append(date).append("\n\n"); - - if (currentForm != null && currentForm.fields != null) { - for (GravityField field : currentForm.fields) { - if ("section".equals(field.type) || "html".equals(field.type) || "captcha".equals(field.type)) continue; - if (formId == ID_REFUSJON_UTLEGG && "25".equals(field.id)) continue; - - String value = ""; - if (field.inputs != null && !field.inputs.isEmpty()) { - for (GravityField input : field.inputs) { - String subVal = entry.optString(input.id); - if (!subVal.isEmpty()) value += " " + subVal; - } - } else { - value = entry.optString(String.valueOf(field.id)); - } - - if (!value.trim().isEmpty()) { - if ("fileupload".equals(field.type)) { - String cleanUrl = extractUrl(value); - if (cleanUrl.startsWith("http")) { - html.append("").append(field.label).append(":
") - .append("Åpne fil

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

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

"); - text.append(field.label).append(":\n").append(value).append("\n\n"); - } - } - } - } - } catch (Exception e) {} - } - - private void fetchChildEntriesRecursive(List ids, int index, StringBuilder html, StringBuilder text, AlertDialog loader) { - if (index >= ids.size()) { - loader.dismiss(); - showFinalDialog(html, text); - return; - } - - String entryId = ids.get(index); - RetrofitClient.getApiService().getSingleEntry(entryId).enqueue(new retrofit2.Callback() { - @Override - public void onResponse(retrofit2.Call call, retrofit2.Response response) { - if (response.isSuccessful() && response.body() != null) { - try { - JsonObject json = response.body().getAsJsonObject(); - String desc = json.has("3") ? json.get("3").getAsString() : "Uten beskrivelse"; - String price = json.has("4") ? json.get("4").getAsString() : ""; - - html.append("Vedlegg ").append(index + 1).append(":
"); - text.append("Vedlegg ").append(index + 1).append(":\n"); - - html.append(desc).append(" (").append(price).append(")
"); - text.append(desc).append(" (").append(price).append(")\n"); - - if (json.has("1")) { - JsonElement fileEl = json.get("1"); - if (fileEl.isJsonArray()) { - JsonArray arr = fileEl.getAsJsonArray(); - for (int i = 0; i < arr.size(); i++) { - String url = arr.get(i).getAsString().replace("\\/", "/"); - html.append("Åpne fil ").append(i+1).append("
"); - text.append(url).append("\n"); - } - } else { - String rawString = fileEl.getAsString(); - if (rawString.startsWith("[") && rawString.endsWith("]")) { - try { - JSONArray arr = new JSONArray(rawString); - for (int i = 0; i < arr.length(); i++) { - String url = arr.getString(i).replace("\\/", "/"); - html.append("Åpne fil ").append(i+1).append("
"); - text.append(url).append("\n"); - } - } catch (JSONException ex) { - String clean = extractUrl(rawString); - html.append("Åpne fil
"); - text.append(clean).append("\n"); - } - } else { - String clean = extractUrl(rawString); - if(clean.startsWith("http")) { - html.append("Åpne fil
"); - text.append(clean).append("\n"); - } - } - } - } - html.append("
"); - text.append("\n"); - - } catch (Exception e) { - Log.e(TAG, "Error parsing child entry", e); - } - } - fetchChildEntriesRecursive(ids, index + 1, html, text, loader); - } - - @Override - public void onFailure(retrofit2.Call call, Throwable t) { - fetchChildEntriesRecursive(ids, index + 1, html, text, loader); - } - }); - } - - private void showFinalDialog(StringBuilder htmlBuilder, StringBuilder textBuilder) { - ScrollView scroll = new ScrollView(getContext()); - TextView text = new TextView(getContext()); - text.setMovementMethod(android.text.method.LinkMovementMethod.getInstance()); - text.setText(Html.fromHtml(htmlBuilder.toString(), Html.FROM_HTML_MODE_COMPACT)); - text.setPadding(40, 40, 40, 40); - scroll.addView(text); - - new AlertDialog.Builder(getContext()) - .setTitle("Detaljer") - .setView(scroll) - .setPositiveButton("Lukk", null) - .setNeutralButton("Del", (d, w) -> shareEntryDetails(textBuilder.toString())) - .create() - .show(); - } - - private String extractUrl(String rawValue) { - if (rawValue == null) return ""; - String clean = rawValue.replace("[", "") - .replace("]", "") - .replace("\"", "") - .replace("\\/", "/"); - return clean.trim(); - } - - private void shareEntryDetails(String text) { - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, text); - sendIntent.setType("text/plain"); - - Intent shareIntent = Intent.createChooser(sendIntent, "Del innsending via..."); - startActivity(shareIntent); - } - - private void prefillFormFromHistory(JSONObject latestEntry) { - if (latestEntry == null) return; - for (Map.Entry entry : inputViews.entrySet()) { - String fieldId = entry.getKey(); - View view = entry.getValue(); - - if (latestEntry.has(fieldId)) { - 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; - } - } - } - } else if (view instanceof CheckBox) { - 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 clearInputs() { - for (View view : inputViews.values()) { - if (view instanceof EditText) { - ((EditText) view).setText(""); - } else if (view instanceof CheckBox) { - ((CheckBox) view).setChecked(false); - } else if (view instanceof RadioGroup) { - ((RadioGroup) view).clearCheck(); - } else if (view instanceof Button) { - Object tag = view.getTag(); - if (tag instanceof TextView) { - ((TextView) tag).setText("Ingen fil valgt"); - } - } - } - fileUploads.clear(); - nestedEntries.clear(); - if (nestedEntriesContainer != null) nestedEntriesContainer.removeAllViews(); - if (totalAmountView != null) totalAmountView.setText("Kr 0,00"); - } - - 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; - } - } - - private void updateStatus(String msg) { - if (getActivity() != null && txtStatus != null) { - getActivity().runOnUiThread(() -> txtStatus.setText(msg)); - } - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\FormsListFragment.java -============================================================ -package com.kbs.kbsintranett; - -import android.graphics.Color; -import android.os.Bundle; -import android.view.Gravity; -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 androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -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 LinearLayout formsContainer; - private ProgressBar progressBar; - private TextView errorText; - private SwipeRefreshLayout swipeRefreshLayout; - private static final Pattern TITLE_NUMBER_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_list, container, false); - - formsContainer = view.findViewById(R.id.forms_container); - swipeRefreshLayout = view.findViewById(R.id.swipe_refresh); - - // Opprett en ProgressBar manuelt for første gangs lasting - progressBar = new ProgressBar(getContext()); - LinearLayout.LayoutParams progressParams = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - progressParams.gravity = Gravity.CENTER; - progressBar.setLayoutParams(progressParams); - - formsContainer.addView(progressBar); - - errorText = new TextView(getContext()); - errorText.setTextColor(Color.RED); - errorText.setVisibility(View.GONE); - errorText.setPadding(20, 20, 20, 20); - formsContainer.addView(errorText); - - // Sett opp listener for swipe - swipeRefreshLayout.setOnRefreshListener(() -> { - fetchFormsList(); - }); - - // Hent data første gang - fetchFormsList(); - - return view; - } - - private void fetchFormsList() { - // Skjul feilmelding før ny henting - if (errorText != null) errorText.setVisibility(View.GONE); - - RetrofitClient.getApiService().getFormsList().enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (!isAdded()) return; - - // Stopp lasting-indikatorer - progressBar.setVisibility(View.GONE); - swipeRefreshLayout.setRefreshing(false); - - if (response.isSuccessful() && response.body() != null) { - List allForms = response.body(); - List activeForms = new ArrayList<>(); - - for (GravityForm form : allForms) { - if (form.getIsActive()) { - // NYTT: Hardkodet filtrering av skjemaer som ikke skal vises i listen - // ID 10 = HMS-bekreftelse (Skal ligge i Håndbok/separat flyt) - // ID 18 = Refusjon-vedlegg (Er et underskjema som brukes av ID 16) - if (form.id == 10 || form.id == 18) { - continue; - } - - activeForms.add(form); - } - } - - Collections.sort(activeForms, new Comparator() { - @Override - public int compare(GravityForm f1, GravityForm f2) { - int num1 = extractNumber(f1.title); - int num2 = extractNumber(f2.title); - return Integer.compare(num1, num2); - } - }); - populateList(activeForms); - - } else { - String msg = "Kunne ikke hente skjemaer. Kode: " + response.code(); - if (response.code() == 401 || response.code() == 403) { - msg += "\n(Mangler tilgang. Prøv å logge ut og inn igjen.)"; - } - showError(msg); - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - if (!isAdded()) return; - - // Stopp lasting-indikatorer - progressBar.setVisibility(View.GONE); - swipeRefreshLayout.setRefreshing(false); - - showError("Nettverksfeil: " + t.getMessage()); - } - }); - } - - private void populateList(List forms) { - formsContainer.removeAllViews(); - - if (forms.isEmpty()) { - showError("Ingen aktive skjemaer funnet."); - return; - } - - for (GravityForm form : forms) { - int formId = form.id; - String cleanTitle = cleanTitle(form.title); - addFormButton(formsContainer, cleanTitle, formId); - } - } - - private void addFormButton(LinearLayout container, String title, int formId) { - Button btn = new Button(getContext()); - btn.setText(title); - btn.setBackgroundColor(Color.parseColor("#0069B3")); - 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); - btn.setLayoutParams(params); - - btn.setOnClickListener(v -> { - Bundle bundle = new Bundle(); - bundle.putInt("formId", formId); - Navigation.findNavController(v).navigate(R.id.action_formsListFragment_to_formsDetailFragment, bundle); - }); - container.addView(btn); - } - - private void showError(String message) { - if (formsContainer == null) return; - formsContainer.removeAllViews(); - - TextView tv = new TextView(getContext()); - tv.setText(message); - tv.setTextColor(Color.RED); - tv.setTextSize(16); - formsContainer.addView(tv); - } - - private int extractNumber(String title) { - if (title == null) return 9999; - Matcher m = TITLE_NUMBER_PATTERN.matcher(title.trim()); - if (m.find()) { - try { - return Integer.parseInt(m.group(1)); - } catch (NumberFormatException e) { - return 9999; - } - } - return 9999; - } - - private String cleanTitle(String title) { - if (title == null) return ""; - Matcher m = TITLE_NUMBER_PATTERN.matcher(title.trim()); - if (m.find()) { - return m.group(2); - } - return title; - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\FormSubmission.java -============================================================ -package com.kbs.kbsintranett; - -import com.google.gson.annotations.SerializedName; -import java.util.Map; - -public class FormSubmission { - // Gravity Forms krever at dataene ligger inni "input_values" - @SerializedName("input_values") - public Map inputValues; - - public FormSubmission(Map inputValues) { - this.inputValues = inputValues; - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\GoogleCalendarModels.java -============================================================ -package com.kbs.kbsintranett; - -import com.google.gson.annotations.SerializedName; -import java.util.List; - -/** - * Hjelpeklasser for å parse JSON direkte fra Google Calendar API v3. - */ -public class GoogleCalendarModels { - - public static class Response { - @SerializedName("items") - public List items; - } - - public static class Item { - @SerializedName("summary") - public String summary; - - @SerializedName("description") - public String description; - - @SerializedName("location") - public String location; - - @SerializedName("start") - public TimePoint start; - - @SerializedName("end") - public TimePoint end; - - @SerializedName("reminders") - public Reminders reminders; - } - - public static class TimePoint { - @SerializedName("dateTime") - public String dateTime; // Format: 2025-12-15T10:00:00+01:00 - - @SerializedName("date") - public String date; // Format: 2025-12-15 (for heldags) - } - - public static class Reminders { - @SerializedName("useDefault") - public boolean useDefault; - - @SerializedName("overrides") - public List overrides; - } - - public static class Override { - @SerializedName("method") - public String method; // f.eks "popup" - - @SerializedName("minutes") - public int minutes; - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\GravityEntryResponse.java -============================================================ -package com.kbs.kbsintranett; - -import com.google.gson.annotations.SerializedName; -import java.util.List; -import java.util.Map; - -public class GravityEntryResponse { - @SerializedName("total_count") - public int totalCount; - - @SerializedName("entries") - public List> entries; - // Vi bruker Map fordi Gravity Forms returnerer alle feltverdier som nøkkel/verdi par i roten av objektet. - // F.eks: { "id": "100", "form_id": "1", "1.3": "Ola", "1.6": "Nordmann" } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\GravityField.java -============================================================ -package com.kbs.kbsintranett; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.annotations.SerializedName; -import java.util.List; - -public class GravityField { - @SerializedName("id") - public String id; - - @SerializedName("type") - public String type; - - @SerializedName("inputType") - public String inputType; - - @SerializedName("label") - public String label; - - @SerializedName("adminLabel") - public String adminLabel; - - @SerializedName("description") - public String description; - - @SerializedName("defaultValue") - public String defaultValue; - - @SerializedName("isRequired") - public boolean isRequired; - - @SerializedName("checkboxLabel") - public String checkboxLabel; - - @SerializedName("visibility") - public String visibility; - - @JsonAdapter(ChoicesAdapter.class) - @SerializedName("choices") - public List choices; - - @SerializedName("content") - public String content; - - // --- BRUKER ADAPTEREN HER --- - @JsonAdapter(InputsAdapter.class) - @SerializedName("inputs") - public List inputs; - // --------------------------- - - @SerializedName("isHidden") - public boolean isHidden; - - @SerializedName("gwreadonly_enable") - public boolean readOnly; - - @JsonAdapter(ConditionalLogicAdapter.class) - @SerializedName("conditionalLogic") - public ConditionalLogic conditionalLogic; - - @SerializedName("gppa-values-templates") - public java.util.Map gppaTemplates; - - @SerializedName("gpnfForm") - public String gpnfForm; - - public static class Choice { - @SerializedName("text") public String text; - @SerializedName("value") public String value; - } - - public static class ConditionalLogic { - @SerializedName("actionType") public String actionType; - @SerializedName("logicType") public String logicType; - @SerializedName("rules") public List rules; - } - - public static class Rule { - @SerializedName("fieldId") public String fieldId; - @SerializedName("operator") public String operator; - @SerializedName("value") public String value; - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\GravityForm.java -============================================================ -package com.kbs.kbsintranett; - -import com.google.gson.annotations.SerializedName; -import java.util.List; - -public class GravityForm { - @SerializedName("id") - public int id; - - @SerializedName("title") - public String title; - - @SerializedName("description") - public String description; - - // Endret til Object for å være robust mot både "1" (String) og 1 (Int) fra API - @SerializedName("is_active") - public Object isActive; - - @SerializedName("fields") - public List fields; - - // Hjelpemetode for å sjekke om skjemaet er aktivt - public boolean getIsActive() { - if (isActive == null) return false; - String s = isActive.toString(); - return "1".equals(s) || "true".equalsIgnoreCase(s); - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookAdapter.java -============================================================ -// FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookAdapter.java -package com.kbs.kbsintranett; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; -import java.util.ArrayList; -import java.util.List; - -public class HandbookAdapter extends RecyclerView.Adapter { - - private List fullList; - private List filteredList; - private OnItemClickListener listener; - - public interface OnItemClickListener { - void onItemClick(HandbookItem item); - } - - public HandbookAdapter(List items, OnItemClickListener listener) { - this.fullList = items; - this.filteredList = new ArrayList<>(items); - this.listener = listener; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_handbook, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - HandbookItem item = filteredList.get(position); - holder.title.setText(item.getTitle()); - - // Håndter ingress: Kutt etter 80 tegn hvis den er veldig lang - String desc = item.getDescription(); - if (desc == null) desc = ""; - if (desc.length() > 100) { - desc = desc.substring(0, 100) + "..."; - } - holder.desc.setText(desc); - - // Mapp ikon-type til ressurs (Utvidet liste fra PHP v3.0) - int iconRes = R.drawable.ic_handbook_general; // Fallback - String type = item.getIconType() != null ? item.getIconType() : ""; - - switch (type) { - case "car": iconRes = R.drawable.ic_handbook_car; break; - case "health": iconRes = R.drawable.ic_handbook_health; break; - case "people": iconRes = R.drawable.ic_handbook_people; break; - case "warning": iconRes = R.drawable.ic_handbook_warning; break; - case "doc": iconRes = R.drawable.ic_handbook_doc; break; - case "card": iconRes = R.drawable.ic_handbook_doc; break; // Bruker doc inntil videre - case "computer": iconRes = R.drawable.ic_handbook_general; break; - case "calendar": iconRes = R.drawable.ic_handbook_general; break; - case "money": iconRes = R.drawable.ic_handbook_doc; break; - case "helmet": iconRes = R.drawable.ic_handbook_warning; break; - case "trash": iconRes = R.drawable.ic_handbook_general; break; - case "book": iconRes = R.drawable.ic_book; break; // Gjenbruk eksisterende ic_book - case "chat": iconRes = R.drawable.ic_handbook_people; break; - default: iconRes = R.drawable.ic_handbook_general; break; - } - holder.icon.setImageResource(iconRes); - - holder.itemView.setOnClickListener(v -> listener.onItemClick(item)); - } - - @Override - public int getItemCount() { - return filteredList.size(); - } - - public void filter(String query) { - filteredList.clear(); - if (query.isEmpty()) { - filteredList.addAll(fullList); - } else { - String q = query.toLowerCase(); - for (HandbookItem item : fullList) { - if (item.getTitle().toLowerCase().contains(q) || - (item.getDescription() != null && item.getDescription().toLowerCase().contains(q))) { - filteredList.add(item); - } - } - } - notifyDataSetChanged(); - } - - public static class ViewHolder extends RecyclerView.ViewHolder { - TextView title, desc; - ImageView icon; - - public ViewHolder(View view) { - super(view); - title = view.findViewById(R.id.title); - desc = view.findViewById(R.id.desc); - icon = view.findViewById(R.id.icon); - } - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookDetailFragment.java -============================================================ -package com.kbs.kbsintranett; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.webkit.WebResourceRequest; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.ProgressBar; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.Toolbar; -import androidx.fragment.app.Fragment; -import androidx.navigation.Navigation; - -import com.google.gson.JsonObject; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class HandbookDetailFragment extends Fragment { - - private int pageId; - private String pageTitle; - private WebView webView; - private ProgressBar progressBar; - - // --- CSS: DESIGN MED MER LUFT --- - private static final String CSS_STYLE = - ""; - - // --- JAVASCRIPT: AUTOSCROLL --- - private static final String JS_SCRIPT = - ""; - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_handbook_detail, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - if (getArguments() != null) { - pageId = getArguments().getInt("page_id"); - pageTitle = getArguments().getString("page_title"); - } - - Toolbar toolbar = view.findViewById(R.id.detail_toolbar); - toolbar.setTitle(pageTitle != null ? pageTitle : "Håndbok"); - toolbar.setNavigationOnClickListener(v -> Navigation.findNavController(view).navigateUp()); - - webView = view.findViewById(R.id.detail_webview); - progressBar = view.findViewById(R.id.detail_loading); - - setupWebView(); - fetchContent(); - } - - private void setupWebView() { - WebSettings settings = webView.getSettings(); - settings.setJavaScriptEnabled(true); - settings.setDomStorageEnabled(true); - settings.setCacheMode(WebSettings.LOAD_NO_CACHE); - - webView.setWebViewClient(new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - return handleLinkClick(request.getUrl().toString()); - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - return handleLinkClick(url); - } - }); - } - - private boolean handleLinkClick(String url) { - // Ignorer klikk på accordion-lenker - if (url.endsWith("#")) return true; - - String lowerUrl = url.toLowerCase(); - int formIdToOpen = 0; - - // --- SPESIALHÅNDTERING: Link til Skjemaer basert på URL-nøkkelord --- - - // ID 1: Ansatteopplysninger - if (lowerUrl.contains("ansatteopplysninger")) { - formIdToOpen = 1; - } - // ID 2: Vernerunde - else if (lowerUrl.contains("vernerunde")) { - formIdToOpen = 2; - } - // ID 4: RUH (Rapport om uønsket hendelse) - else if (lowerUrl.contains("uonsket-hendelse") || lowerUrl.contains("/ruh")) { - formIdToOpen = 4; - } - // ID 5: Lån av verktøy/henger - else if (lowerUrl.contains("lan-av") || lowerUrl.contains("verktoy")) { - formIdToOpen = 5; - } - // ID 6: Avviksmelding - else if (lowerUrl.contains("avviksmelding") || lowerUrl.contains("/avvik")) { - formIdToOpen = 6; - } - // ID 9: Sikkerhetskurs / Kompetansebevis - else if (lowerUrl.contains("sikkerhetskurs") || lowerUrl.contains("kompetansebevis")) { - formIdToOpen = 9; - } - // ID 10: HMS Bekreftelse - else if (lowerUrl.contains("hms-bekreftelse") || lowerUrl.contains("hms-policy")) { - formIdToOpen = 10; - } - // ID 11: Egenmelding - else if (lowerUrl.contains("egenmelding")) { - formIdToOpen = 11; - } - // ID 12: Sjekkliste firmabil - else if (lowerUrl.contains("sjekkliste") && (lowerUrl.contains("bil") || lowerUrl.contains("kjoretoy"))) { - formIdToOpen = 12; - } - // ID 14: SJA (Sikker Jobbanalyse) - else if (lowerUrl.contains("sja") || lowerUrl.contains("jobbanalyse")) { - formIdToOpen = 14; - } - // ID 15: Fraværsvarsel - else if (lowerUrl.contains("fravaersvarsel") || lowerUrl.contains("fravarsvarsel")) { - formIdToOpen = 15; - } - // ID 16: Refusjon utlegg - else if (lowerUrl.contains("refusjon") || lowerUrl.contains("utlegg")) { - formIdToOpen = 16; - } - // ID 21: Medarbeidersamtale - else if (lowerUrl.contains("medarbeidersamtale")) { - formIdToOpen = 21; - } - // ID 22: Medarbeiderundersøkelse - else if (lowerUrl.contains("medarbeiderundersokelse")) { - formIdToOpen = 22; - } - - // Hvis vi fant et skjema, naviger dit internt - if (formIdToOpen > 0) { - Bundle bundle = new Bundle(); - bundle.putInt("formId", formIdToOpen); - Navigation.findNavController(getView()).navigate(R.id.action_handbook_to_form, bundle); - return true; - } - - // --- STANDARD INTERN NAVIGASJON --- - if (url.contains("intranet.kbs.no") || url.startsWith("/")) { - int targetId = extractIdFromUrl(url); - if (targetId > 0) { - navigateToPage(targetId, "Laster..."); - return true; - } - - progressBar.setVisibility(View.VISIBLE); - RetrofitClient.getApiService().lookupPageId(url).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (!isAdded()) return; - progressBar.setVisibility(View.GONE); - - if (response.isSuccessful() && response.body() != null) { - int id = response.body().get("id").getAsInt(); - if (id > 0) { - navigateToPage(id, "Laster..."); - } else { - openExternal(url); - } - } else { - openExternal(url); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - if (!isAdded()) return; - progressBar.setVisibility(View.GONE); - openExternal(url); - } - }); - return true; - } else { - // Ekstern lenke - Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - startActivity(browserIntent); - return true; - } - } - - private void navigateToPage(int id, String title) { - Bundle bundle = new Bundle(); - bundle.putInt("page_id", id); - bundle.putString("page_title", title); - Navigation.findNavController(getView()).navigate(R.id.action_handbook_to_detail, bundle); - } - - private void openExternal(String url) { - Intent intent = new Intent(getContext(), WebViewActivity.class); - intent.putExtra(WebViewActivity.EXTRA_URL, url); - intent.putExtra(WebViewActivity.EXTRA_TITLE, "KBS Intranett"); - startActivity(intent); - } - - private int extractIdFromUrl(String url) { - Pattern p = Pattern.compile("[?&](p|page_id|post)=([0-9]+)"); - Matcher m = p.matcher(url); - if (m.find()) { - try { - return Integer.parseInt(m.group(2)); - } catch (NumberFormatException e) { - return 0; - } - } - return 0; - } - - private void fetchContent() { - progressBar.setVisibility(View.VISIBLE); - RetrofitClient.getApiService().getHandbookPage(pageId).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (!isAdded()) return; - progressBar.setVisibility(View.GONE); - - if (response.isSuccessful() && response.body() != null) { - HandbookPage page = response.body(); - - if (getView() != null) { - Toolbar toolbar = getView().findViewById(R.id.detail_toolbar); - if (toolbar != null) toolbar.setTitle(page.title); - } - - String htmlContent = "" + - "" + - CSS_STYLE + - JS_SCRIPT + - ""; - - htmlContent += "

" + page.title + "

"; - - if (page.content != null) { - htmlContent += page.content; - } else { - htmlContent += "

Ingen innhold funnet.

"; - } - htmlContent += ""; - - webView.loadDataWithBaseURL("https://intranet.kbs.no", htmlContent, "text/html", "UTF-8", null); - - } else { - Toast.makeText(getContext(), "Kunne ikke laste innhold.", Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - if (!isAdded()) return; - progressBar.setVisibility(View.GONE); - Toast.makeText(getContext(), "Nettverksfeil", Toast.LENGTH_SHORT).show(); - } - }); - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookFragment.java -============================================================ -// FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookFragment.java -package com.kbs.kbsintranett; - -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.ProgressBar; -import android.widget.Toast; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.navigation.Navigation; // Viktig -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import java.util.List; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class HandbookFragment extends Fragment { - - private RecyclerView recyclerView; - private ProgressBar progressBar; - private EditText searchField; - private HandbookAdapter adapter; - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_handbook, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - recyclerView = view.findViewById(R.id.recycler_handbook); - progressBar = view.findViewById(R.id.progressBar); - searchField = view.findViewById(R.id.search_field); - - recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2)); - - searchField.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) { - if (adapter != null) adapter.filter(s.toString()); - } - }); - - fetchHandbook(); - } - - private void fetchHandbook() { - RetrofitClient.getApiService().getHandbookItems().enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (!isAdded()) return; - progressBar.setVisibility(View.GONE); - - if (response.isSuccessful() && response.body() != null) { - adapter = new HandbookAdapter(response.body(), item -> { - // NYTT: Naviger til Native Detail Fragment - Bundle bundle = new Bundle(); - bundle.putInt("page_id", item.getId()); - bundle.putString("page_title", item.getTitle()); - - Navigation.findNavController(getView()) - .navigate(R.id.action_handbook_to_detail, bundle); - }); - recyclerView.setAdapter(adapter); - } else { - String msg = "Kunne ikke laste håndboken. Kode: " + response.code(); - if (response.code() == 404) msg += "\n(Fant ikke foreldresiden 'interninstruks-hms')"; - Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show(); - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - if (!isAdded()) return; - progressBar.setVisibility(View.GONE); - Toast.makeText(getContext(), "Nettverksfeil", Toast.LENGTH_SHORT).show(); - } - }); - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookItem.java -============================================================ -// FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookItem.java -package com.kbs.kbsintranett; - -import com.google.gson.annotations.SerializedName; -import java.io.Serializable; - -public class HandbookItem implements Serializable { - @SerializedName("id") - private int id; // NYTT - - @SerializedName("title") - private String title; - - @SerializedName("desc") - private String description; - - @SerializedName("icon_type") - private String iconType; - - @SerializedName("url") - private String url; - - public int getId() { return id; } - public String getTitle() { return title; } - public String getDescription() { return description; } - public String getIconType() { return iconType; } - public String getUrl() { return url; } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookPage.java -============================================================ -// FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookPage.java -package com.kbs.kbsintranett; - -import com.google.gson.annotations.SerializedName; - -public class HandbookPage { - @SerializedName("id") - public int id; - - @SerializedName("title") - public String title; - - @SerializedName("content") - public String content; // Dette er HTML-strengen -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\HomeFragment.java -============================================================ -package com.kbs.kbsintranett; - -import android.Manifest; -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.navigation.Navigation; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.TimeZone; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class HomeFragment extends Fragment { - private ActivityResultLauncher requestPermissionLauncher; - private RecyclerView calendarRecycler; - private RecyclerView newsRecycler; - private ProgressBar mainProgressBar; - private SwipeRefreshLayout swipeRefreshLayout; - - private int activeNetworkCalls = 0; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - requestPermissionLauncher = registerForActivityResult( - new ActivityResultContracts.RequestPermission(), - isGranted -> { - if (calendarRecycler != null) { - refreshData(); - } - } - ); - // GAMMEL METODE FJERNET HERFRA (startNotificationWorker) - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_home, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - mainProgressBar = view.findViewById(R.id.main_loading_spinner); - if (mainProgressBar != null) mainProgressBar.setVisibility(View.VISIBLE); - - swipeRefreshLayout = view.findViewById(R.id.swipe_refresh_home); - swipeRefreshLayout.setOnRefreshListener(this::refreshData); - - View profileBtn = view.findViewById(R.id.btn_profile); - if (profileBtn != null) { - profileBtn.setOnClickListener(v -> Navigation.findNavController(view).navigate(R.id.navigation_profile)); - } - - Button btnCreateEvent = view.findViewById(R.id.btn_create_event); - List writeable = UserManager.getInstance().getWriteableCalendars(); - if (writeable != null && !writeable.isEmpty()) { - btnCreateEvent.setVisibility(View.VISIBLE); - btnCreateEvent.setOnClickListener(v -> { - Navigation.findNavController(view).navigate(R.id.action_home_to_create_event); - }); - } else { - btnCreateEvent.setVisibility(View.GONE); - } - - calendarRecycler = view.findViewById(R.id.recycler_calendar); - calendarRecycler.setLayoutManager(new LinearLayoutManager(getContext())); - calendarRecycler.setAdapter(new CalendarAdapter(new ArrayList<>(), event -> {})); - - TextView viewAllCalendar = view.findViewById(R.id.btn_view_all_calendar); - if (viewAllCalendar != null) { - viewAllCalendar.setOnClickListener(v -> Navigation.findNavController(view).navigate(R.id.action_home_to_calendarFull)); - } - - if (android.os.Build.VERSION.SDK_INT >= 33) { - if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {}).launch(Manifest.permission.POST_NOTIFICATIONS); - } - } - - newsRecycler = view.findViewById(R.id.recycler_news); - newsRecycler.setLayoutManager(new LinearLayoutManager(getContext())); - newsRecycler.setNestedScrollingEnabled(false); - newsRecycler.setAdapter(new NewsAdapter(new ArrayList<>(), item -> {})); - - TextView viewAllNews = view.findViewById(R.id.btn_view_all_news); - if (viewAllNews != null) { - viewAllNews.setOnClickListener(v -> { - Navigation.findNavController(view).navigate(R.id.action_home_to_newsFull); - }); - } - - refreshData(); - } - - @Override - public void onResume() { - super.onResume(); - if (activeNetworkCalls == 0) { - refreshData(); - } - } - - private void refreshData() { - activeNetworkCalls = 2; - - if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED) { - fetchCalendarEvents(calendarRecycler); - } else { - checkLoadingComplete(); - requestPermissionLauncher.launch(Manifest.permission.READ_CALENDAR); - } - - fetchNewsFromWordpress(newsRecycler); - } - - private void checkLoadingComplete() { - activeNetworkCalls--; - if (activeNetworkCalls <= 0) { - activeNetworkCalls = 0; - if (mainProgressBar != null) mainProgressBar.setVisibility(View.GONE); - if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(false); - } - } - - private void fetchCalendarEvents(RecyclerView recyclerView) { - new Thread(() -> { - List deviceEvents = CalendarManager.getDeviceEvents(getContext(), true); - new Handler(Looper.getMainLooper()).post(() -> fetchApiEvents(recyclerView, deviceEvents)); - }).start(); - } - - private void fetchApiEvents(RecyclerView recyclerView, List deviceEvents) { - RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (!isAdded()) return; - - List apiEvents = new ArrayList<>(); - if (response.isSuccessful() && response.body() != null) { - apiEvents = response.body(); - - CacheManager.saveCalendarEvents(getContext(), apiEvents); - - for (CalendarEvent e : apiEvents) { - CalendarManager.formatEventForUI(e); - } - } else { - apiEvents = CacheManager.getCachedCalendarEvents(getContext()); - for (CalendarEvent e : apiEvents) CalendarManager.formatEventForUI(e); - if (!apiEvents.isEmpty()) { - Toast.makeText(getContext(), "Server utilgjengelig. Viser lagret kalender.", Toast.LENGTH_SHORT).show(); - } - } - - updateCalendarUI(recyclerView, apiEvents, deviceEvents); - checkLoadingComplete(); - } - - @Override - public void onFailure(Call> call, Throwable t) { - if (!isAdded()) return; - - List cachedApiEvents = CacheManager.getCachedCalendarEvents(getContext()); - for (CalendarEvent e : cachedApiEvents) CalendarManager.formatEventForUI(e); - - if (!cachedApiEvents.isEmpty()) { - Toast.makeText(getContext(), "Ingen nettverk. Viser lagret kalender.", Toast.LENGTH_SHORT).show(); - } - - updateCalendarUI(recyclerView, cachedApiEvents, deviceEvents); - checkLoadingComplete(); - } - }); - } - - private void updateCalendarUI(RecyclerView recyclerView, List apiEvents, List deviceEvents) { - List merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents); - List upcomingEvents = new ArrayList<>(); - String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date()); - - for (CalendarEvent e : merged) { - if (e.getRawDate() != null && e.getRawDate().compareTo(today) >= 0) { - upcomingEvents.add(e); - } - } - - List topEvents = new ArrayList<>(); - for(int i=0; i { - CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event); - sheet.setOnEventChangeListener(HomeFragment.this::refreshData); - sheet.show(getParentFragmentManager(), "CalendarDetails"); - })); - } - - private void fetchNewsFromWordpress(RecyclerView recyclerView) { - WordPressApiService apiService = RetrofitClient.getApiService(); - apiService.getPosts().enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (getContext() == null) return; - - List postsToShow = new ArrayList<>(); - - if (response.isSuccessful() && response.body() != null) { - postsToShow = response.body(); - CacheManager.saveNewsPosts(getContext(), postsToShow); - } else { - postsToShow = CacheManager.getCachedNewsPosts(getContext()); - if (!postsToShow.isEmpty()) { - Toast.makeText(getContext(), "Server utilgjengelig. Viser lagrede nyheter.", Toast.LENGTH_SHORT).show(); - } - } - - updateNewsUI(recyclerView, postsToShow); - checkLoadingComplete(); - } - - @Override - public void onFailure(Call> call, Throwable t) { - if (getContext() == null) return; - - List cachedPosts = CacheManager.getCachedNewsPosts(getContext()); - if (!cachedPosts.isEmpty()) { - Toast.makeText(getContext(), "Ingen nettverk. Viser lagrede nyheter.", Toast.LENGTH_SHORT).show(); - } - - updateNewsUI(recyclerView, cachedPosts); - checkLoadingComplete(); - } - }); - } - - private void updateNewsUI(RecyclerView recyclerView, List posts) { - SimpleDateFormat rawFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); - rawFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); - SimpleDateFormat targetFormat = new SimpleDateFormat("dd. MMM yyyy", Locale.getDefault()); - targetFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); - - for (WpPost post : posts) { - try { - Date date = rawFormat.parse(post.date); - post.date = targetFormat.format(date); - } catch (Exception e) {} - } - - NewsAdapter adapter = new NewsAdapter(posts, post -> { - Bundle bundle = new Bundle(); - bundle.putSerializable("post_data", post); - Navigation.findNavController(getView()).navigate(R.id.action_home_to_newsDetail, bundle); - }); - recyclerView.setAdapter(adapter); - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\ImageDialogFragment.java -============================================================ -package com.kbs.kbsintranett; - -import android.app.Dialog; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.ProgressBar; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.DialogFragment; -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; - -public class ImageDialogFragment extends DialogFragment { - - private static final String ARG_URL = "image_url"; - - public static ImageDialogFragment newInstance(String imageUrl) { - ImageDialogFragment fragment = new ImageDialogFragment(); - Bundle args = new Bundle(); - args.putString(ARG_URL, imageUrl); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onStart() { - super.onStart(); - // Gjør dialogen fullskjerm - Dialog dialog = getDialog(); - if (dialog != null) { - int width = ViewGroup.LayoutParams.MATCH_PARENT; - int height = ViewGroup.LayoutParams.MATCH_PARENT; - dialog.getWindow().setLayout(width, height); - dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK)); - } - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_image_dialog, container, false); - - ImageView imageView = view.findViewById(R.id.full_screen_image); - ImageButton closeBtn = view.findViewById(R.id.btn_close_image); - ProgressBar progressBar = view.findViewById(R.id.loading_image); - - String url = getArguments() != null ? getArguments().getString(ARG_URL) : null; - - if (url != null) { - Glide.with(this) - .load(url) - .transition(DrawableTransitionOptions.withCrossFade()) - .listener(new com.bumptech.glide.request.RequestListener() { - @Override - public boolean onLoadFailed(@Nullable com.bumptech.glide.load.engine.GlideException e, Object model, com.bumptech.glide.request.target.Target target, boolean isFirstResource) { - progressBar.setVisibility(View.GONE); - return false; - } - - @Override - public boolean onResourceReady(android.graphics.drawable.Drawable resource, Object model, com.bumptech.glide.request.target.Target target, com.bumptech.glide.load.DataSource dataSource, boolean isFirstResource) { - progressBar.setVisibility(View.GONE); - return false; - } - }) - .into(imageView); - } - - closeBtn.setOnClickListener(v -> dismiss()); - // Lukk også hvis man trykker på selve bildet - imageView.setOnClickListener(v -> dismiss()); - - return view; - } - - @NonNull - @Override - public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - Dialog dialog = super.onCreateDialog(savedInstanceState); - dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); - return dialog; - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\InputsAdapter.java -============================================================ -package com.kbs.kbsintranett; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; - -public class InputsAdapter implements JsonDeserializer> { - @Override - public List deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - if (json.isJsonNull()) { - return new ArrayList<>(); - } - // Fikser krasjen: Hvis Gravity Forms sender "" i stedet for [], returner tom liste - if (json.isJsonPrimitive() && json.getAsJsonPrimitive().isString()) { - return new ArrayList<>(); - } - if (json.isJsonArray()) { - List list = new ArrayList<>(); - for (JsonElement e : json.getAsJsonArray()) { - list.add(context.deserialize(e, GravityField.class)); - } - return list; - } - return new ArrayList<>(); - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\InternalLinkMovementMethod.java -============================================================ -// FILSTI: app\src\main\java\com\kbs\kbsintranett\InternalLinkMovementMethod.java -package com.kbs.kbsintranett; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; // <-- Sjekk at denne er med! -import android.text.Spannable; -import android.text.method.LinkMovementMethod; -import android.text.style.URLSpan; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.navigation.Navigation; - -import com.google.gson.JsonObject; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class InternalLinkMovementMethod extends LinkMovementMethod { - private static InternalLinkMovementMethod instance; - private static final String TAG = "InternalLinkMethod"; - - public static InternalLinkMovementMethod getInstance() { - if (instance == null) instance = new InternalLinkMovementMethod(); - return instance; - } - - @Override - public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { - int action = event.getAction(); - - if (action == MotionEvent.ACTION_UP) { - int x = (int) event.getX(); - int y = (int) event.getY(); - - x -= widget.getTotalPaddingLeft(); - y -= widget.getTotalPaddingTop(); - - x += widget.getScrollX(); - y += widget.getScrollY(); - - int line = widget.getLayout().getLineForVertical(y); - int off = widget.getLayout().getOffsetForHorizontal(line, x); - - URLSpan[] link = buffer.getSpans(off, off, URLSpan.class); - if (link.length != 0) { - String url = link[0].getURL(); - handleLink(widget.getContext(), url, widget); - return true; - } - } - return super.onTouchEvent(widget, buffer, event); - } - - private void handleLink(Context context, String url, View view) { - Log.d(TAG, "Link clicked: " + url); - - // 1. Sjekk om det er en intern lenke - if (url.contains("intranet.kbs.no") || url.startsWith("/")) { - - // a) Prøv å finne ID hvis den finnes i URLen (?p=123) - int pageId = extractIdFromUrl(url); - - if (pageId > 0) { - navigateToInternalPage(view, pageId, "Laster..."); - } else { - // b) Det er en "pen" URL. Vi må spørre APIet hva IDen er. - // Vi bruker Toast for å gi feedback om at noe skjer - Toast.makeText(context, "Åpner side...", Toast.LENGTH_SHORT).show(); - - RetrofitClient.getApiService().lookupPageId(url).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful() && response.body() != null) { - int id = response.body().get("id").getAsInt(); - if (id > 0) { - // Suksess! Naviger internt - navigateToInternalPage(view, id, "Laster..."); - } else { - // Fant ikke ID, åpne i WebView som fallback - openInWebView(context, url); - } - } else { - openInWebView(context, url); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - openInWebView(context, url); - } - }); - } - } else { - // Ekstern lenke - åpne i nettleser - Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - context.startActivity(browserIntent); - } - } - - private int extractIdFromUrl(String url) { - Pattern p = Pattern.compile("[?&](p|page_id|post)=([0-9]+)"); - Matcher m = p.matcher(url); - if (m.find()) { - try { - return Integer.parseInt(m.group(2)); - } catch (NumberFormatException e) { - return 0; - } - } - return 0; - } - - private void navigateToInternalPage(View view, int pageId, String title) { - try { - Bundle bundle = new Bundle(); - bundle.putInt("page_id", pageId); - bundle.putString("page_title", title); - Navigation.findNavController(view).navigate(R.id.action_handbook_to_detail, bundle); - } catch (Exception e) { - Log.e(TAG, "Kunne ikke navigere", e); - } - } - - private void openInWebView(Context context, String url) { - Intent intent = new Intent(context, WebViewActivity.class); - intent.putExtra(WebViewActivity.EXTRA_URL, url); - intent.putExtra(WebViewActivity.EXTRA_TITLE, "KBS Intranett"); - context.startActivity(intent); - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\KbsApplication.java -============================================================ -package com.kbs.kbsintranett; - -import android.app.Application; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.os.Build; - -public class KbsApplication extends Application { - - public static final String CHANNEL_ID = "kbs_calendar_channel"; - - @Override - public void onCreate() { - super.onCreate(); - createNotificationChannel(); - } - - private void createNotificationChannel() { - // Vi oppretter kanalen her ved oppstart, så den er klar uansett om - // det er MainActivity eller en bakgrunnsjobb som trenger den. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel channel = new NotificationChannel( - CHANNEL_ID, - "KBS Kalendervarsler", - NotificationManager.IMPORTANCE_HIGH - ); - channel.setDescription("Varsler for kalenderhendelser"); - - NotificationManager manager = getSystemService(NotificationManager.class); - if (manager != null) { - manager.createNotificationChannel(channel); - } - } - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\LoginFragment.java -============================================================ -package com.kbs.kbsintranett; - -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import android.widget.Toast; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.navigation.NavController; -import androidx.navigation.Navigation; - -import com.google.android.gms.auth.api.signin.GoogleSignIn; -import com.google.android.gms.auth.api.signin.GoogleSignInAccount; -import com.google.android.gms.auth.api.signin.GoogleSignInClient; -import com.google.android.gms.auth.api.signin.GoogleSignInOptions; -import com.google.android.gms.common.SignInButton; -import com.google.android.gms.common.api.ApiException; -import com.google.android.gms.tasks.Task; - -public class LoginFragment extends Fragment { - - private static final String TAG = "LoginFragment"; - private GoogleSignInClient mGoogleSignInClient; - private TextView statusText; - private SignInButton signInButton; - - // Håndterer resultatet fra Google-vinduet - private final ActivityResultLauncher signInLauncher = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - Task task = GoogleSignIn.getSignedInAccountFromIntent(result.getData()); - handleGoogleResult(task); - } - ); - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_login, container, false); - statusText = view.findViewById(R.id.status_text); - signInButton = view.findViewById(R.id.sign_in_button); - signInButton.setSize(SignInButton.SIZE_WIDE); - - // Hent ID fra MainActivity - String clientId = MainActivity.GOOGLE_WEB_CLIENT_ID; - GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) - .requestIdToken(clientId) - .requestEmail() - .build(); - mGoogleSignInClient = GoogleSignIn.getClient(requireActivity(), gso); - - signInButton.setOnClickListener(v -> { - statusText.setText("Starter Google innlogging..."); - Intent signInIntent = mGoogleSignInClient.getSignInIntent(); - signInLauncher.launch(signInIntent); - }); - return view; - } - - private void handleGoogleResult(Task completedTask) { - try { - GoogleSignInAccount account = completedTask.getResult(ApiException.class); - // 1. Google er OK. Nå logger vi inn på WordPress. - statusText.setText("Google OK. Kobler til KBS Intranett..."); - signInButton.setEnabled(false); // Hindre dobbeltklikk - - String photoUrl = (account.getPhotoUrl() != null) ? account.getPhotoUrl().toString() : null; - - AuthRepository.loginToWordPress( - account.getIdToken(), - account.getDisplayName(), - account.getEmail(), - photoUrl, - new AuthRepository.AuthCallback() { - @Override - public void onSuccess(String role) { - // 2. Alt er OK! Naviger til Hjem. - if (isAdded()) { - statusText.setText("Innlogging OK!"); - NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); - navController.navigate(R.id.action_login_to_home); - } - } - - @Override - public void onError(String message) { - if (isAdded()) { - statusText.setText(message); - signInButton.setEnabled(true); - } - } - } - ); - } catch (ApiException e) { - // --- KORRIGERT FEILMELDING --- - Log.w(TAG, "signInResult:failed code=" + e.getStatusCode()); - String message; - if (e.getStatusCode() == 12500) { - message = "Konto ikke funnet, eller konto uten rettigheter."; - } else { - message = "Google-feil: " + e.getStatusCode(); - } - statusText.setText(message); - // --- SLUTT PÅ KORRIGERING --- - } - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\LoginRequest.java -============================================================ -package com.kbs.kbsintranett; - -public class LoginRequest { - public String token; - - public LoginRequest(String token) { - this.token = token; - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\LoginResponse.java -============================================================ -// FILSTI: app\src\main\java\com\kbs\kbsintranett\LoginResponse.java -package com.kbs.kbsintranett; - -import com.google.gson.annotations.SerializedName; -import java.util.List; - -public class LoginResponse { - public boolean success; - @SerializedName("full_cookie") - public String fullCookie; - - public String role; - - @SerializedName("user_id") - public int userId; - - @SerializedName("first_name") - public String firstName; - - @SerializedName("last_name") - public String lastName; - - @SerializedName("stilling") - public String stilling; - - @SerializedName("mobiltelefon") - public String mobiltelefon; - - // NYTT FELT: Liste over kalendere brukeren kan skrive til - @SerializedName("writeable_calendars") - public List writeableCalendars; - - public String message; -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\MainActivity.java -============================================================ -package com.kbs.kbsintranett; - -import android.app.AlarmManager; -import android.app.AlertDialog; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.provider.Settings; -import android.util.Log; -import android.view.View; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.ContextCompat; -import androidx.navigation.NavController; -import androidx.navigation.fragment.NavHostFragment; -import androidx.navigation.ui.NavigationUI; - -import com.google.android.gms.auth.api.signin.GoogleSignIn; -import com.google.android.gms.auth.api.signin.GoogleSignInAccount; -import com.google.android.gms.auth.api.signin.GoogleSignInClient; -import com.google.android.gms.auth.api.signin.GoogleSignInOptions; -import com.google.android.material.bottomnavigation.BottomNavigationView; - -public class MainActivity extends AppCompatActivity { - - public static final String GOOGLE_WEB_CLIENT_ID = "[SENSURERT].apps.googleusercontent.com"; // Bytt med din egen hvis denne er feil - - private static final String TAG = "MainActivity"; - private NavController navController; - private BottomNavigationView bottomNav; - - private ActivityResultLauncher requestPermissionLauncher; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - // --- 1. SETUP UI & NAVIGASJON --- - bottomNav = findViewById(R.id.bottom_nav_view); - - NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager() - .findFragmentById(R.id.nav_host_fragment); - - if (navHostFragment != null) { - navController = navHostFragment.getNavController(); - if (bottomNav != null) { - NavigationUI.setupWithNavController(bottomNav, navController); - - // Håndter "Reselection" (Klikk på fanen man allerede er i) - bottomNav.setOnItemReselectedListener(item -> { - navController.popBackStack(item.getItemId(), false); - }); - } - - // Skjul meny på login-skjerm - navController.addOnDestinationChangedListener((controller, destination, arguments) -> { - if (bottomNav == null) return; - - if (destination.getId() == R.id.navigation_login) { - bottomNav.setVisibility(View.GONE); - } else { - bottomNav.setVisibility(View.VISIBLE); - } - }); - } - - // --- 2. VARSLINGSOPPSETT --- - createNotificationChannel(); - - requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { - if (isGranted) { - Log.d(TAG, "Varslingstillatelse gitt!"); - } else { - Log.e(TAG, "Varslingstillatelse avslått."); - } - }); - - checkNotificationPermission(); - checkExactAlarmPermission(); - - // GAMMEL METODE FJERNET HERFRA (NotificationWorker) - - // --- 3. AUTENTISERING --- - checkLoginState(); - } - - private void checkLoginState() { - GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this); - if (account == null) { - navigateToLogin(); - } else { - refreshGoogleToken(); - } - } - - private void refreshGoogleToken() { - GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) - .requestIdToken(GOOGLE_WEB_CLIENT_ID) - .requestEmail() - .build(); - GoogleSignInClient client = GoogleSignIn.getClient(this, gso); - - client.silentSignIn() - .addOnSuccessListener(account -> { - String photoUrl = (account.getPhotoUrl() != null) ? account.getPhotoUrl().toString() : null; - - AuthRepository.loginToWordPress( - account.getIdToken(), - account.getDisplayName(), - account.getEmail(), - photoUrl, - new AuthRepository.AuthCallback() { - @Override - public void onSuccess(String role) { - Log.d(TAG, "Silent login fullført. Rolle: " + role); - if (navController != null && navController.getCurrentDestination() != null && - navController.getCurrentDestination().getId() == R.id.navigation_login) { - navController.navigate(R.id.action_login_to_home); - } - } - - @Override - public void onError(String message) { - Log.e(TAG, "Silent login feilet mot WP: " + message); - navigateToLogin(); - } - } - ); - }) - .addOnFailureListener(e -> { - Log.e(TAG, "Silent Sign-In feilet mot Google", e); - navigateToLogin(); - }); - } - - private void navigateToLogin() { - if (navController != null) { - if (navController.getCurrentDestination() != null && - navController.getCurrentDestination().getId() != R.id.navigation_login) { - navController.navigate(R.id.navigation_login); - } - } - } - - private void createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - CharSequence name = "KBS Kalendervarsler"; - String description = "Varsler for kalenderhendelser"; - int importance = NotificationManager.IMPORTANCE_HIGH; - NotificationChannel channel = new NotificationChannel("kbs_calendar_channel", name, importance); - channel.setDescription(description); - NotificationManager notificationManager = getSystemService(NotificationManager.class); - if (notificationManager != null) { - notificationManager.createNotificationChannel(channel); - } - } - } - - private void checkNotificationPermission() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - requestPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS); - } - } - } - - private void checkExactAlarmPermission() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - if (alarmManager != null && !alarmManager.canScheduleExactAlarms()) { - new AlertDialog.Builder(this) - .setTitle("Varslingstillatelse kreves") - .setMessage("For at kalenderen skal kunne varsle deg nøyaktig når et møte starter, må du gi appen tilgang til å sette alarmer.") - .setPositiveButton("Gå til Innstillinger", (dialog, which) -> { - Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM); - intent.setData(Uri.parse("package:" + getPackageName())); - startActivity(intent); - }) - .setNegativeButton("Senere", null) - .show(); - } - } - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\MyFirebaseMessagingService.java -============================================================ -package com.kbs.kbsintranett; - -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.os.Build; -import android.util.Log; -import androidx.annotation.NonNull; -import androidx.core.app.ActivityCompat; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; -import androidx.core.content.ContextCompat; -import com.google.firebase.messaging.FirebaseMessagingService; -import com.google.firebase.messaging.RemoteMessage; -import java.util.List; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class MyFirebaseMessagingService extends FirebaseMessagingService { - - private static final String TAG = "FCMService"; - private static final String CHANNEL_ID = "kbs_calendar_channel"; - - @Override - public void onNewToken(@NonNull String token) { - super.onNewToken(token); - Log.d(TAG, "Ny FCM Token: " + token); - AuthRepository.updateDeviceToken(token); - } - - @Override - public void onMessageReceived(@NonNull RemoteMessage remoteMessage) { - super.onMessageReceived(remoteMessage); - - Log.d(TAG, "Melding mottatt fra: " + remoteMessage.getFrom()); - - // 1. Sjekk data payload (Bakgrunnsoppdatering) - if (remoteMessage.getData().size() > 0) { - String forceRefresh = remoteMessage.getData().get("force_refresh"); - - if ("true".equalsIgnoreCase(forceRefresh)) { - Log.d(TAG, "Mottok 'force_refresh' - oppdaterer kalender og alarmer..."); - updateCalendarAndAlarms(); - } - - // Hvis meldingen også har egne titler i data-feltet (valgfritt) - String title = remoteMessage.getData().get("title"); - String body = remoteMessage.getData().get("body"); - if (title != null && body != null) { - showNotification(title, body); - } - } - - // 2. Sjekk notification payload (Vises automatisk når app er i bakgrunn, men vi håndterer den her for forgrunn) - if (remoteMessage.getNotification() != null) { - Log.d(TAG, "Melding varsel body: " + remoteMessage.getNotification().getBody()); - showNotification( - remoteMessage.getNotification().getTitle(), - remoteMessage.getNotification().getBody() - ); - } - } - - private void updateCalendarAndAlarms() { - // Vi bruker Retrofit for å hente kalenderen på nytt - RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (response.isSuccessful() && response.body() != null) { - // Lagre til cache først (god praksis) - CacheManager.saveCalendarEvents(getApplicationContext(), response.body()); - - // Oppdater alarmer lokalt - AlarmScheduler.scheduleAlarmsForEvents(getApplicationContext(), response.body()); - Log.d(TAG, "Kalender og alarmer oppdatert via Push."); - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - Log.e(TAG, "Feil ved push-oppdatering av kalender", t); - } - }); - } - - private void showNotification(String title, String message) { - createNotificationChannel(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - return; - } - } - - Intent tapIntent = new Intent(this, MainActivity.class); - tapIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - PendingIntent pendingIntent = PendingIntent.getActivity( - this, - 0, - tapIntent, - PendingIntent.FLAG_IMMUTABLE - ); - - NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_stat_kbs) - .setColor(ContextCompat.getColor(this, R.color.kbs_logo_blue)) - .setContentTitle(title) - .setContentText(message) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setContentIntent(pendingIntent) - .setAutoCancel(true); - - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); - notificationManager.notify((int) System.currentTimeMillis(), builder.build()); - } - - private void createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel channel = new NotificationChannel( - CHANNEL_ID, - "KBS Kalendervarsler", - NotificationManager.IMPORTANCE_HIGH - ); - channel.setDescription("Varsler fra KBS Intranett"); - NotificationManager manager = getSystemService(NotificationManager.class); - if (manager != null) { - manager.createNotificationChannel(channel); - } - } - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\NewsAdapter.java -============================================================ -package com.kbs.kbsintranett; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; -import java.util.List; - -public class NewsAdapter extends RecyclerView.Adapter { - - private List posts; - private OnItemClickListener listener; // NYTT - - // Interface for klikk - public interface OnItemClickListener { - void onItemClick(WpPost post); - } - - // Oppdatert konstruktør - public NewsAdapter(List posts, OnItemClickListener listener) { - this.posts = posts; - this.listener = listener; - } - - // Overload for bakoverkompatibilitet (hvis du ikke sender listener) - public NewsAdapter(List posts) { - this.posts = posts; - this.listener = null; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - WpPost post = posts.get(position); - - holder.title.setText(post.getTitleStr()); - holder.excerpt.setText(post.getExcerptStr()); - holder.date.setText(post.date); - - String cat = post.getCategoryName(); - if (!cat.isEmpty()) { - holder.category.setText(cat); - holder.category.setVisibility(View.VISIBLE); - } else { - holder.category.setVisibility(View.GONE); - } - - String imageUrl = post.getFeaturedImageUrl(); - if (imageUrl != null && !imageUrl.isEmpty()) { - holder.image.setVisibility(View.VISIBLE); - Glide.with(holder.itemView.getContext()) - .load(imageUrl) - .transition(DrawableTransitionOptions.withCrossFade()) - .centerCrop() - .into(holder.image); - } else { - holder.image.setVisibility(View.GONE); - } - - // NYTT: Håndter klikk - holder.itemView.setOnClickListener(v -> { - if (listener != null) { - listener.onItemClick(post); - } - }); - } - - @Override - public int getItemCount() { - return posts.size(); - } - - public static class ViewHolder extends RecyclerView.ViewHolder { - TextView title, excerpt, date, category; - ImageView image; - - public ViewHolder(View view) { - super(view); - title = view.findViewById(R.id.news_title); - excerpt = view.findViewById(R.id.news_excerpt); - date = view.findViewById(R.id.news_date); - category = view.findViewById(R.id.news_category); - image = view.findViewById(R.id.news_image); - } - } - - // NYTT: Metode for å oppdatere listen etter filtrering - public void updateList(List newPosts) { - this.posts = newPosts; - notifyDataSetChanged(); - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\NewsDetailFragment.java -============================================================ -package com.kbs.kbsintranett; - -import android.content.Context; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.webkit.JavascriptInterface; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.ImageView; -import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.Toolbar; -import androidx.fragment.app.Fragment; -import androidx.navigation.Navigation; -import com.bumptech.glide.Glide; - -public class NewsDetailFragment extends Fragment { - - // CSS Styling (Samme stil som håndboken, pluss bildehåndtering) - private static final String CSS_STYLE = - ""; - - // JavaScript for å fange opp bildeklikk - private static final String JS_SCRIPT = - ""; - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_news_detail, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - if (getArguments() != null) { - WpPost post = (WpPost) getArguments().getSerializable("post_data"); - if (post != null) { - setupViews(view, post); - } - } - } - - private void setupViews(View view, WpPost post) { - Toolbar toolbar = view.findViewById(R.id.detail_toolbar); - toolbar.setNavigationOnClickListener(v -> Navigation.findNavController(view).navigateUp()); - - ImageView image = view.findViewById(R.id.detail_image); - TextView title = view.findViewById(R.id.detail_title); - TextView category = view.findViewById(R.id.detail_category); - TextView date = view.findViewById(R.id.detail_date); - TextView author = view.findViewById(R.id.detail_author); - WebView webView = view.findViewById(R.id.detail_webview); - - // Header bilde - String imgUrl = post.getFeaturedImageUrl(); - if (imgUrl != null) { - Glide.with(this).load(imgUrl).centerCrop().into(image); - } else { - image.setBackgroundColor(getResources().getColor(android.R.color.darker_gray)); - } - - title.setText(post.getTitleStr()); - category.setText(post.getCategoryName()); - date.setText("Publisert: " + post.date); - author.setText("Av: " + post.getAuthorName()); - - // Konfigurer WebView - WebSettings settings = webView.getSettings(); - settings.setJavaScriptEnabled(true); - settings.setDomStorageEnabled(true); - - // Legg til Interface for å snakke med Java - webView.addJavascriptInterface(new WebAppInterface(getContext()), "Android"); - - // Håndter linker internt (som i Håndboken) - webView.setWebViewClient(new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - // Bruk samme link-logikk som i Håndboken hvis nødvendig, - // men her lar vi linker åpnes i nettleser for enkelhets skyld foreløpig - return false; - } - }); - - // Bygg HTML - String rawContent = post.getContentStr(); - - // Vask innholdet litt hvis nødvendig (f.eks fjerne inline styles som ødelegger) - // Her legger vi bare til vår CSS og JS - String htmlData = "" + - "" + - CSS_STYLE + - JS_SCRIPT + - "" + - rawContent + - ""; - - webView.loadDataWithBaseURL("https://intranet.kbs.no", htmlData, "text/html", "UTF-8", null); - } - - // Bridge-klasse for å ta imot klikk fra JavaScript - public class WebAppInterface { - Context mContext; - - WebAppInterface(Context c) { - mContext = c; - } - - @JavascriptInterface - public void showImage(String url) { - // Må kjøres på UI-tråden - if (getActivity() != null) { - getActivity().runOnUiThread(() -> { - ImageDialogFragment dialog = ImageDialogFragment.newInstance(url); - dialog.show(getParentFragmentManager(), "image_lightbox"); - }); - } - } - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\NewsFullFragment.java -============================================================ -package com.kbs.kbsintranett; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.Toast; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.navigation.Navigation; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.TimeZone; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class NewsFullFragment extends Fragment { - - private RecyclerView recyclerViewNews; - private RecyclerView recyclerViewCategories; - private ProgressBar progressBar; - private NewsAdapter newsAdapter; - private List allPosts = new ArrayList<>(); // Holder på ALLE postene - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_news_full, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - recyclerViewNews = view.findViewById(R.id.recycler_news_full); - recyclerViewCategories = view.findViewById(R.id.recycler_categories); - progressBar = view.findViewById(R.id.loading_news_full); - ImageView backBtn = view.findViewById(R.id.btn_back_news); - - // Setup Nyhetsliste - recyclerViewNews.setLayoutManager(new LinearLayoutManager(getContext())); - - // Setup Kategorier (Horisontal) - recyclerViewCategories.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false)); - setupCategories(); - - backBtn.setOnClickListener(v -> Navigation.findNavController(view).navigateUp()); - - fetchAllNews(); - } - - private void setupCategories() { - // Listen over kategorier du ønsket - List categories = Arrays.asList( - "Alle", // Standard vis alt - "Avtaler og invitasjoner", "BHT", "Bilhold", "Cordel", - "Ferieavvikling", "Fest og moro", "Generell drift", - "HMS", "IT og sikkerhet", "Miljøfyrtårn", "Møtereferat", "SMX" - ); - - CategoryAdapter catAdapter = new CategoryAdapter(categories, selectedCategory -> { - filterNews(selectedCategory); - }); - recyclerViewCategories.setAdapter(catAdapter); - } - - private void fetchAllNews() { - progressBar.setVisibility(View.VISIBLE); - // Hent 50 siste (bør holde for en "Siste nytt" liste, ellers må vi paginere) - RetrofitClient.getApiService().getAllPosts().enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (!isAdded()) return; - progressBar.setVisibility(View.GONE); - - if (response.isSuccessful() && response.body() != null) { - allPosts = response.body(); - formatDates(allPosts); - - // Vis alle i starten - newsAdapter = new NewsAdapter(new ArrayList<>(allPosts), post -> { - Bundle bundle = new Bundle(); - bundle.putSerializable("post_data", post); - Navigation.findNavController(getView()).navigate(R.id.action_newsFull_to_newsDetail, bundle); - }); - recyclerViewNews.setAdapter(newsAdapter); - } else { - Toast.makeText(getContext(), "Klarte ikke laste nyheter", Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - if (!isAdded()) return; - progressBar.setVisibility(View.GONE); - Toast.makeText(getContext(), "Nettverksfeil", Toast.LENGTH_SHORT).show(); - } - }); - } - - private void filterNews(String category) { - if (newsAdapter == null) return; - - List filteredList = new ArrayList<>(); - - if (category.equals("Alle")) { - filteredList.addAll(allPosts); - } else { - for (WpPost post : allPosts) { - // Vi sjekker om kategorinavnet matcher - if (post.getCategoryName().equals(category)) { - filteredList.add(post); - } - } - } - - // Oppdater adapteren med den filtrerte listen - newsAdapter.updateList(filteredList); - } - - private void formatDates(List posts) { - SimpleDateFormat rawFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); - rawFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); - SimpleDateFormat targetFormat = new SimpleDateFormat("dd. MMM yyyy", Locale.getDefault()); - targetFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); - - for (WpPost post : posts) { - try { - Date date = rawFormat.parse(post.date); - post.date = targetFormat.format(date); - } catch (Exception e) {} - } - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\NewsItem.java -============================================================ -package com.kbs.kbsintranett; - -public class NewsItem { - private String title; - private String excerpt; // Kort tekst/ingress - private String author; - - public NewsItem(String title, String excerpt, String author) { - this.title = title; - this.excerpt = excerpt; - this.author = author; - } - - public String getTitle() { return title; } - public String getExcerpt() { return excerpt; } - public String getAuthor() { return author; } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\ProfileFragment.java -============================================================ -package com.kbs.kbsintranett; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.navigation.NavController; -import androidx.navigation.Navigation; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.RequestOptions; -import com.google.android.gms.auth.api.signin.GoogleSignIn; -import com.google.android.gms.auth.api.signin.GoogleSignInClient; -import com.google.android.gms.auth.api.signin.GoogleSignInOptions; - -public class ProfileFragment extends Fragment { - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_profile, container, false); - - ImageView closeBtn = view.findViewById(R.id.btn_close_profile); - ImageView profileImage = view.findViewById(R.id.profile_image); - TextView nameText = view.findViewById(R.id.profile_name); - TextView emailText = view.findViewById(R.id.profile_email); - TextView roleText = view.findViewById(R.id.profile_role); - Button logoutBtn = view.findViewById(R.id.btn_logout); - Button updateInfoBtn = view.findViewById(R.id.btn_update_info); - TextView versionText = view.findViewById(R.id.tv_version_info); // NYTT - - UserManager user = UserManager.getInstance(); - nameText.setText(user.getUserDisplayName()); - emailText.setText(user.getUserEmail()); - roleText.setText("Rolle: " + user.getUserRole()); - - // NYTT: Sett versjonstekst - String versionInfo = "Versjon " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")"; - versionText.setText(versionInfo); - - if (user.getPhotoUrl() != null) { - Glide.with(this) - .load(user.getPhotoUrl()) - .apply(RequestOptions.circleCropTransform()) - .into(profileImage); - } - - closeBtn.setOnClickListener(v -> { - Navigation.findNavController(view).navigateUp(); - }); - - updateInfoBtn.setOnClickListener(v -> { - Bundle bundle = new Bundle(); - bundle.putInt("formId", 1); - Navigation.findNavController(view).navigate(R.id.action_profile_to_form, bundle); - }); - - logoutBtn.setOnClickListener(v -> performLogout()); - - return view; - } - - private void performLogout() { - GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) - .requestIdToken(MainActivity.GOOGLE_WEB_CLIENT_ID) - .requestEmail() - .build(); - GoogleSignInClient client = GoogleSignIn.getClient(requireActivity(), gso); - - client.signOut().addOnCompleteListener(task -> { - UserManager.getInstance().logout(); - RetrofitClient.clearClient(); - NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); - navController.navigate(R.id.action_profile_to_login); - Toast.makeText(getContext(), "Du er nå logget ut", Toast.LENGTH_SHORT).show(); - }); - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\RegisterDeviceRequest.java -============================================================ -package com.kbs.kbsintranett; - -import com.google.gson.annotations.SerializedName; - -public class RegisterDeviceRequest { - @SerializedName("fcm_token") - public String fcmToken; - - @SerializedName("platform") - public String platform; - - public RegisterDeviceRequest(String fcmToken) { - this.fcmToken = fcmToken; - this.platform = "android"; - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\RetrofitClient.java -============================================================ -package com.kbs.kbsintranett; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; - -import java.io.IOException; -import java.util.List; - -import okhttp3.Interceptor; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.logging.HttpLoggingInterceptor; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - -public class RetrofitClient { - private static final String BASE_URL = "https://intranet.kbs.no/"; - private static Retrofit retrofit = null; - public static WordPressApiService getApiService() { - if (retrofit == null) { - - HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); - if (BuildConfig.DEBUG) { - // I debug-modus logger vi det mest nødvendige - logging.setLevel(HttpLoggingInterceptor.Level.BASIC); - } else { - // I release er vi stille for ytelse og sikkerhet - logging.setLevel(HttpLoggingInterceptor.Level.NONE); - } - - OkHttpClient client = new OkHttpClient.Builder() - .addInterceptor(logging) - .addInterceptor(new Interceptor() { - @Override - public Response intercept(Chain chain) throws IOException { - Request originalRequest = chain.request(); - Request.Builder builder = originalRequest.newBuilder(); - - String dynamicCookie = UserManager.getInstance().getCookie(); - if (dynamicCookie != null && !dynamicCookie.isEmpty()) { - builder.header("Cookie", dynamicCookie); - } - - return chain.proceed(builder.build()); - } - }) - .build(); - - Gson gson = new GsonBuilder() - .registerTypeAdapter(new TypeToken>(){}.getType(), new ChoicesAdapter()) - .setLenient() - .create(); - - retrofit = new Retrofit.Builder() - .baseUrl(BASE_URL) - .client(client) - .addConverterFactory(GsonConverterFactory.create(gson)) - .build(); - } - return retrofit.create(WordPressApiService.class); - } - - public static void clearClient() { - retrofit = null; - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\User.java -============================================================ -package com.kbs.kbsintranett; - -import com.google.gson.annotations.SerializedName; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -public class User implements Serializable { - @SerializedName("id") - private int id; - - @SerializedName("name") - private String name; - - @SerializedName("email") - private String email; - - @SerializedName("roles") - private List roles; // NYTT: Liste over roller - - // For bruk i UI (sjekkbokser) - private boolean isSelected = false; - - public int getId() { return id; } - public String getName() { return name; } - public String getEmail() { return email; } - public List getRoles() { return roles != null ? roles : new ArrayList<>(); } - - public boolean isSelected() { return isSelected; } - public void setSelected(boolean selected) { isSelected = selected; } - - @Override - public String toString() { - return name; - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\UserManager.java -============================================================ -// FILSTI: app\src\main\java\com\kbs\kbsintranett\UserManager.java -package com.kbs.kbsintranett; - -import androidx.annotation.Nullable; -import java.util.ArrayList; -import java.util.List; - -/** - * UserManager fungerer som en global sesjon for appen. - * Den holder på informasjon om innlogget bruker, rettigheter og autentiserings-cookie. - */ -public class UserManager { - private static UserManager instance; - - // Google Data - private String userDisplayName; - private String userEmail; - private String googleIdToken; - private String photoUrl; - - // WordPress Data - private int userId; - private String userRole; - private String currentCookie; - - // Extended Info - private String firstName; - private String lastName; - private String stilling; - private String mobiltelefon; - - // FCM Token (Push) - private String fcmToken; - - // NYTT: - private List writeableCalendars = new ArrayList<>(); - - private UserManager() {} - - public static synchronized UserManager getInstance() { - if (instance == null) { - instance = new UserManager(); - } - return instance; - } - - public void setUserData(String name, String email, String token, @Nullable String photoUrl) { - this.userDisplayName = name; - this.userEmail = email; - this.googleIdToken = token; - this.photoUrl = photoUrl; - } - - public void setExtendedUserInfo(String firstName, String lastName, String stilling, String mobiltelefon) { - this.firstName = firstName; - this.lastName = lastName; - this.stilling = stilling; - this.mobiltelefon = mobiltelefon; - } - - public void setWriteableCalendars(List calendars) { - this.writeableCalendars = calendars != null ? calendars : new ArrayList<>(); - } - - public List getWriteableCalendars() { - return writeableCalendars; - } - - public void setCookie(String cookie) { this.currentCookie = cookie; } - public void setUserRole(String role) { this.userRole = role; } - public void setUserId(int id) { this.userId = id; } - - public String getUserDisplayName() { return userDisplayName != null ? userDisplayName : ""; } - public String getUserEmail() { return userEmail != null ? userEmail : ""; } - public String getGoogleIdToken() { return googleIdToken; } - public String getPhotoUrl() { return photoUrl; } - public String getCookie() { return currentCookie; } - public String getUserRole() { return userRole != null ? userRole : "subscriber"; } - public int getUserId() { return userId; } - public String getFirstName() { return firstName != null ? firstName : ""; } - public String getLastName() { return lastName != null ? lastName : ""; } - public String getStilling() { return stilling != null ? stilling : ""; } - public String getMobiltelefon() { return mobiltelefon != null ? mobiltelefon : ""; } - - public void setFcmToken(String token) { this.fcmToken = token; } - public String getFcmToken() { return fcmToken; } - - public boolean isLoggedIn() { return userEmail != null && !userEmail.isEmpty(); } - public boolean isAdmin() { return "administrator".equalsIgnoreCase(userRole); } - public boolean isEditorOrAbove() { - return userRole != null && (userRole.equalsIgnoreCase("administrator") || userRole.equalsIgnoreCase("editor")); - } - - public void logout() { - userDisplayName = null; - userEmail = null; - googleIdToken = null; - photoUrl = null; - userRole = null; - currentCookie = null; - userId = 0; - firstName = null; - lastName = null; - stilling = null; - mobiltelefon = null; - writeableCalendars.clear(); - // Vi sletter ikke fcmToken ved logout, da enheten fortsatt er den samme - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\WebViewActivity.java -============================================================ -package com.kbs.kbsintranett; - -import android.annotation.SuppressLint; -import android.os.Bundle; -import android.webkit.CookieManager; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; - -public class WebViewActivity extends AppCompatActivity { - - public static final String EXTRA_URL = "extra_url"; - public static final String EXTRA_TITLE = "extra_title"; - - @SuppressLint("SetJavaScriptEnabled") - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_webview); - - String url = getIntent().getStringExtra(EXTRA_URL); - String title = getIntent().getStringExtra(EXTRA_TITLE); - - Toolbar toolbar = findViewById(R.id.toolbar); - toolbar.setTitle(title != null ? title : "Håndbok"); - toolbar.setNavigationIcon(android.R.drawable.ic_menu_revert); - toolbar.setNavigationOnClickListener(v -> finish()); - - WebView webView = findViewById(R.id.webview); - WebSettings settings = webView.getSettings(); - settings.setJavaScriptEnabled(true); - settings.setDomStorageEnabled(true); - settings.setBuiltInZoomControls(true); - settings.setDisplayZoomControls(false); - - // --- MAGIEN SKJER HER: INJISER COOKIE --- - String cookie = UserManager.getInstance().getCookie(); - if (cookie != null && !cookie.isEmpty()) { - CookieManager cookieManager = CookieManager.getInstance(); - cookieManager.setAcceptCookie(true); - // Vi antar at domenet er intranet.kbs.no basert på APIet - cookieManager.setCookie("https://intranet.kbs.no", cookie); - } - // ---------------------------------------- - - webView.setWebViewClient(new WebViewClient()); // Åpne linker i samme WebView - - if (url != null) { - webView.loadUrl(url); - } - } -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\WordPressApiService.java -============================================================ -package com.kbs.kbsintranett; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import java.util.List; -import java.util.Map; -import okhttp3.MultipartBody; -import okhttp3.RequestBody; -import retrofit2.Call; -import retrofit2.http.Body; -import retrofit2.http.GET; -import retrofit2.http.POST; -import retrofit2.http.Path; -import retrofit2.http.Multipart; -import retrofit2.http.Part; -import retrofit2.http.PartMap; -import retrofit2.http.Query; - -public interface WordPressApiService { - @GET("wp-json/wp/v2/posts?per_page=10&_embed") - Call> getPosts(); - @GET("wp-json/kbs/v1/forms/{id}") - Call getForm(@Path("id") int formId); - - @POST("wp-json/gf/v2/forms/{id}/submissions") - Call submitForm(@Path("id") int formId, @Body FormSubmission submission); - - @POST("wp-json/kbs/v1/login") - Call googleLogin(@Body LoginRequest request); - - @GET("wp-json/kbs/v1/forms") - Call> getFormsList(); - - @Multipart - @POST("wp-json/gf/v2/forms/{id}/submissions") - Call submitMultipartForm( - @Path("id") int formId, - @PartMap Map textFields, - @Part List files - ); - - @GET("wp-json/kbs/v1/calendar/events") - Call> getCalendarEvents(); - - @POST("wp-json/kbs/v1/calendar/create") - Call createCalendarEvent(@Body CreateEventRequest request); - - // NYTT ENDEPUNKT - @GET("wp-json/kbs/v1/users") - Call> getUsersList(); - - @GET("wp-json/gf/v2/entries") - Call getEntries( - @Query("form_ids") int formId, - @Query("search") String searchJson, - @Query("paging[page_size]") int pageSize - ); - - @GET("wp-json/gf/v2/entries/{entry_id}") - Call getSingleEntry(@Path("entry_id") String entryId); - - @GET("wp-json/wp/v2/posts?per_page=50&_embed") - Call> getAllPosts(); - - @GET("wp-json/kbs/v1/handbook") - Call> getHandbookItems(); - - @GET("wp-json/kbs/v1/handbook/{id}") - Call getHandbookPage(@Path("id") int id); - - @GET("wp-json/kbs/v1/lookup-id") - Call lookupPageId(@Query("url") String url); - - @POST("wp-json/kbs/v1/calendar/update") - Call updateCalendarEvent(@Body CreateEventRequest request); - - @POST("wp-json/kbs/v1/calendar/delete") - Call deleteCalendarEvent(@Body CreateEventRequest request); - - @POST("wp-json/kbs/v1/device/register") - Call registerDevice(@Body RegisterDeviceRequest request); -} - -============================================================ -FILSTI: app\src\main\java\com\kbs\kbsintranett\WpPost.java -============================================================ -package com.kbs.kbsintranett; - -import com.google.gson.annotations.SerializedName; -import java.io.Serializable; -import java.util.Arrays; -import java.util.List; - -public class WpPost implements Serializable { - @SerializedName("title") - public Rendered title; - - @SerializedName("excerpt") - public Rendered excerpt; - - @SerializedName("content") - public Rendered content; - - @SerializedName("date") - public String date; - - @SerializedName("_embedded") - public Embedded embedded; - - public static class Rendered implements Serializable { - @SerializedName("rendered") - public String renderedString; - } - - public static class Embedded implements Serializable { - @SerializedName("wp:featuredmedia") - public List mediaList; - - @SerializedName("wp:term") - public List> termList; - - // NYTT: Forfatter-liste - @SerializedName("author") - public List authorList; - } - - public static class Media implements Serializable { - @SerializedName("source_url") - public String sourceUrl; - } - - public static class Term implements Serializable { - @SerializedName("name") - public String name; - } - - // NYTT: Forfatter-klasse - public static class Author implements Serializable { - @SerializedName("name") - public String name; - } - - public String getTitleStr() { - return title != null ? title.renderedString : "Uten tittel"; - } - - public String getExcerptStr() { - return excerpt != null ? - android.text.Html.fromHtml(excerpt.renderedString, android.text.Html.FROM_HTML_MODE_COMPACT).toString().trim() : ""; - } - - public String getContentStr() { - return content != null ? content.renderedString : ""; - } - - public String getFeaturedImageUrl() { - if (embedded != null && embedded.mediaList != null && !embedded.mediaList.isEmpty()) { - return embedded.mediaList.get(0).sourceUrl; - } - return null; - } - - // NYTT: Hent forfatternavn - public String getAuthorName() { - if (embedded != null && embedded.authorList != null && !embedded.authorList.isEmpty()) { - return embedded.authorList.get(0).name; - } - return "Ukjent"; // Fallback - } - - public String getCategoryName() { - if (embedded != null && embedded.termList != null && !embedded.termList.isEmpty()) { - List categories = embedded.termList.get(0); - if (categories == null || categories.isEmpty()) return ""; - - List priorityCategories = Arrays.asList( - "Avtaler og invitasjoner", "BHT", "Bilhold", "Cordel", - "Ferieavvikling", "Fest og moro", "Generell drift", - "HMS", "IT og sikkerhet", "Miljøfyrtårn", "Møtereferat", "SMX" - ); - - for (Term term : categories) { - if (priorityCategories.contains(term.name)) return term.name; - } - for (Term term : categories) { - if (term.name.contains("Alle ansatte")) return "Til info"; - } - return categories.get(0).name; - } - return ""; - } -} - -============================================================ -FILSTI: app\src\main\res\color\selector_day_text.xml -============================================================ - - - - - - -============================================================ -FILSTI: app\src\main\res\drawable\bg_category_selected.xml -============================================================ - - - - - - -============================================================ -FILSTI: app\src\main\res\drawable\bg_category_unselected.xml -============================================================ - - - - - - - -============================================================ -FILSTI: app\src\main\res\drawable\bg_date_box.xml -============================================================ - - - - - - -============================================================ -FILSTI: app\src\main\res\drawable\ic_book.xml -============================================================ - - - - - - - -============================================================ -FILSTI: app\src\main\res\drawable\ic_form.xml -============================================================ - - - - - - - -============================================================ -FILSTI: app\src\main\res\drawable\ic_handbook_car.xml -============================================================ - - - - -============================================================ -FILSTI: app\src\main\res\drawable\ic_handbook_doc.xml -============================================================ - - - - -============================================================ -FILSTI: app\src\main\res\drawable\ic_handbook_general.xml -============================================================ - - - - -============================================================ -FILSTI: app\src\main\res\drawable\ic_handbook_health.xml -============================================================ - - - - -============================================================ -FILSTI: app\src\main\res\drawable\ic_handbook_people.xml -============================================================ - - - - -============================================================ -FILSTI: app\src\main\res\drawable\ic_handbook_warning.xml -============================================================ - - - - -============================================================ -FILSTI: app\src\main\res\drawable\ic_home.xml -============================================================ - - - - - - - -============================================================ -FILSTI: app\src\main\res\drawable\ic_launcher_background.xml -============================================================ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -============================================================ -FILSTI: app\src\main\res\drawable\ic_launcher_foreground.xml -============================================================ - - - - - - - - - - - - -============================================================ -FILSTI: app\src\main\res\drawable\selector_day_toggle.xml -============================================================ - - - - - - - - - - - - - - - -============================================================ -FILSTI: app\src\main\res\layout\activity_main.xml -============================================================ - - - - - - - - - -============================================================ -FILSTI: app\src\main\res\layout\activity_webview.xml -============================================================ - - - - - - - - -============================================================ -FILSTI: app\src\main\res\layout\bottom_sheet_calendar_details.xml -============================================================ - - - - - - - - - - - - - - - - - - - - - - - -