From ee198150c22368ba4e8a05a93a72016000e79ed9 Mon Sep 17 00:00:00 2001 From: ErolHaagenrud Date: Thu, 8 Jan 2026 11:32:48 +0100 Subject: [PATCH] =?UTF-8?q?F=C3=B8r=20sentralisert=20brukerliste?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kbs/kbsintranett/AddTaskBottomSheet.java | 179 ++++++++++++++---- .../kbsintranett/CalendarFullFragment.java | 12 +- .../com/kbs/kbsintranett/HomeAdapter.java | 95 ++++++++-- .../com/kbs/kbsintranett/HomeFragment.java | 122 +++++++++--- .../com/kbs/kbsintranett/MainActivity.java | 20 +- .../kbs/kbsintranett/NewsFullFragment.java | 17 +- .../com/kbs/kbsintranett/TaskAdapter.java | 31 ++- .../kbsintranett/TaskDetailsBottomSheet.java | 15 +- .../java/com/kbs/kbsintranett/TaskItem.java | 29 +-- .../com/kbs/kbsintranett/TasksFragment.java | 136 +++++++++---- .../kbs/kbsintranett/WordPressApiService.java | 59 +++--- .../main/res/layout/bottom_sheet_add_task.xml | 148 ++++++++------- .../res/layout/bottom_sheet_task_details.xml | 31 ++- .../res/layout/fragment_calendar_full.xml | 64 +++---- .../main/res/layout/fragment_news_full.xml | 26 +-- app/src/main/res/layout/fragment_tasks.xml | 16 +- .../main/res/layout/item_home_empty_tasks.xml | 10 + app/src/main/res/layout/item_task.xml | 11 +- 18 files changed, 684 insertions(+), 337 deletions(-) create mode 100644 app/src/main/res/layout/item_home_empty_tasks.xml diff --git a/app/src/main/java/com/kbs/kbsintranett/AddTaskBottomSheet.java b/app/src/main/java/com/kbs/kbsintranett/AddTaskBottomSheet.java index ee80466..a1247f5 100644 --- a/app/src/main/java/com/kbs/kbsintranett/AddTaskBottomSheet.java +++ b/app/src/main/java/com/kbs/kbsintranett/AddTaskBottomSheet.java @@ -2,22 +2,27 @@ package com.kbs.kbsintranett; import android.app.AlertDialog; import android.app.DatePickerDialog; +import android.app.Dialog; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Locale; +import java.util.Map; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -26,15 +31,18 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { private EditText etTitle, etDesc; private Button btnDate, btnUsers, btnSave; - private TextView txtDatePreview, txtUsersPreview; + 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 + public interface OnTaskAddedListener { void onTaskAdded(TaskItem task); + void onTaskUpdated(TaskItem task); // NYTT } private OnTaskAddedListener listener; @@ -43,11 +51,32 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { this.listener = listener; } + // NYTT: Metode for å sette oppgaven som skal redigeres + public void setTaskToEdit(TaskItem task) { + this.taskToEdit = task; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + BottomSheetDialog dialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState); + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + dialog.setOnShowListener(dialogInterface -> { + BottomSheetDialog d = (BottomSheetDialog) dialogInterface; + View bottomSheet = d.findViewById(com.google.android.material.R.id.design_bottom_sheet); + if (bottomSheet != null) { + BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED); + } + }); + return dialog; + } + @Nullable @Override 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 etTitle = v.findViewById(R.id.et_task_title); etDesc = v.findViewById(R.id.et_task_desc); btnDate = v.findViewById(R.id.btn_task_date); @@ -56,7 +85,16 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { txtDatePreview = v.findViewById(R.id.txt_date_preview); txtUsersPreview = v.findViewById(R.id.txt_users_preview); - dueDate.add(Calendar.DAY_OF_MONTH, 1); + if (taskToEdit != null) { + txtSheetTitle.setText("Rediger Oppgave"); + etTitle.setText(taskToEdit.getTitle()); + etDesc.setText(taskToEdit.getDescription()); + dueDate.setTimeInMillis(taskToEdit.getDueDate()); + btnSave.setText("Oppdater Oppgave"); + } else { + dueDate.add(Calendar.DAY_OF_MONTH, 1); + } + updateDatePreview(); btnDate.setOnClickListener(view -> { @@ -81,6 +119,18 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { if (response.isSuccessful() && response.body() != null) { allUsersFromApi = response.body(); filterUsersByHierarchy(); + + // Hvis vi redigerer, må vi mappe eksisterende deltakere til selectedUsers listen + if (taskToEdit != null) { + selectedUsers.clear(); + Map currentAssignees = taskToEdit.getAssigneeStatus(); + for (User u : allUsersFromApi) { + if (currentAssignees.containsKey(u.getEmail())) { + selectedUsers.add(u); + } + } + updateUsersPreview(); + } } } @Override @@ -91,34 +141,30 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { private void filterUsersByHierarchy() { filteredUsers.clear(); UserManager me = UserManager.getInstance(); - - // 1. Hvis Admin/Editor, legg til alle - if (me.isEditorOrAbove()) { - filteredUsers.addAll(allUsersFromApi); - return; - } - - // 2. Finn mine roller/avdelinger 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; } } - // 3. Filtrer logikk (Identisk med CreateEventFragment) + if (me.isEditorOrAbove()) { + filteredUsers.addAll(allUsersFromApi); + return; + } + for (User u : allUsersFromApi) { - // Alltid legg til seg selv 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(); - // Sjekk om vi deler en avdelingsrolle if ((r.equals("serviceavdelingen") && myRoles.contains("serviceavdelingen")) || (r.equals("automasjonsavdelingen") && myRoles.contains("automasjonsavdelingen")) || (r.equals("prosjektavdelingen") && myRoles.contains("prosjektavdelingen")) || @@ -128,10 +174,7 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { break; } } - - if (hasAccess && !filteredUsers.contains(u)) { - filteredUsers.add(u); - } + if (hasAccess && !filteredUsers.contains(u)) filteredUsers.add(u); } } @@ -140,28 +183,44 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { Toast.makeText(getContext(), "Henter tilgjengelige personer...", Toast.LENGTH_SHORT).show(); return; } - String[] names = new String[filteredUsers.size()]; boolean[] checked = new boolean[filteredUsers.size()]; for (int i = 0; i < filteredUsers.size(); i++) { names[i] = filteredUsers.get(i).getName(); - checked[i] = selectedUsers.contains(filteredUsers.get(i)); + // 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())) { + isSelected = true; + break; + } + } + checked[i] = isSelected; } - new AlertDialog.Builder(getContext()) .setTitle("Tildel til...") .setMultiChoiceItems(names, checked, (dialog, which, isChecked) -> { - if (isChecked) selectedUsers.add(filteredUsers.get(which)); - else selectedUsers.remove(filteredUsers.get(which)); - }) - .setPositiveButton("OK", (dialog, which) -> { - if (selectedUsers.isEmpty()) txtUsersPreview.setText("Kun meg"); - else if (selectedUsers.size() == 1) txtUsersPreview.setText(selectedUsers.get(0).getName()); - else txtUsersPreview.setText(selectedUsers.size() + " personer valgt"); + 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); + } else { + User toRemove = null; + for(User su : selectedUsers) if(su.getEmail().equalsIgnoreCase(user.getEmail())) toRemove = su; + if(toRemove != null) selectedUsers.remove(toRemove); + } }) + .setPositiveButton("OK", (dialog, which) -> updateUsersPreview()) .show(); } + private void updateUsersPreview() { + if (selectedUsers.isEmpty()) txtUsersPreview.setText("Kun meg"); + else if (selectedUsers.size() == 1) txtUsersPreview.setText(selectedUsers.get(0).getName()); + else txtUsersPreview.setText(selectedUsers.size() + " personer valgt"); + } + private void updateDatePreview() { SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()); txtDatePreview.setText("Frist: " + sdf.format(dueDate.getTime())); @@ -174,14 +233,64 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment { return; } - TaskItem task = new TaskItem(title, etDesc.getText().toString(), dueDate.getTimeInMillis()); - if (selectedUsers.isEmpty()) { - task.addAssignee(UserManager.getInstance().getUserEmail()); - } else { - for (User u : selectedUsers) task.addAssignee(u.getEmail()); - } + 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. - if (listener != null) listener.onTaskAdded(task); + // Lag en kopi av gamle statuser for å bevare "Fullført" for de som fortsatt er med + Map oldStatus = taskToEdit.getAssigneeStatus(); + taskToEdit.getAssigneeStatus().clear(); + + if (selectedUsers.isEmpty()) { + String myEmail = UserManager.getInstance().getUserEmail(); + taskToEdit.addAssignee(myEmail); + if (oldStatus.containsKey(myEmail)) taskToEdit.setParticipantStatus(myEmail, oldStatus.get(myEmail)); + } else { + for (User u : selectedUsers) { + taskToEdit.addAssignee(u.getEmail()); + if (oldStatus.containsKey(u.getEmail())) { + taskToEdit.setParticipantStatus(u.getEmail(), oldStatus.get(u.getEmail())); + } + } + } + + // 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()); + + 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()); + } else { + for (User u : selectedUsers) newTask.addAssignee(u.getEmail()); + } + if (listener != null) listener.onTaskAdded(newTask); + } 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/CalendarFullFragment.java b/app/src/main/java/com/kbs/kbsintranett/CalendarFullFragment.java index 083e110..de4b47d 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CalendarFullFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/CalendarFullFragment.java @@ -6,7 +6,6 @@ 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 androidx.annotation.NonNull; @@ -15,6 +14,7 @@ import androidx.fragment.app.Fragment; import androidx.navigation.Navigation; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.ArrayList; import java.util.List; import retrofit2.Call; @@ -26,7 +26,7 @@ public class CalendarFullFragment extends Fragment { private RecyclerView recyclerView; private ProgressBar progressBar; private TextView emptyView; - private Button btnAddEvent; + private FloatingActionButton fabAddEvent; private LinearLayoutManager layoutManager; @Nullable @@ -41,16 +41,14 @@ public class CalendarFullFragment extends Fragment { recyclerView = view.findViewById(R.id.recycler_full_calendar); progressBar = view.findViewById(R.id.loading_full_calendar); emptyView = view.findViewById(R.id.empty_view_calendar); - btnAddEvent = view.findViewById(R.id.btn_add_calendar_event); + fabAddEvent = view.findViewById(R.id.fab_add_calendar_event); layoutManager = new LinearLayoutManager(getContext()); recyclerView.setLayoutManager(layoutManager); - // VIKTIG: Ingen referanse til btn_back_calendar her lenger. - if (!UserManager.getInstance().getWriteableCalendars().isEmpty() || UserManager.getInstance().isEditorOrAbove()) { - btnAddEvent.setVisibility(View.VISIBLE); - btnAddEvent.setOnClickListener(v -> Navigation.findNavController(view).navigate(R.id.navigation_create_event)); + fabAddEvent.setVisibility(View.VISIBLE); + fabAddEvent.setOnClickListener(v -> Navigation.findNavController(view).navigate(R.id.navigation_create_event)); } } diff --git a/app/src/main/java/com/kbs/kbsintranett/HomeAdapter.java b/app/src/main/java/com/kbs/kbsintranett/HomeAdapter.java index adf449c..0a519cd 100644 --- a/app/src/main/java/com/kbs/kbsintranett/HomeAdapter.java +++ b/app/src/main/java/com/kbs/kbsintranett/HomeAdapter.java @@ -2,18 +2,25 @@ package com.kbs.kbsintranett; import android.content.res.ColorStateList; import android.graphics.Color; +import android.graphics.Paint; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.CheckBox; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.cardview.widget.CardView; +import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; +import java.util.Locale; public class HomeAdapter extends RecyclerView.Adapter { @@ -21,17 +28,21 @@ public class HomeAdapter extends RecyclerView.Adapter { public static final int TYPE_SECTION_TITLE = 2; public static final int TYPE_CALENDAR_ITEM = 3; public static final int TYPE_NEWS_ITEM = 4; + public static final int TYPE_TASK_ITEM = 5; + public static final int TYPE_EMPTY_TASKS = 6; private final List items; private final OnHomeClickListener listener; public interface OnHomeClickListener { - void onProfileClick(); void onCreateEventClick(); void onViewAllCalendarClick(); void onViewAllNewsClick(); + void onViewAllTasksClick(); void onCalendarItemClick(CalendarEvent event); void onNewsItemClick(WpPost post); + void onTaskItemClick(TaskItem task); + void onTaskStatusChanged(TaskItem task, boolean isDone); } public HomeAdapter(List items, OnHomeClickListener listener) { @@ -46,6 +57,8 @@ public class HomeAdapter extends RecyclerView.Adapter { if (item instanceof SectionTitleItem) return TYPE_SECTION_TITLE; if (item instanceof CalendarEvent) return TYPE_CALENDAR_ITEM; if (item instanceof WpPost) return TYPE_NEWS_ITEM; + if (item instanceof TaskItem) return TYPE_TASK_ITEM; + if (item instanceof EmptyTasksItem) return TYPE_EMPTY_TASKS; return -1; } @@ -60,6 +73,10 @@ public class HomeAdapter extends RecyclerView.Adapter { return new SectionTitleViewHolder(inflater.inflate(R.layout.item_home_section_title, parent, false)); case TYPE_CALENDAR_ITEM: return new CalendarViewHolder(inflater.inflate(R.layout.item_calendar, parent, false)); + case TYPE_TASK_ITEM: + return new TaskViewHolder(inflater.inflate(R.layout.item_task, parent, false)); + case TYPE_EMPTY_TASKS: + return new EmptyViewHolder(inflater.inflate(R.layout.item_home_empty_tasks, parent, false)); case TYPE_NEWS_ITEM: return new NewsViewHolder(inflater.inflate(R.layout.item_news, parent, false)); default: @@ -79,7 +96,8 @@ public class HomeAdapter extends RecyclerView.Adapter { SectionTitleViewHolder vh = (SectionTitleViewHolder) holder; vh.title.setText(section.title); vh.btnViewAll.setOnClickListener(v -> { - if (section.isCalendar) listener.onViewAllCalendarClick(); + if (section.type == SectionTitleItem.TYPE_CALENDAR) listener.onViewAllCalendarClick(); + else if (section.type == SectionTitleItem.TYPE_TASKS) listener.onViewAllTasksClick(); else listener.onViewAllNewsClick(); }); } @@ -90,28 +108,49 @@ public class HomeAdapter extends RecyclerView.Adapter { vh.month.setText(event.getMonth()); vh.time.setText(event.getTime()); vh.title.setText(event.getTitle()); - boolean isPrivate = event.getDescription() != null && event.getDescription().contains("#deltakere:"); - int color; try { - color = Color.parseColor(isPrivate ? "#673AB7" : event.getCalendarColor()); + int color = Color.parseColor(isPrivate ? "#673AB7" : event.getCalendarColor()); + vh.dateBox.setBackgroundTintList(ColorStateList.valueOf(color)); } catch (Exception e) { - color = Color.parseColor("#0069B3"); + vh.dateBox.setBackgroundTintList(ColorStateList.valueOf(Color.parseColor("#0069B3"))); } - vh.dateBox.setBackgroundTintList(ColorStateList.valueOf(color)); vh.itemView.setOnClickListener(v -> listener.onCalendarItemClick(event)); } + else if (holder instanceof TaskViewHolder) { + TaskItem task = (TaskItem) item; + TaskViewHolder vh = (TaskViewHolder) holder; + vh.title.setText(task.getTitle()); + SimpleDateFormat sdf = new SimpleDateFormat("dd. MMM", Locale.getDefault()); + vh.date.setText("Frist: " + sdf.format(new Date(task.getDueDate()))); + + String myEmail = UserManager.getInstance().getUserEmail(); + boolean myStatus = task.getParticipantStatus(myEmail); + vh.checkBox.setChecked(myStatus); + + long now = System.currentTimeMillis(); + boolean isOverdue = task.getDueDate() < now && !task.isFullyCompleted() && !myStatus; + + if (isOverdue) { + vh.cardView.setCardBackgroundColor(ContextCompat.getColor(vh.itemView.getContext(), R.color.kbs_soft_light_pink_beige)); + vh.date.setTextColor(ContextCompat.getColor(vh.itemView.getContext(), R.color.kbs_logo_accent_red)); + } else { + vh.cardView.setCardBackgroundColor(Color.WHITE); + vh.date.setTextColor(ContextCompat.getColor(vh.itemView.getContext(), R.color.kbs_muted_blue_gray)); + } + + vh.checkBox.setOnClickListener(v -> listener.onTaskStatusChanged(task, vh.checkBox.isChecked())); + vh.itemView.setOnClickListener(v -> listener.onTaskItemClick(task)); + } else if (holder instanceof NewsViewHolder) { WpPost post = (WpPost) item; NewsViewHolder vh = (NewsViewHolder) holder; vh.title.setText(post.getTitleStr()); vh.excerpt.setText(post.getExcerptStr()); vh.date.setText(post.date); - String cat = post.getCategoryName(); vh.category.setText(cat); vh.category.setVisibility(cat.isEmpty() ? View.GONE : View.VISIBLE); - String imgUrl = post.getFeaturedImageUrl(); if (imgUrl != null) { vh.image.setVisibility(View.VISIBLE); @@ -129,7 +168,6 @@ public class HomeAdapter extends RecyclerView.Adapter { } // --- VIEW HOLDERS --- - static class CreateButtonViewHolder extends RecyclerView.ViewHolder { Button btnCreate; CreateButtonViewHolder(View v) { super(v); btnCreate = v.findViewById(R.id.btn_create_event); } @@ -143,13 +181,26 @@ public class HomeAdapter extends RecyclerView.Adapter { static class CalendarViewHolder extends RecyclerView.ViewHolder { TextView day, month, title, time; LinearLayout dateBox; - CalendarViewHolder(View v) { + CalendarViewHolder(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); + } + } + + static class TaskViewHolder extends RecyclerView.ViewHolder { + TextView title, date; + CheckBox checkBox; + CardView cardView; + TaskViewHolder(View v) { super(v); - day = v.findViewById(R.id.cal_day); - month = v.findViewById(R.id.cal_month); - title = v.findViewById(R.id.cal_title); - time = v.findViewById(R.id.cal_time); - dateBox = v.findViewById(R.id.date_box_background); + title = v.findViewById(R.id.task_title); + date = v.findViewById(R.id.task_date); + checkBox = v.findViewById(R.id.task_checkbox); + cardView = (CardView) v; } } @@ -166,10 +217,18 @@ public class HomeAdapter extends RecyclerView.Adapter { } } + static class EmptyViewHolder extends RecyclerView.ViewHolder { + EmptyViewHolder(View v) { super(v); } + } + // --- WRAPPER KLASSER --- public static class CreateButtonItem {} + public static class EmptyTasksItem {} public static class SectionTitleItem { - String title; boolean isCalendar; - public SectionTitleItem(String t, boolean c) { this.title = t; this.isCalendar = c; } + public static final int TYPE_CALENDAR = 0; + public static final int TYPE_NEWS = 1; + public static final int TYPE_TASKS = 2; + String title; int type; + public SectionTitleItem(String t, int type) { this.title = t; this.type = type; } } } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java index e91f454..e553a51 100644 --- a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java @@ -9,6 +9,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ProgressBar; +import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; @@ -19,8 +20,10 @@ import androidx.navigation.Navigation; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import com.google.gson.JsonElement; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; @@ -37,6 +40,7 @@ public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickLis private List currentEvents = new ArrayList<>(); private List currentNews = new ArrayList<>(); + private List currentTasks = new ArrayList<>(); private int activeNetworkCalls = 0; private ActivityResultLauncher requestPermissionLauncher; @@ -67,22 +71,17 @@ public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickLis recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); swipeRefreshLayout.setOnRefreshListener(this::refreshData); - if (android.os.Build.VERSION.SDK_INT >= 33) { - if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); - } - } - refreshData(); } private void refreshData() { if (activeNetworkCalls > 0) return; - activeNetworkCalls = 2; + activeNetworkCalls = 3; if (mainProgressBar != null) mainProgressBar.setVisibility(View.VISIBLE); fetchCalendarData(); fetchNewsData(); + fetchTaskData(); } private void fetchCalendarData() { @@ -106,12 +105,10 @@ public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickLis } else { apiEvents = CacheManager.getCachedCalendarEvents(getContext()); } - for (CalendarEvent e : apiEvents) CalendarManager.formatEventForUI(e); currentEvents = CalendarManager.mergeAndSort(apiEvents, deviceEvents); checkLoadingComplete(); } - @Override public void onFailure(Call> call, Throwable t) { List cached = CacheManager.getCachedCalendarEvents(getContext()); @@ -137,7 +134,6 @@ public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickLis formatNewsDates(currentNews); checkLoadingComplete(); } - @Override public void onFailure(Call> call, Throwable t) { currentNews = CacheManager.getCachedNewsPosts(getContext()); @@ -147,6 +143,26 @@ public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickLis }); } + private void fetchTaskData() { + RetrofitClient.getApiService().getTasks().enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + currentTasks = response.body(); + CacheManager.saveTasks(getContext(), currentTasks); + } else { + currentTasks = CacheManager.getTasks(getContext()); + } + checkLoadingComplete(); + } + @Override + public void onFailure(Call> call, Throwable t) { + currentTasks = CacheManager.getTasks(getContext()); + checkLoadingComplete(); + } + }); + } + private void formatNewsDates(List posts) { SimpleDateFormat rawFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); SimpleDateFormat targetFormat = new SimpleDateFormat("dd. MMM yyyy", Locale.getDefault()); @@ -170,45 +186,97 @@ public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickLis private void buildAndDisplayList() { List items = new ArrayList<>(); + String myEmail = UserManager.getInstance().getUserEmail(); - // 1. Kalender Seksjon (Knappen "Ny hendelse" er fjernet herfra) - items.add(new HomeAdapter.SectionTitleItem("Kommende hendelser", true)); + items.add(new HomeAdapter.SectionTitleItem("Kommende hendelser", HomeAdapter.SectionTitleItem.TYPE_CALENDAR)); String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date()); - int count = 0; + int calCount = 0; for (CalendarEvent e : currentEvents) { if (e.getRawDate() != null && e.getRawDate().compareTo(today) >= 0) { items.add(e); - count++; + calCount++; } - if (count >= 3) break; + if (calCount >= 3) break; } - // 2. Nyheter Seksjon - items.add(new HomeAdapter.SectionTitleItem("Siste nytt", false)); + items.add(new HomeAdapter.SectionTitleItem("Mine oppgaver", HomeAdapter.SectionTitleItem.TYPE_TASKS)); + List myActiveTasks = new ArrayList<>(); + for (TaskItem t : currentTasks) { + if (t.isUserParticipant(myEmail) && !t.getParticipantStatus(myEmail) && !t.isFullyCompleted()) { + myActiveTasks.add(t); + } + } + + if (myActiveTasks.isEmpty()) { + items.add(new HomeAdapter.EmptyTasksItem()); + } else { + Collections.sort(myActiveTasks, (t1, t2) -> Long.compare(t1.getDueDate(), t2.getDueDate())); + for (int i = 0; i < Math.min(myActiveTasks.size(), 3); i++) { + items.add(myActiveTasks.get(i)); + } + } + + items.add(new HomeAdapter.SectionTitleItem("Siste nytt", HomeAdapter.SectionTitleItem.TYPE_NEWS)); items.addAll(currentNews); adapter = new HomeAdapter(items, this); recyclerView.setAdapter(adapter); } - @Override public void onProfileClick() {} - @Override public void onCreateEventClick() { - Navigation.findNavController(getView()).navigate(R.id.navigation_create_event); - } - @Override public void onViewAllCalendarClick() { - Navigation.findNavController(getView()).navigate(R.id.navigation_calendar_full); - } - @Override public void onViewAllNewsClick() { - Navigation.findNavController(getView()).navigate(R.id.navigation_news_full); - } + @Override public void onCreateEventClick() { Navigation.findNavController(getView()).navigate(R.id.navigation_create_event); } + @Override public void onViewAllCalendarClick() { Navigation.findNavController(getView()).navigate(R.id.navigation_calendar_full); } + @Override public void onViewAllNewsClick() { Navigation.findNavController(getView()).navigate(R.id.navigation_news_full); } + @Override public void onViewAllTasksClick() { Navigation.findNavController(getView()).navigate(R.id.navigation_tasks); } + @Override public void onCalendarItemClick(CalendarEvent event) { CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event); sheet.setOnEventChangeListener(this::refreshData); sheet.show(getParentFragmentManager(), "CalendarDetails"); } + @Override public void onNewsItemClick(WpPost post) { Bundle bundle = new Bundle(); bundle.putSerializable("post_data", post); Navigation.findNavController(getView()).navigate(R.id.navigation_news_detail, bundle); } + + @Override public void onTaskItemClick(TaskItem task) { + TaskDetailsBottomSheet sheet = new TaskDetailsBottomSheet(task, new TaskDetailsBottomSheet.OnTaskChangeListener() { + @Override public void onTaskChanged() { saveAndSyncTasks(); } + @Override public void onTaskDeleted(TaskItem taskToDelete) { + currentTasks.remove(taskToDelete); + saveAndSyncTasks(); + } + @Override public void onEditRequested(TaskItem taskToEdit) { + AddTaskBottomSheet editDialog = new AddTaskBottomSheet(); + editDialog.setTaskToEdit(taskToEdit); + editDialog.setOnTaskAddedListener(new AddTaskBottomSheet.OnTaskAddedListener() { + @Override public void onTaskAdded(TaskItem task) {} + @Override public void onTaskUpdated(TaskItem task) { + saveAndSyncTasks(); + } + }); + editDialog.show(getChildFragmentManager(), "EditTask"); + } + }); + sheet.show(getChildFragmentManager(), "TaskDetails"); + } + + @Override public void onTaskStatusChanged(TaskItem task, boolean isDone) { + String myEmail = UserManager.getInstance().getUserEmail(); + task.setParticipantStatus(myEmail, isDone); + if (task.getCreatedByEmail().equalsIgnoreCase(myEmail) && isDone) { + task.setFullyCompleted(true); + } + saveAndSyncTasks(); + } + + private void saveAndSyncTasks() { + CacheManager.saveTasks(getContext(), currentTasks); + buildAndDisplayList(); + RetrofitClient.getApiService().syncTasks(currentTasks).enqueue(new Callback() { + @Override public void onResponse(Call call, Response response) {} + @Override public void onFailure(Call call, Throwable t) {} + }); + } } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/MainActivity.java b/app/src/main/java/com/kbs/kbsintranett/MainActivity.java index a63fb42..23b3d2b 100644 --- a/app/src/main/java/com/kbs/kbsintranett/MainActivity.java +++ b/app/src/main/java/com/kbs/kbsintranett/MainActivity.java @@ -40,10 +40,7 @@ import java.util.concurrent.TimeUnit; public class MainActivity extends AppCompatActivity { - // Din oppdaterte linje: public static final String GOOGLE_WEB_CLIENT_ID = BuildConfig.WEB_CLIENT_ID; - - private static final String TAG = "MainActivity"; private NavController navController; private DrawerLayout drawerLayout; private AppBarConfiguration appBarConfiguration; @@ -73,7 +70,18 @@ public class MainActivity extends AppCompatActivity { .build(); NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration); - NavigationUI.setupWithNavController(navigationView, navController); + + // Vi bruker en tilpasset listener for å sikre at Hjem og andre punkter alltid fungerer + navigationView.setNavigationItemSelectedListener(item -> { + boolean handled = NavigationUI.onNavDestinationSelected(item, navController); + if (handled || item.getItemId() == R.id.navigation_home) { + if (item.getItemId() == R.id.navigation_home) { + navController.navigate(R.id.navigation_home); + } + drawerLayout.closeDrawer(GravityCompat.START); + } + return handled; + }); navController.addOnDestinationChangedListener((controller, destination, arguments) -> { if (destination.getId() == R.id.navigation_login) { @@ -82,7 +90,6 @@ public class MainActivity extends AppCompatActivity { } else { toolbar.setVisibility(View.VISIBLE); drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); - // Oppdater navn hver gang vi bytter skjerm for å være sikker updateNavHeader(navigationView); } }); @@ -90,9 +97,7 @@ public class MainActivity extends AppCompatActivity { setupTaskReminders(); createNotificationChannel(); - requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {}); - checkNotificationPermission(); checkExactAlarmPermission(); checkLoginState(); @@ -102,7 +107,6 @@ public class MainActivity extends AppCompatActivity { View headerView = navigationView.getHeaderView(0); TextView name = headerView.findViewById(R.id.nav_header_name); TextView email = headerView.findViewById(R.id.nav_header_email); - UserManager user = UserManager.getInstance(); if (user.isLoggedIn()) { name.setText(user.getUserDisplayName()); diff --git a/app/src/main/java/com/kbs/kbsintranett/NewsFullFragment.java b/app/src/main/java/com/kbs/kbsintranett/NewsFullFragment.java index 38e4475..13a7c9e 100644 --- a/app/src/main/java/com/kbs/kbsintranett/NewsFullFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/NewsFullFragment.java @@ -4,7 +4,6 @@ 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; @@ -30,7 +29,7 @@ public class NewsFullFragment extends Fragment { private RecyclerView recyclerViewCategories; private ProgressBar progressBar; private NewsAdapter newsAdapter; - private List allPosts = new ArrayList<>(); // Holder på ALLE postene + private List allPosts = new ArrayList<>(); @Nullable @Override @@ -45,7 +44,6 @@ public class NewsFullFragment extends Fragment { 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())); @@ -54,15 +52,12 @@ public class NewsFullFragment extends Fragment { 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 + "Alle", "Avtaler og invitasjoner", "BHT", "Bilhold", "Cordel", "Ferieavvikling", "Fest og moro", "Generell drift", "HMS", "IT og sikkerhet", "Miljøfyrtårn", "Møtereferat", "SMX" @@ -76,7 +71,6 @@ public class NewsFullFragment extends Fragment { 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) { @@ -87,7 +81,6 @@ public class NewsFullFragment extends Fragment { 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); @@ -110,29 +103,23 @@ public class NewsFullFragment extends Fragment { 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 { diff --git a/app/src/main/java/com/kbs/kbsintranett/TaskAdapter.java b/app/src/main/java/com/kbs/kbsintranett/TaskAdapter.java index b25bcc8..76d3d6b 100644 --- a/app/src/main/java/com/kbs/kbsintranett/TaskAdapter.java +++ b/app/src/main/java/com/kbs/kbsintranett/TaskAdapter.java @@ -8,6 +8,8 @@ import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.cardview.widget.CardView; +import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.RecyclerView; import java.text.SimpleDateFormat; import java.util.Date; @@ -46,18 +48,38 @@ public class TaskAdapter extends RecyclerView.Adapter { SimpleDateFormat sdf = new SimpleDateFormat("dd. MMM", Locale.getDefault()); holder.date.setText("Frist: " + sdf.format(new Date(task.getDueDate()))); + // Vis hvem som tildelte oppgaven hvis det ikke er meg selv (Nytt krav) + if (task.getCreatedByEmail() != null && !task.getCreatedByEmail().equalsIgnoreCase(currentUserEmail)) { + holder.creator.setText("Tildelt av: " + task.getCreatedByName()); + holder.creator.setVisibility(View.VISIBLE); + } else { + holder.creator.setVisibility(View.GONE); + } + boolean myStatus = task.getParticipantStatus(currentUserEmail); holder.checkBox.setChecked(myStatus); + long now = System.currentTimeMillis(); + boolean isOverdue = task.getDueDate() < now && !task.isFullyCompleted() && !myStatus; + + if (isOverdue) { + holder.cardView.setCardBackgroundColor(ContextCompat.getColor(holder.itemView.getContext(), R.color.kbs_soft_light_pink_beige)); + holder.date.setTextColor(ContextCompat.getColor(holder.itemView.getContext(), R.color.kbs_logo_accent_red)); + holder.date.setText("FORFALT: " + sdf.format(new Date(task.getDueDate()))); + } else { + holder.cardView.setCardBackgroundColor(Color.WHITE); + holder.date.setTextColor(ContextCompat.getColor(holder.itemView.getContext(), R.color.kbs_muted_blue_gray)); + } + if (myStatus || task.isFullyCompleted()) { holder.title.setPaintFlags(holder.title.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); holder.title.setTextColor(Color.GRAY); - } else { + holder.cardView.setCardBackgroundColor(Color.parseColor("#F5F5F5")); + } else if (!isOverdue) { holder.title.setPaintFlags(holder.title.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG)); holder.title.setTextColor(Color.BLACK); } - // Vis fremdrift (f.eks 1/3 fullført) int total = task.getAssigneeStatus().size(); int done = 0; for (Boolean b : task.getAssigneeStatus().values()) if (b) done++; @@ -71,14 +93,17 @@ public class TaskAdapter extends RecyclerView.Adapter { public int getItemCount() { return tasks.size(); } static class ViewHolder extends RecyclerView.ViewHolder { - TextView title, date, progress; + TextView title, date, creator, progress; CheckBox checkBox; + CardView cardView; ViewHolder(View v) { super(v); title = v.findViewById(R.id.task_title); date = v.findViewById(R.id.task_date); + creator = v.findViewById(R.id.task_creator); progress = v.findViewById(R.id.task_progress); checkBox = v.findViewById(R.id.task_checkbox); + cardView = (CardView) v; } } } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/TaskDetailsBottomSheet.java b/app/src/main/java/com/kbs/kbsintranett/TaskDetailsBottomSheet.java index e71f366..600fef2 100644 --- a/app/src/main/java/com/kbs/kbsintranett/TaskDetailsBottomSheet.java +++ b/app/src/main/java/com/kbs/kbsintranett/TaskDetailsBottomSheet.java @@ -1,6 +1,5 @@ package com.kbs.kbsintranett; -import android.graphics.Color; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -25,6 +24,7 @@ public class TaskDetailsBottomSheet extends BottomSheetDialogFragment { public interface OnTaskChangeListener { void onTaskChanged(); void onTaskDeleted(TaskItem task); + void onEditRequested(TaskItem task); // NYTT } public TaskDetailsBottomSheet(TaskItem task, OnTaskChangeListener listener) { @@ -42,7 +42,9 @@ public class TaskDetailsBottomSheet extends BottomSheetDialogFragment { TextView desc = v.findViewById(R.id.detail_task_desc); LinearLayout participantsContainer = v.findViewById(R.id.container_participants_status); SwitchMaterial switchNotify = v.findViewById(R.id.switch_notifications); + LinearLayout ownerActions = v.findViewById(R.id.layout_owner_actions); Button btnDelete = v.findViewById(R.id.btn_delete_task); + Button btnEdit = v.findViewById(R.id.btn_edit_task); title.setText(task.getTitle()); SimpleDateFormat sdf = new SimpleDateFormat("EEEE dd. MMMM yyyy", Locale.getDefault()); @@ -53,7 +55,7 @@ public class TaskDetailsBottomSheet extends BottomSheetDialogFragment { desc.setVisibility(View.VISIBLE); } - // List opp alle deltakere og deres status + participantsContainer.removeAllViews(); for (Map.Entry entry : task.getAssigneeStatus().entrySet()) { TextView t = new TextView(getContext()); String status = entry.getValue() ? "✅ Fullført" : "⏳ Pågår"; @@ -68,13 +70,16 @@ public class TaskDetailsBottomSheet extends BottomSheetDialogFragment { if (listener != null) listener.onTaskChanged(); }); - // Kun eier kan slette oppgaven - if (task.getCreatedByEmail().equals(UserManager.getInstance().getUserEmail())) { - btnDelete.setVisibility(View.VISIBLE); + if (task.getCreatedByEmail().equalsIgnoreCase(UserManager.getInstance().getUserEmail())) { + ownerActions.setVisibility(View.VISIBLE); btnDelete.setOnClickListener(view -> { if (listener != null) listener.onTaskDeleted(task); dismiss(); }); + btnEdit.setOnClickListener(view -> { + if (listener != null) listener.onEditRequested(task); + dismiss(); + }); } return v; diff --git a/app/src/main/java/com/kbs/kbsintranett/TaskItem.java b/app/src/main/java/com/kbs/kbsintranett/TaskItem.java index 1c22b70..6b8629a 100644 --- a/app/src/main/java/com/kbs/kbsintranett/TaskItem.java +++ b/app/src/main/java/com/kbs/kbsintranett/TaskItem.java @@ -13,11 +13,10 @@ public class TaskItem implements Serializable { private String createdByEmail; private String createdByName; - // Key: Bruker-epost, Value: Har fullført (true/false) private Map assigneeStatus = new HashMap<>(); private boolean notificationsEnabled = true; - private boolean isFullyCompleted = false; // Satt av eier eller når alle er ferdige + private boolean isFullyCompleted = false; public TaskItem(String title, String description, long dueDate) { this.id = UUID.randomUUID().toString(); @@ -28,15 +27,7 @@ public class TaskItem implements Serializable { this.createdByName = UserManager.getInstance().getUserDisplayName(); } - // Hjelpemetoder - public void addAssignee(String email) { assigneeStatus.put(email, false); } - public void setParticipantStatus(String email, boolean done) { assigneeStatus.put(email, done); } - public boolean getParticipantStatus(String email) { return assigneeStatus.getOrDefault(email, false); } - - public boolean isUserParticipant(String email) { return assigneeStatus.containsKey(email); } - public Map getAssigneeStatus() { return assigneeStatus; } - - // Getters / Setters + // Getters public String getId() { return id; } public String getTitle() { return title; } public String getDescription() { return description; } @@ -44,7 +35,19 @@ public class TaskItem implements Serializable { public String getCreatedByEmail() { return createdByEmail; } public String getCreatedByName() { return createdByName; } public boolean isNotificationsEnabled() { return notificationsEnabled; } - public void setNotificationsEnabled(boolean enabled) { this.notificationsEnabled = enabled; } public boolean isFullyCompleted() { return isFullyCompleted; } - public void setFullyCompleted(boolean fullyCompleted) { isFullyCompleted = fullyCompleted; } + + // Setters (NYTT FOR REDIGERING) + public void setTitle(String title) { this.title = title; } + public void setDescription(String description) { this.description = description; } + public void setDueDate(long dueDate) { this.dueDate = dueDate; } + public void setNotificationsEnabled(boolean enabled) { this.notificationsEnabled = enabled; } + public void setFullyCompleted(boolean fullyCompleted) { this.isFullyCompleted = fullyCompleted; } + + // Participant logikk + public void addAssignee(String email) { assigneeStatus.put(email, false); } + public void setParticipantStatus(String email, boolean done) { assigneeStatus.put(email, done); } + public boolean getParticipantStatus(String email) { return assigneeStatus.getOrDefault(email, false); } + public boolean isUserParticipant(String email) { return assigneeStatus.containsKey(email); } + public Map getAssigneeStatus() { return assigneeStatus; } } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/TasksFragment.java b/app/src/main/java/com/kbs/kbsintranett/TasksFragment.java index 47d6803..093a4bb 100644 --- a/app/src/main/java/com/kbs/kbsintranett/TasksFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/TasksFragment.java @@ -4,21 +4,29 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.tabs.TabLayout; +import com.google.gson.JsonElement; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; public class TasksFragment extends Fragment implements TaskAdapter.OnTaskClickListener { private RecyclerView recyclerView; private TaskAdapter adapter; private TabLayout tabLayout; + private SwipeRefreshLayout swipeRefresh; private List allTasks = new ArrayList<>(); private String myEmail; @@ -35,14 +43,25 @@ public class TasksFragment extends Fragment implements TaskAdapter.OnTaskClickLi myEmail = UserManager.getInstance().getUserEmail(); tabLayout = view.findViewById(R.id.task_tabs); recyclerView = view.findViewById(R.id.recycler_tasks); + swipeRefresh = view.findViewById(R.id.swipe_refresh_tasks); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + setupTabs(); + FloatingActionButton fab = view.findViewById(R.id.fab_add_task); fab.setOnClickListener(v -> { AddTaskBottomSheet dialog = new AddTaskBottomSheet(); - dialog.setOnTaskAddedListener(task -> { - allTasks.add(0, task); - saveAndRefresh(); + dialog.setOnTaskAddedListener(new AddTaskBottomSheet.OnTaskAddedListener() { + @Override + public void onTaskAdded(TaskItem task) { + allTasks.add(0, task); + saveAndSync(); + } + + @Override + public void onTaskUpdated(TaskItem task) { + saveAndSync(); + } }); dialog.show(getChildFragmentManager(), "AddTask"); }); @@ -53,12 +72,41 @@ public class TasksFragment extends Fragment implements TaskAdapter.OnTaskClickLi @Override public void onTabReselected(TabLayout.Tab tab) {} }); - loadTasks(); - } + swipeRefresh.setOnRefreshListener(this::fetchTasksFromServer); - private void loadTasks() { allTasks = CacheManager.getTasks(getContext()); filterAndDisplay(); + fetchTasksFromServer(); + } + + private void setupTabs() { + tabLayout.removeAllTabs(); + tabLayout.addTab(tabLayout.newTab().setText("Mine")); + tabLayout.addTab(tabLayout.newTab().setText("Fullførte")); + tabLayout.addTab(tabLayout.newTab().setText("Tildelt andre")); + if (UserManager.getInstance().isEditorOrAbove()) { + tabLayout.addTab(tabLayout.newTab().setText("Alle")); + } + } + + private void fetchTasksFromServer() { + swipeRefresh.setRefreshing(true); + RetrofitClient.getApiService().getTasks().enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + swipeRefresh.setRefreshing(false); + if (response.isSuccessful() && response.body() != null) { + allTasks = response.body(); + CacheManager.saveTasks(getContext(), allTasks); + filterAndDisplay(); + } + } + @Override + public void onFailure(Call> call, Throwable t) { + swipeRefresh.setRefreshing(false); + Toast.makeText(getContext(), "Kunne ikke synkronisere oppgaver", Toast.LENGTH_SHORT).show(); + } + }); } private void filterAndDisplay() { @@ -66,21 +114,31 @@ public class TasksFragment extends Fragment implements TaskAdapter.OnTaskClickLi int selectedTab = tabLayout.getSelectedTabPosition(); for (TaskItem t : allTasks) { - if (selectedTab == 0) { // MINE (Oppgaver tildelt meg som ikke er fullført av meg ennå) - if (t.isUserParticipant(myEmail) && !t.getParticipantStatus(myEmail) && !t.isFullyCompleted()) { - filtered.add(t); - } - } else if (selectedTab == 1) { // TILDELT ANDRE (Oppgaver jeg har laget, uansett om jeg er med selv) - if (t.getCreatedByEmail().equals(myEmail) && !t.isFullyCompleted()) { - filtered.add(t); - } - } else { // FULLFØRTE (Alt som er arkivert som ferdig) - if (t.isFullyCompleted() || (t.isUserParticipant(myEmail) && t.getParticipantStatus(myEmail))) { - filtered.add(t); - } + boolean isParticipant = t.isUserParticipant(myEmail); + boolean isCreator = t.getCreatedByEmail().equalsIgnoreCase(myEmail); + boolean iHaveDoneIt = t.getParticipantStatus(myEmail); + boolean hasOtherParticipants = false; + for (String email : t.getAssigneeStatus().keySet()) { + if (!email.equalsIgnoreCase(myEmail)) { hasOtherParticipants = true; break; } + } + + switch (selectedTab) { + case 0: // MINE + if (isParticipant && !iHaveDoneIt && !t.isFullyCompleted()) filtered.add(t); + break; + case 1: // FULLFØRTE + if (t.isFullyCompleted() || (isParticipant && iHaveDoneIt)) filtered.add(t); + break; + case 2: // TILDELT ANDRE + if (isCreator && !t.isFullyCompleted() && hasOtherParticipants) filtered.add(t); + break; + case 3: // ALLE + if (!t.isFullyCompleted()) filtered.add(t); + break; } } - + if (selectedTab == 1) Collections.sort(filtered, (t1, t2) -> Long.compare(t2.getDueDate(), t1.getDueDate())); + else Collections.sort(filtered, (t1, t2) -> Long.compare(t1.getDueDate(), t2.getDueDate())); adapter = new TaskAdapter(filtered, this); recyclerView.setAdapter(adapter); } @@ -88,15 +146,21 @@ public class TasksFragment extends Fragment implements TaskAdapter.OnTaskClickLi @Override public void onTaskClick(TaskItem task) { TaskDetailsBottomSheet sheet = new TaskDetailsBottomSheet(task, new TaskDetailsBottomSheet.OnTaskChangeListener() { - @Override - public void onTaskChanged() { - saveAndRefresh(); - } - - @Override - public void onTaskDeleted(TaskItem taskToDelete) { + @Override public void onTaskChanged() { saveAndSync(); } + @Override public void onTaskDeleted(TaskItem taskToDelete) { allTasks.remove(taskToDelete); - saveAndRefresh(); + saveAndSync(); + } + @Override public void onEditRequested(TaskItem taskToEdit) { + AddTaskBottomSheet editDialog = new AddTaskBottomSheet(); + editDialog.setTaskToEdit(taskToEdit); + editDialog.setOnTaskAddedListener(new AddTaskBottomSheet.OnTaskAddedListener() { + @Override public void onTaskAdded(TaskItem task) {} // Ikke i bruk her + @Override public void onTaskUpdated(TaskItem task) { + saveAndSync(); + } + }); + editDialog.show(getChildFragmentManager(), "EditTask"); } }); sheet.show(getChildFragmentManager(), "TaskDetails"); @@ -105,18 +169,22 @@ public class TasksFragment extends Fragment implements TaskAdapter.OnTaskClickLi @Override public void onStatusChanged(TaskItem task, boolean isDone) { task.setParticipantStatus(myEmail, isDone); - - // Logikk: Hvis den som opprettet oppgaven merker den som ferdig, - // regnes hele oppgaven som ferdig for alle (arkiveres). - if (task.getCreatedByEmail().equals(myEmail) && isDone) { + boolean hasOthers = false; + for (String email : task.getAssigneeStatus().keySet()) { + if (!email.equalsIgnoreCase(myEmail)) { hasOthers = true; break; } + } + if (task.getCreatedByEmail().equalsIgnoreCase(myEmail) && isDone && !hasOthers) { task.setFullyCompleted(true); } - - saveAndRefresh(); + saveAndSync(); } - private void saveAndRefresh() { + private void saveAndSync() { CacheManager.saveTasks(getContext(), allTasks); filterAndDisplay(); + RetrofitClient.getApiService().syncTasks(allTasks).enqueue(new Callback() { + @Override public void onResponse(Call call, Response response) {} + @Override public void onFailure(Call call, Throwable t) {} + }); } } \ 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 e561ff0..b005d0b 100644 --- a/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java +++ b/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java @@ -17,20 +17,47 @@ import retrofit2.http.PartMap; import retrofit2.http.Query; public interface WordPressApiService { + + // --- NYHETER --- @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); + @GET("wp-json/wp/v2/posts?per_page=50&_embed") + Call> getAllPosts(); + + // --- AUTENTISERING & ENHET --- @POST("wp-json/kbs/v1/login") Call googleLogin(@Body LoginRequest request); + @POST("wp-json/kbs/v1/device/register") + Call registerDevice(@Body RegisterDeviceRequest request); + + @GET("wp-json/kbs/v1/users") + Call> getUsersList(); + + + // --- KALENDER --- + @GET("wp-json/kbs/v1/calendar/events") + Call> getCalendarEvents(); + + @POST("wp-json/kbs/v1/calendar/create") + Call createCalendarEvent(@Body CreateEventRequest request); + + @POST("wp-json/kbs/v1/calendar/update") + Call updateCalendarEvent(@Body CreateEventRequest request); + + @POST("wp-json/kbs/v1/calendar/delete") + Call deleteCalendarEvent(@Body CreateEventRequest request); + + + // --- SKJEMAER (GRAVITY FORMS) --- @GET("wp-json/kbs/v1/forms") Call> getFormsList(); + @GET("wp-json/kbs/v1/forms/{id}") + Call getForm(@Path("id") int formId); + @Multipart @POST("wp-json/gf/v2/forms/{id}/submissions") Call submitMultipartForm( @@ -39,16 +66,6 @@ public interface WordPressApiService { @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, @@ -59,9 +76,8 @@ public interface WordPressApiService { @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(); + // --- HÅNDBOK --- @GET("wp-json/kbs/v1/handbook") Call> getHandbookItems(); @@ -71,12 +87,11 @@ public interface WordPressApiService { @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); + // --- OPPGAVER (SYNKRONISERING) --- + @GET("wp-json/kbs/v1/tasks") + Call> getTasks(); - @POST("wp-json/kbs/v1/device/register") - Call registerDevice(@Body RegisterDeviceRequest request); + @POST("wp-json/kbs/v1/tasks/sync") + Call syncTasks(@Body List tasks); } \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_add_task.xml b/app/src/main/res/layout/bottom_sheet_add_task.xml index f32543b..a7526df 100644 --- a/app/src/main/res/layout/bottom_sheet_add_task.xml +++ b/app/src/main/res/layout/bottom_sheet_add_task.xml @@ -1,84 +1,92 @@ - - - - - - - + android:fillViewport="true"> -