From 3b89af6605393a5bd927d6bc6626de8a22cb8956 Mon Sep 17 00:00:00 2001 From: ErolHaagenrud Date: Tue, 6 Jan 2026 14:15:02 +0100 Subject: [PATCH] =?UTF-8?q?F=C3=B8r=20oppdateringer=20av=20fragment.home.x?= =?UTF-8?q?ml,=20HomeAdapter.java=20og=20HomeFragment.java?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- .../com/kbs/kbsintranett/CalendarAdapter.java | 20 +- .../CalendarDetailsBottomSheet.java | 99 +- .../kbs/kbsintranett/CreateEventFragment.java | 210 ++++- .../main/java/com/kbs/kbsintranett/User.java | 36 + .../kbs/kbsintranett/WordPressApiService.java | 5 +- .../layout/bottom_sheet_calendar_details.xml | 34 +- .../main/res/layout/fragment_create_event.xml | 51 +- hele_prosjektet.txt | 865 ++++++++++++------ 9 files changed, 993 insertions(+), 329 deletions(-) create mode 100644 app/src/main/java/com/kbs/kbsintranett/User.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 196ce47..962afa0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -13,7 +13,7 @@ android { minSdk = 28 targetSdk = 34 versionCode = 4 - versionName = "1.5.0" + versionName = "1.5.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/com/kbs/kbsintranett/CalendarAdapter.java b/app/src/main/java/com/kbs/kbsintranett/CalendarAdapter.java index 51028d7..950f4a9 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CalendarAdapter.java +++ b/app/src/main/java/com/kbs/kbsintranett/CalendarAdapter.java @@ -16,6 +16,9 @@ public class CalendarAdapter extends RecyclerView.Adapter 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); } @@ -40,9 +43,18 @@ public class CalendarAdapter extends RecyclerView.Adapter) også blir klikkbare - Linkify.addLinks(desc, Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS); - - // Gjør at man kan klikke på lenkene - desc.setMovementMethod(LinkMovementMethod.getInstance()); - - desc.setVisibility(View.VISIBLE); + 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()); // Viser kun teksten, ikonet ligger i XML + loc.setText(event.getLocation()); loc.setVisibility(View.VISIBLE); - - // Gjør adressen klikkbar for å åpne kart loc.setOnClickListener(v -> { String location = event.getLocation(); - // Opprett en geo-URI. "q=" søker etter stedet. Uri gmmIntentUri = Uri.parse("geo:0,0?q=" + Uri.encode(location)); Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri); - // Sett pakke til Google Maps for å foretrekke det, men la systemet velge hvis ikke installert mapIntent.setPackage("com.google.android.apps.maps"); - try { startActivity(mapIntent); } catch (Exception e) { - // Fallback: Hvis Google Maps ikke finnes, prøv uten pakkenavn (hvilken som helst kart-app) mapIntent.setPackage(null); try { startActivity(mapIntent); @@ -137,6 +156,35 @@ public class CalendarDetailsBottomSheet extends BottomSheetDialogFragment { 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") @@ -157,12 +205,9 @@ public class CalendarDetailsBottomSheet extends BottomSheetDialogFragment { public void onResponse(Call call, Response response) { if (response.isSuccessful()) { Toast.makeText(getContext(), "Slettet!", Toast.LENGTH_SHORT).show(); - - // VARSLE LISTEN OM AT NOE ER SLETTET if (changeListener != null) { changeListener.onEventChanged(); } - dismiss(); } else { Toast.makeText(getContext(), "Kunne ikke slette", Toast.LENGTH_LONG).show(); diff --git a/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java b/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java index 398ec55..068fc03 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java @@ -15,6 +15,7 @@ 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; @@ -31,9 +32,12 @@ 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; @@ -43,8 +47,9 @@ public class CreateEventFragment extends Fragment { private Spinner spinnerCalendar, spinnerRecurrence; private ChipGroup chipGroupReminders; private Switch switchAllDay; - private TextView txtPreview; - private Button btnStartDate, btnStartTime, btnEndDate, btnEndTime, btnSave; + 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(); @@ -52,6 +57,13 @@ public class CreateEventFragment extends Fragment { 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; @@ -75,13 +87,17 @@ public class CreateEventFragment extends Fragment { 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); @@ -91,6 +107,7 @@ public class CreateEventFragment extends Fragment { setupCalendarSpinner(); setupReminderChips(); + fetchUsers(); // SJEKK OM VI ER I REDIGERINGS-MODUS if (getArguments() != null && getArguments().containsKey("edit_event")) { @@ -113,6 +130,22 @@ public class CreateEventFragment extends Fragment { 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) { @@ -129,12 +162,148 @@ 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() { + 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()); - String cleanDesc = event.getDescription().replaceAll("#varsel:[\\d,]+", "").trim(); + // 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()); @@ -523,6 +692,30 @@ public class CreateEventFragment extends Fragment { 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 @@ -534,8 +727,6 @@ public class CreateEventFragment extends Fragment { Toast.makeText(getContext(), eventToEdit != null ? "Oppdaterer..." : "Oppretter...", Toast.LENGTH_SHORT).show(); - // **VIKTIG ENDRING:** - // Vi henter context her (mens Fragmentet lever) for å bruke den i bakgrunnstråden final Context appContext = requireContext().getApplicationContext(); Call call; @@ -550,10 +741,7 @@ public class CreateEventFragment extends Fragment { public void onResponse(Call call, Response response) { if (response.isSuccessful()) { Toast.makeText(getContext(), eventToEdit != null ? "Hendelse oppdatert!" : "Hendelse opprettet!", Toast.LENGTH_LONG).show(); - - // Start oppdatering av alarmer i bakgrunnen fetchCalendarAndSchedule(appContext); - Navigation.findNavController(getView()).navigateUp(); } else { Toast.makeText(getContext(), "Feil (" + response.code() + ")", Toast.LENGTH_LONG).show(); @@ -567,16 +755,12 @@ public class CreateEventFragment extends Fragment { }); } - // Oppdatert metode som tar imot context som parameter private void fetchCalendarAndSchedule(Context context) { new Thread(() -> { try { - // Sjekk en ekstra gang for å være sikker if (context == null) return; - Response> response = RetrofitClient.getApiService().getCalendarEvents().execute(); if (response.isSuccessful() && response.body() != null) { - // Bruk contexten vi fikk tilsendt, ikke getContext() som kan være null AlarmScheduler.scheduleAlarmsForEvents(context, response.body()); } } catch (Exception e) { diff --git a/app/src/main/java/com/kbs/kbsintranett/User.java b/app/src/main/java/com/kbs/kbsintranett/User.java new file mode 100644 index 0000000..effa1b7 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/User.java @@ -0,0 +1,36 @@ +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; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java b/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java index 726539a..e561ff0 100644 --- a/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java +++ b/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java @@ -45,6 +45,10 @@ public interface WordPressApiService { @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, @@ -73,7 +77,6 @@ public interface WordPressApiService { @POST("wp-json/kbs/v1/calendar/delete") Call deleteCalendarEvent(@Body CreateEventRequest request); - // NYTT: Registrer enhet for push-varsler @POST("wp-json/kbs/v1/device/register") Call registerDevice(@Body RegisterDeviceRequest request); } \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_calendar_details.xml b/app/src/main/res/layout/bottom_sheet_calendar_details.xml index 0acc9d3..20512e4 100644 --- a/app/src/main/res/layout/bottom_sheet_calendar_details.xml +++ b/app/src/main/res/layout/bottom_sheet_calendar_details.xml @@ -42,10 +42,6 @@ android:drawablePadding="8dp" app:drawableStartCompat="@android:drawable/ic_menu_recent_history"/> - - + + + + + + + + + + + + + + + + +