Opprette kalender og varsler fungerer. Dette er før utvidelse

This commit is contained in:
ErolHaagenrud 2025-12-17 06:20:32 +01:00
parent 544cda4ce4
commit fb9749ba3f
15 changed files with 917 additions and 73 deletions

View file

@ -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);

View file

@ -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() {
// 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<String> 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<String> 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<String> 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<String> 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<JsonElement>() {
@Override
public void onResponse(Call<JsonElement> call, Response<JsonElement> 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<JsonElement> call, Throwable t) {
Log.e("KBS_ERROR", "Nettverksfeil", t);
Toast.makeText(getContext(), "Nettverksfeil: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
}

View file

@ -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;
}
}

View file

@ -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<String> 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<CalendarEvent> deviceEvents = CalendarManager.getDeviceEvents(getContext());
// 2. Hent felles hendelser fra WordPress (Proxy mot Google)
RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback<List<CalendarEvent>>() {
@Override
public void onResponse(Call<List<CalendarEvent>> call, Response<List<CalendarEvent>> response) {
@ -112,16 +122,12 @@ public class HomeFragment extends Fragment {
List<CalendarEvent> 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<CalendarEvent> merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents);
// 4. Filtrer (kun fremtidige + i dag)
List<CalendarEvent> 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<CalendarEvent> top5 = new ArrayList<>();
for(int i=0; i<Math.min(upcomingEvents.size(), 5); i++) {
top5.add(upcomingEvents.get(i));
@ -146,7 +151,6 @@ public class HomeFragment extends Fragment {
@Override
public void onFailure(Call<List<CalendarEvent>> call, Throwable t) {
if (!isAdded()) return;
// Fallback til kun lokale events
if (!deviceEvents.isEmpty()) {
List<CalendarEvent> top5 = new ArrayList<>();
for(int i=0; i<Math.min(deviceEvents.size(), 5); i++) top5.add(deviceEvents.get(i));

View file

@ -17,7 +17,7 @@ import java.util.Locale;
import retrofit2.Response;
public class NotificationWorker extends Worker {
private static final String TAG = "KBS_DEBUG";
private static final String TAG = "NotificationWorker";
public NotificationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
@ -26,26 +26,22 @@ public class NotificationWorker extends Worker {
@NonNull
@Override
public Result doWork() {
Log.d(TAG, "NotificationWorker: Starter sjekk av kalender via WordPress Proxy...");
try {
Response<List<CalendarEvent>> response = RetrofitClient.getApiService().getCalendarEvents().execute();
if (response.isSuccessful() && response.body() != null) {
List<CalendarEvent> 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 .
// 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.");
}
}
}

View file

@ -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);
}

View file

@ -19,7 +19,6 @@ import retrofit2.http.Query;
public interface WordPressApiService {
@GET("wp-json/wp/v2/posts?per_page=10&_embed")
Call<List<WpPost>> getPosts();
@GET("wp-json/kbs/v1/forms/{id}")
Call<GravityForm> getForm(@Path("id") int formId);
@ -40,11 +39,13 @@ public interface WordPressApiService {
@Part List<MultipartBody.Part> files
);
// BRUKES : Henter kalenderhendelser via WordPress proxy.
// Dette sikrer at vi får med reminder_minutes fra serveren.
@GET("wp-json/kbs/v1/calendar/events")
Call<List<CalendarEvent>> getCalendarEvents();
// DETTE ER METODEN SOM MANGLER:
@POST("wp-json/kbs/v1/calendar/create")
Call<JsonElement> createCalendarEvent(@Body CreateEventRequest request);
@GET("wp-json/gf/v2/entries")
Call<GravityEntryResponse> getEntries(
@Query("form_ids") int formId,

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:color="#FFFFFF"/>
<item android:color="#333333"/>
</selector>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<shape android:shape="oval">
<solid android:color="#0069B3"/> <!-- KBS Blå -->
</shape>
</item>
<item>
<shape android:shape="oval">
<solid android:color="#EEEEEE"/> <!-- Lys grå -->
</shape>
</item>
</selector>

View file

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Egendefinert gjentakelse"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="#333"
android:layout_marginBottom="24dp"/>
<!-- FREKVENS -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Gjenta hvert: "
android:textSize="16sp"/>
<EditText
android:id="@+id/et_interval"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:inputType="number"
android:text="1"
android:gravity="center"
android:layout_marginHorizontal="8dp"/>
<Spinner
android:id="@+id/spinner_freq"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:entries="@array/recurrence_freq_array"/>
</LinearLayout>
<!-- UKEDAGER (Vises kun hvis Uke er valgt) -->
<LinearLayout
android:id="@+id/layout_weekdays"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp"
android:visibility="visible">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Gjenta på"
android:layout_marginBottom="8dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="7">
<ToggleButton android:id="@+id/tg_mon" android:textOn="M" android:textOff="M" style="@style/DayToggle"/>
<ToggleButton android:id="@+id/tg_tue" android:textOn="T" android:textOff="T" style="@style/DayToggle"/>
<ToggleButton android:id="@+id/tg_wed" android:textOn="O" android:textOff="O" style="@style/DayToggle"/>
<ToggleButton android:id="@+id/tg_thu" android:textOn="T" android:textOff="T" style="@style/DayToggle"/>
<ToggleButton android:id="@+id/tg_fri" android:textOn="F" android:textOff="F" style="@style/DayToggle"/>
<ToggleButton android:id="@+id/tg_sat" android:textOn="L" android:textOff="L" style="@style/DayToggle"/>
<ToggleButton android:id="@+id/tg_sun" android:textOn="S" android:textOff="S" style="@style/DayToggle"/>
</LinearLayout>
</LinearLayout>
<!-- SLUTTER -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Slutter"
android:layout_marginBottom="8dp"/>
<RadioGroup
android:id="@+id/rg_end"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioButton
android:id="@+id/rb_never"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Aldri"
android:checked="true"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<RadioButton
android:id="@+id/rb_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Den"/>
<Button
android:id="@+id/btn_end_date_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Velg dato"
android:layout_marginStart="16dp"
style="@style/Widget.MaterialComponents.Button.TextButton"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<RadioButton
android:id="@+id/rb_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Etter"/>
<EditText
android:id="@+id/et_count"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:inputType="number"
android:text="13"
android:gravity="center"
android:layout_marginHorizontal="16dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ganger"/>
</LinearLayout>
</RadioGroup>
<!-- KNAPPER -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="end"
android:layout_marginTop="32dp">
<Button
android:id="@+id/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Avbryt"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:textColor="#666"/>
<Button
android:id="@+id/btn_done"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ferdig"
android:backgroundTint="@color/kbs_logo_blue"
android:textColor="#FFF"
android:layout_marginStart="16dp"/>
</LinearLayout>
</LinearLayout>
</ScrollView>

View file

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Opprett ny hendelse"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginBottom="24dp"
android:textColor="#333"/>
<EditText
android:id="@+id/et_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Tittel"
android:inputType="textCapSentences"
android:padding="12dp"
android:background="@android:drawable/edit_text"
android:layout_marginBottom="16dp"/>
<EditText
android:id="@+id/et_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Beskrivelse / Notater"
android:inputType="textMultiLine"
android:minLines="3"
android:padding="12dp"
android:gravity="top"
android:background="@android:drawable/edit_text"
android:layout_marginBottom="16dp"/>
<EditText
android:id="@+id/et_location"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Hvor (Sted)"
android:inputType="textCapWords"
android:padding="12dp"
android:background="@android:drawable/edit_text"
android:layout_marginBottom="16dp"/>
<Switch
android:id="@+id/switch_all_day"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hele dagen"
android:layout_marginBottom="16dp"/>
<!-- Start Dato/Tid -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<Button
android:id="@+id/btn_start_date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Startdato"
android:layout_marginEnd="4dp"/>
<Button
android:id="@+id/btn_start_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Starttid"
android:layout_marginStart="4dp"/>
</LinearLayout>
<!-- Slutt Dato/Tid -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="16dp">
<Button
android:id="@+id/btn_end_date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Sluttdato"
android:layout_marginEnd="4dp"/>
<Button
android:id="@+id/btn_end_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Slutttid"
android:layout_marginStart="4dp"/>
</LinearLayout>
<TextView
android:id="@+id/txt_time_preview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Valgt: -"
android:gravity="center"
android:textStyle="bold"
android:layout_marginBottom="24dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Velg Kalender:"
android:textSize="14sp"
android:textColor="#666"/>
<Spinner
android:id="@+id/spinner_calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:padding="12dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Gjentakelse:"
android:textSize="14sp"
android:textColor="#666"/>
<Spinner
android:id="@+id/spinner_recurrence"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:padding="12dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Varsling:"
android:textSize="14sp"
android:textColor="#666"/>
<Spinner
android:id="@+id/spinner_reminder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:padding="12dp"/>
<Button
android:id="@+id/btn_save_event"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Lagre i Kalender"
android:backgroundTint="@color/kbs_logo_blue"
android:textColor="#FFFFFF"/>
</LinearLayout>
</ScrollView>

View file

@ -7,6 +7,7 @@
android:padding="8dp"
android:background="@color/kbs_very_light_blue">
<!-- OVERSKRIFT OG PROFIL -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -33,6 +34,21 @@
app:tint="@color/kbs_logo_blue"/>
</RelativeLayout>
<!-- NYTT: KNAPP FOR Å OPPRETTE HENDELSE -->
<!-- Denne var borte i din fil. Den settes til visibility="gone" som standard,
og skrus på av Java-koden hvis brukeren er admin. -->
<Button
android:id="@+id/btn_create_event"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="+ Ny Kalenderhendelse"
android:backgroundTint="@color/kbs_logo_blue"
android:textColor="#FFFFFF"
android:layout_marginHorizontal="8dp"
android:layout_marginBottom="12dp"
android:visibility="gone"/>
<!-- KALENDER-SEKSJON -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -70,6 +86,7 @@
android:scrollbars="vertical"
android:layout_marginBottom="16dp"/>
<!-- NYHETER-SEKSJON -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -31,8 +31,19 @@
<action
android:id="@+id/action_home_to_newsDetail"
app:destination="@id/navigation_news_detail" />
<!-- HER ER DEN MANGLENDE LINKEN: -->
<action
android:id="@+id/action_home_to_create_event"
app:destination="@id/navigation_create_event" />
</fragment>
<!-- NYTT FRAGMENT: Opprett hendelse -->
<fragment
android:id="@+id/navigation_create_event"
android:name="com.kbs.kbsintranett.CreateEventFragment"
android:label="Ny Hendelse"
tools:layout="@layout/fragment_create_event" />
<fragment
android:id="@+id/navigation_calendar_full"
android:name="com.kbs.kbsintranett.CalendarFullFragment"

View file

@ -1,3 +1,10 @@
<resources>
<string name="app_name">KBS Intranett</string>
<!-- NYTT: Array for gjentakelse-spinneren -->
<string-array name="recurrence_freq_array">
<item>dag</item>
<item>uke</item>
<item>måned</item>
<item>år</item>
</string-array>
</resources>

View file

@ -7,4 +7,17 @@
</style>
<style name="Theme.KBSIntranett" parent="Base.Theme.KBSIntranett" />
<!-- NYTT: Stil for runde ukedag-knapper (M T O T F L S) -->
<style name="DayToggle">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">40dp</item>
<item name="android:layout_weight">1</item>
<item name="android:background">@drawable/selector_day_toggle</item>
<item name="android:textColor">@color/selector_day_text</item>
<item name="android:textOff">M</item>
<item name="android:textOn">M</item>
<item name="android:textSize">12sp</item>
<item name="android:layout_margin">2dp</item>
</style>
</resources>