diff --git a/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java b/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java index a58c761..5dbc441 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java +++ b/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java @@ -118,11 +118,7 @@ public class CalendarManager { } CalendarEvent event = new CalendarEvent(title, rawStart, rawEnd, desc, loc); - - // For lokale events bruker vi ikke reminderMinutes fra API, - // men systemet håndterer varsling selv for lokale kalendere. - // Vi setter den til 0 her for å unngå doble varsler fra appen. - event.setReminderMinutes(0); + event.setReminderMinutes(0); // Systemet håndterer lokale varsler formatEventForUI(event); deviceEvents.add(event); diff --git a/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java b/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java new file mode 100644 index 0000000..5373479 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java @@ -0,0 +1,426 @@ +package com.kbs.kbsintranett; + +import android.app.AlertDialog; +import android.app.DatePickerDialog; +import android.app.TimePickerDialog; +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.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.gson.JsonElement; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Locale; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class CreateEventFragment extends Fragment { + private EditText etTitle, etDesc, etLocation; + private Spinner spinnerCalendar, spinnerReminder, spinnerRecurrence; + private Switch switchAllDay; + private TextView txtPreview; + private Button btnStartDate, btnStartTime, btnEndDate, btnEndTime, btnSave; + + private Calendar startCal = Calendar.getInstance(); + private Calendar endCal = Calendar.getInstance(); + + private String selectedRRule = null; + private boolean isCustomRecurrence = false; + + @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); + spinnerReminder = view.findViewById(R.id.spinner_reminder); + spinnerRecurrence = view.findViewById(R.id.spinner_recurrence); + + txtPreview = view.findViewById(R.id.txt_time_preview); + + 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); + + // 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); + + setupStandardSpinners(); + 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()); + + spinnerRecurrence.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + 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) {} + }); + } + + private void setupStandardSpinners() { + // Må matche PHP switch-case nøyaktig + String[] calendars = { + "Felles", + "Administrasjonen", + "Serviceavdelingen", + "Automasjonsavdelingen", + "Prosjektavdelingen" + }; + spinnerCalendar.setAdapter(new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, calendars)); + + String[] reminders = {"Ingen varsling", "10 min før", "15 min før", "30 min før", "1 time før", "2 timer før", "24 timer før"}; + ArrayAdapter remAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, reminders); + spinnerReminder.setAdapter(remAdapter); + spinnerReminder.setSelection(2); // 15 min default + } + + 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() { + int pos = spinnerCalendar.getSelectedItemPosition(); + switch (pos) { + case 1: return "Administrasjonen"; + case 2: return "Serviceavdelingen"; + case 3: return "Automasjonsavdelingen"; + case 4: return "Prosjektavdelingen"; + default: return "Felles"; // case 0 + } + } + + private int getReminderMinutes() { + int pos = spinnerReminder.getSelectedItemPosition(); + switch (pos) { + case 1: return 10; + case 2: return 15; + case 3: return 30; + case 4: return 60; + case 5: return 120; + case 6: return 1440; + default: return 0; + } + } + + 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()); + + CreateEventRequest req = new CreateEventRequest( + title, + etDesc.getText().toString(), + etLocation.getText().toString(), + sdf.format(startCal.getTime()), + sdf.format(endCal.getTime()), + getCalendarSlug(), + getReminderMinutes(), + isAllDay, + selectedRRule + ); + + Toast.makeText(getContext(), "Oppretter...", Toast.LENGTH_SHORT).show(); + + // HER ER DEN NYE LOGIKKEN FOR Å LESE FEILMELDING FRA PHP + RetrofitClient.getApiService().createCalendarEvent(req).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + Toast.makeText(getContext(), "Hendelse opprettet!", Toast.LENGTH_LONG).show(); + Navigation.findNavController(getView()).navigateUp(); + } else { + // Les feilmelding fra serveren (body) + String errorMsg = "Ukjent feil"; + try { + if (response.errorBody() != null) { + errorMsg = response.errorBody().string(); + } + } catch (Exception e) { + errorMsg = "Kunne ikke lese feil: " + e.getMessage(); + } + + Log.e("KBS_ERROR", "Server svarte med feil: " + errorMsg); + // Vis en kortversjon til brukeren, eller hele hvis du debugger + Toast.makeText(getContext(), "Feil (" + response.code() + "): Sjekk Logcat", Toast.LENGTH_LONG).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Log.e("KBS_ERROR", "Nettverksfeil", t); + Toast.makeText(getContext(), "Nettverksfeil: " + t.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); + } + +} diff --git a/app/src/main/java/com/kbs/kbsintranett/CreateEventRequest.java b/app/src/main/java/com/kbs/kbsintranett/CreateEventRequest.java index 457f7b3..4698223 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CreateEventRequest.java +++ b/app/src/main/java/com/kbs/kbsintranett/CreateEventRequest.java @@ -1,4 +1,43 @@ package com.kbs.kbsintranett; +import com.google.gson.annotations.SerializedName; public class CreateEventRequest { + @SerializedName("title") + public String title; + + @SerializedName("description") + public String description; + + @SerializedName("location") + public String location; // NY + + @SerializedName("start_time") + public String startTime; + + @SerializedName("end_time") + public String endTime; + + @SerializedName("calendar_type") + public String calendarType; + + @SerializedName("reminder_minutes") + public int reminderMinutes; + + @SerializedName("is_all_day") + public boolean isAllDay; // NY + + @SerializedName("recurrence") + public String recurrence; // NY (RRULE) + + public CreateEventRequest(String title, String description, String location, String startTime, String endTime, String calendarType, int reminderMinutes, boolean isAllDay, String recurrence) { + this.title = title; + this.description = description; + this.location = location; + this.startTime = startTime; + this.endTime = endTime; + this.calendarType = calendarType; + this.reminderMinutes = reminderMinutes; + this.isAllDay = isAllDay; + this.recurrence = recurrence; + } } diff --git a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java index 42d8213..241b2e0 100644 --- a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java @@ -3,10 +3,13 @@ package com.kbs.kbsintranett; import android.Manifest; import android.content.pm.PackageManager; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; import android.widget.TextView; +import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; @@ -30,7 +33,6 @@ import retrofit2.Callback; import retrofit2.Response; public class HomeFragment extends Fragment { - private ActivityResultLauncher requestPermissionLauncher; private RecyclerView calendarRecycler; @@ -63,6 +65,16 @@ public class HomeFragment extends Fragment { profileBtn.setOnClickListener(v -> Navigation.findNavController(view).navigate(R.id.navigation_profile)); } + Button btnCreateEvent = view.findViewById(R.id.btn_create_event); + if (UserManager.getInstance().isEditorOrAbove()) { + 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 -> {})); @@ -100,10 +112,8 @@ public class HomeFragment extends Fragment { } private void fetchCalendarEvents(RecyclerView recyclerView) { - // 1. Hent personlige hendelser List deviceEvents = CalendarManager.getDeviceEvents(getContext()); - // 2. Hent felles hendelser fra WordPress (Proxy mot Google) RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback>() { @Override public void onResponse(Call> call, Response> response) { @@ -112,16 +122,12 @@ public class HomeFragment extends Fragment { List apiEvents = new ArrayList<>(); if (response.isSuccessful() && response.body() != null) { apiEvents = response.body(); - // Formater datoer for visning (UI-logikk) for (CalendarEvent e : apiEvents) { CalendarManager.formatEventForUI(e); } } - // 3. Flett og sorter List merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents); - - // 4. Filtrer (kun fremtidige + i dag) List upcomingEvents = new ArrayList<>(); String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date()); @@ -131,7 +137,6 @@ public class HomeFragment extends Fragment { } } - // 5. Vis topp 5 List top5 = new ArrayList<>(); for(int i=0; i> call, Throwable t) { if (!isAdded()) return; - // Fallback til kun lokale events if (!deviceEvents.isEmpty()) { List top5 = new ArrayList<>(); for(int i=0; i> response = RetrofitClient.getApiService().getCalendarEvents().execute(); if (response.isSuccessful() && response.body() != null) { - List events = response.body(); - scheduleAlarms(events); + scheduleAlarms(response.body()); return Result.success(); } else { - int code = response.code(); - Log.e(TAG, "NotificationWorker: API-kall mot WP feilet. Kode: " + code); - // Stopp retry ved klientfeil (4xx) inkludert 429 - if (code >= 400 && code < 500) { + // Ved klientfeil (4xx), stopp retry-loop + if (response.code() >= 400 && response.code() < 500) { + Log.w(TAG, "Kunne ikke hente kalender. Kode: " + response.code()); return Result.failure(); } return Result.retry(); } } catch (IOException e) { - Log.e(TAG, "NotificationWorker: Nettverksfeil", e); + Log.e(TAG, "Nettverksfeil under kalendersjekk", e); return Result.retry(); } } @@ -56,32 +52,23 @@ public class NotificationWorker extends Worker { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (!alarmManager.canScheduleExactAlarms()) { - Log.e(TAG, "NotificationWorker: MANGLER tillatelse for nøyaktige alarmer!"); - return; + return; // Mangler rettigheter, kan ikke sette alarm } } long now = System.currentTimeMillis(); SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); - // Tillater alarmer som skulle gått for inntil 30 minutter siden (Catch-up) - long catchUpWindow = now - (30 * 60 * 1000L); - // Setter ikke alarmer lenger frem enn 24 timer - long futureWindow = now + (24 * 60 * 60 * 1000L); - - int countSet = 0; + long catchUpWindow = now - (30 * 60 * 1000L); // 30 min bakover + long futureWindow = now + (24 * 60 * 60 * 1000L); // 24 timer fremover for (CalendarEvent event : events) { try { - String title = event.getTitle(); - - // Ignorer heldagshendelser (datoformat uten klokkeslett) if (event.getRawDate() == null || event.getRawDate().length() == 10) continue; Date eventDate = null; if (event.getRawDate().contains("T")) { String raw = event.getRawDate(); - // Kutter tidssone for å parse som lokal tid "face value" if (raw.length() > 19) raw = raw.substring(0, 19); eventDate = isoFormat.parse(raw); } @@ -89,38 +76,22 @@ public class NotificationWorker extends Worker { if (eventDate == null) continue; int minutesBefore = event.getReminderMinutes(); - long triggerTime = eventDate.getTime() - (minutesBefore * 60 * 1000L); - - // --- DIAGNOSE: Logg alle "Test"-events for å se hva PHP faktisk leverer --- - if (title.toLowerCase().contains("test")) { - Log.d(TAG, "--------------------------------------------------"); - Log.d(TAG, "DIAGNOSE EVENT: " + title); - Log.d(TAG, " Starttid: " + eventDate); - Log.d(TAG, " PHP sier reminderMinutes: " + minutesBefore); - - if (minutesBefore <= 0) { - Log.d(TAG, " HANDLING: Skippes (Ingen varsling)"); - } else { - Log.d(TAG, " Ønsket alarmtid: " + new Date(triggerTime)); - Log.d(TAG, " Nå: " + new Date(now)); - } - } - // ------------------------------------------------------------------------ - if (minutesBefore <= 0) continue; + long triggerTime = eventDate.getTime() - (minutesBefore * 60 * 1000L); + + // Sjekk om alarmen er innenfor tidsvinduet if (triggerTime > catchUpWindow && triggerTime < futureWindow) { - // CATCH-UP: Hvis tiden har passert, fyr av NÅ. + // Catch-up: Hvis tiden har passert, fyr av umiddelbart if (triggerTime < now) { - Log.i(TAG, " -> Alarmtid passert (" + new Date(triggerTime) + "). Kjører Catch-up NÅ."); triggerTime = now + 1000; } - int alarmId = (title + event.getRawDate()).hashCode(); + int alarmId = (event.getTitle() + event.getRawDate()).hashCode(); Intent intent = new Intent(context, AlarmReceiver.class); - intent.putExtra("TITLE", title); + 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); @@ -138,20 +109,13 @@ public class NotificationWorker extends Worker { alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); } - if (title.toLowerCase().contains("test")) { - Log.d(TAG, " RESULTAT: ALARM SATT OK"); - Log.d(TAG, "--------------------------------------------------"); - } - countSet++; + // Logger kun når en alarm faktisk blir satt/oppdatert + Log.i(TAG, "Alarm satt for: " + event.getTitle() + " (" + new Date(triggerTime) + ")"); } } catch (Exception e) { - Log.e(TAG, "Feil ved behandling av event: " + event.getTitle(), e); + Log.e(TAG, "Feil ved behandling av event", e); } } - - if (countSet == 0) { - Log.d(TAG, "Ingen nye alarmer satt i denne runden."); - } } } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/RetrofitClient.java b/app/src/main/java/com/kbs/kbsintranett/RetrofitClient.java index c6676c1..c1bd042 100644 --- a/app/src/main/java/com/kbs/kbsintranett/RetrofitClient.java +++ b/app/src/main/java/com/kbs/kbsintranett/RetrofitClient.java @@ -18,15 +18,15 @@ 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) { - // ENDRET: Redusert loggnivå fra BODY til BASIC for å unngå spam i Logcat 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); } diff --git a/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java b/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java index dff915a..8ea0fb4 100644 --- a/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java +++ b/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java @@ -19,7 +19,6 @@ 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); @@ -40,11 +39,13 @@ public interface WordPressApiService { @Part List files ); - // BRUKES NÅ: Henter kalenderhendelser via WordPress proxy. -// Dette sikrer at vi får med reminder_minutes fra serveren. @GET("wp-json/kbs/v1/calendar/events") Call> getCalendarEvents(); + // DETTE ER METODEN SOM MANGLER: + @POST("wp-json/kbs/v1/calendar/create") + Call createCalendarEvent(@Body CreateEventRequest request); + @GET("wp-json/gf/v2/entries") Call getEntries( @Query("form_ids") int formId, diff --git a/app/src/main/res/color/selector_day_text.xml b/app/src/main/res/color/selector_day_text.xml new file mode 100644 index 0000000..6393d83 --- /dev/null +++ b/app/src/main/res/color/selector_day_text.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_day_toggle.xml b/app/src/main/res/drawable/selector_day_toggle.xml new file mode 100644 index 0000000..56b1579 --- /dev/null +++ b/app/src/main/res/drawable/selector_day_toggle.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_custom_recurrence.xml b/app/src/main/res/layout/dialog_custom_recurrence.xml new file mode 100644 index 0000000..97b330e --- /dev/null +++ b/app/src/main/res/layout/dialog_custom_recurrence.xml @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +