Før sentralisert brukerliste

This commit is contained in:
ErolHaagenrud 2026-01-08 11:32:48 +01:00
parent 974c41ca4d
commit ee198150c2
18 changed files with 684 additions and 337 deletions

View file

@ -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<User> allUsersFromApi = new ArrayList<>();
private List<User> filteredUsers = new ArrayList<>();
private List<User> selectedUsers = new ArrayList<>();
private TaskItem taskToEdit = null; // NYTT: Hold 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 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, vi mappe eksisterende deltakere til selectedUsers listen
if (taskToEdit != null) {
selectedUsers.clear();
Map<String, Boolean> 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<String> 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 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<String, Boolean> 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 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 legger til i TaskItem
// Se punkt 5 nedenfor for oppdatert TaskItem.java
t.setTitle(title);
t.setDescription(desc);
t.setDueDate(date);
}
}

View file

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

View file

@ -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<RecyclerView.ViewHolder> {
@ -21,17 +28,21 @@ public class HomeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
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<Object> 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<Object> items, OnHomeClickListener listener) {
@ -46,6 +57,8 @@ public class HomeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
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<RecyclerView.ViewHolder> {
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<RecyclerView.ViewHolder> {
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<RecyclerView.ViewHolder> {
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<RecyclerView.ViewHolder> {
}
// --- 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<RecyclerView.ViewHolder> {
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<RecyclerView.ViewHolder> {
}
}
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; }
}
}

View file

@ -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<CalendarEvent> currentEvents = new ArrayList<>();
private List<WpPost> currentNews = new ArrayList<>();
private List<TaskItem> currentTasks = new ArrayList<>();
private int activeNetworkCalls = 0;
private ActivityResultLauncher<String> 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<List<CalendarEvent>> call, Throwable t) {
List<CalendarEvent> cached = CacheManager.getCachedCalendarEvents(getContext());
@ -137,7 +134,6 @@ public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickLis
formatNewsDates(currentNews);
checkLoadingComplete();
}
@Override
public void onFailure(Call<List<WpPost>> 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<List<TaskItem>>() {
@Override
public void onResponse(Call<List<TaskItem>> call, Response<List<TaskItem>> 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<List<TaskItem>> call, Throwable t) {
currentTasks = CacheManager.getTasks(getContext());
checkLoadingComplete();
}
});
}
private void formatNewsDates(List<WpPost> 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<Object> 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<TaskItem> 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<JsonElement>() {
@Override public void onResponse(Call<JsonElement> call, Response<JsonElement> response) {}
@Override public void onFailure(Call<JsonElement> call, Throwable t) {}
});
}
}

View file

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

View file

@ -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<WpPost> allPosts = new ArrayList<>(); // Holder ALLE postene
private List<WpPost> 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<String> 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 vi paginere)
RetrofitClient.getApiService().getAllPosts().enqueue(new Callback<List<WpPost>>() {
@Override
public void onResponse(Call<List<WpPost>> call, Response<List<WpPost>> 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<WpPost> 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<WpPost> 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 {

View file

@ -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<TaskAdapter.ViewHolder> {
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<TaskAdapter.ViewHolder> {
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;
}
}
}

View file

@ -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<String, Boolean> 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;

View file

@ -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<String, Boolean> 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<String, Boolean> 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<String, Boolean> getAssigneeStatus() { return assigneeStatus; }
}

View file

@ -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<TaskItem> 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<List<TaskItem>>() {
@Override
public void onResponse(Call<List<TaskItem>> call, Response<List<TaskItem>> response) {
swipeRefresh.setRefreshing(false);
if (response.isSuccessful() && response.body() != null) {
allTasks = response.body();
CacheManager.saveTasks(getContext(), allTasks);
filterAndDisplay();
}
}
@Override
public void onFailure(Call<List<TaskItem>> 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<JsonElement>() {
@Override public void onResponse(Call<JsonElement> call, Response<JsonElement> response) {}
@Override public void onFailure(Call<JsonElement> call, Throwable t) {}
});
}
}

View file

@ -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<List<WpPost>> getPosts();
@GET("wp-json/kbs/v1/forms/{id}")
Call<GravityForm> getForm(@Path("id") int formId);
@POST("wp-json/gf/v2/forms/{id}/submissions")
Call<JsonElement> submitForm(@Path("id") int formId, @Body FormSubmission submission);
@GET("wp-json/wp/v2/posts?per_page=50&_embed")
Call<List<WpPost>> getAllPosts();
// --- AUTENTISERING & ENHET ---
@POST("wp-json/kbs/v1/login")
Call<LoginResponse> googleLogin(@Body LoginRequest request);
@POST("wp-json/kbs/v1/device/register")
Call<JsonElement> registerDevice(@Body RegisterDeviceRequest request);
@GET("wp-json/kbs/v1/users")
Call<List<User>> getUsersList();
// --- KALENDER ---
@GET("wp-json/kbs/v1/calendar/events")
Call<List<CalendarEvent>> getCalendarEvents();
@POST("wp-json/kbs/v1/calendar/create")
Call<JsonElement> createCalendarEvent(@Body CreateEventRequest request);
@POST("wp-json/kbs/v1/calendar/update")
Call<JsonElement> updateCalendarEvent(@Body CreateEventRequest request);
@POST("wp-json/kbs/v1/calendar/delete")
Call<JsonElement> deleteCalendarEvent(@Body CreateEventRequest request);
// --- SKJEMAER (GRAVITY FORMS) ---
@GET("wp-json/kbs/v1/forms")
Call<List<GravityForm>> getFormsList();
@GET("wp-json/kbs/v1/forms/{id}")
Call<GravityForm> getForm(@Path("id") int formId);
@Multipart
@POST("wp-json/gf/v2/forms/{id}/submissions")
Call<JsonElement> submitMultipartForm(
@ -39,16 +66,6 @@ public interface WordPressApiService {
@Part List<MultipartBody.Part> files
);
@GET("wp-json/kbs/v1/calendar/events")
Call<List<CalendarEvent>> getCalendarEvents();
@POST("wp-json/kbs/v1/calendar/create")
Call<JsonElement> createCalendarEvent(@Body CreateEventRequest request);
// NYTT ENDEPUNKT
@GET("wp-json/kbs/v1/users")
Call<List<User>> getUsersList();
@GET("wp-json/gf/v2/entries")
Call<GravityEntryResponse> getEntries(
@Query("form_ids") int formId,
@ -59,9 +76,8 @@ public interface WordPressApiService {
@GET("wp-json/gf/v2/entries/{entry_id}")
Call<JsonElement> getSingleEntry(@Path("entry_id") String entryId);
@GET("wp-json/wp/v2/posts?per_page=50&_embed")
Call<List<WpPost>> getAllPosts();
// --- HÅNDBOK ---
@GET("wp-json/kbs/v1/handbook")
Call<List<HandbookItem>> getHandbookItems();
@ -71,12 +87,11 @@ public interface WordPressApiService {
@GET("wp-json/kbs/v1/lookup-id")
Call<JsonObject> lookupPageId(@Query("url") String url);
@POST("wp-json/kbs/v1/calendar/update")
Call<JsonElement> updateCalendarEvent(@Body CreateEventRequest request);
@POST("wp-json/kbs/v1/calendar/delete")
Call<JsonElement> deleteCalendarEvent(@Body CreateEventRequest request);
// --- OPPGAVER (SYNKRONISERING) ---
@GET("wp-json/kbs/v1/tasks")
Call<List<TaskItem>> getTasks();
@POST("wp-json/kbs/v1/device/register")
Call<JsonElement> registerDevice(@Body RegisterDeviceRequest request);
@POST("wp-json/kbs/v1/tasks/sync")
Call<JsonElement> syncTasks(@Body List<TaskItem> tasks);
}

View file

@ -1,84 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp"
android:background="@color/white">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ny Oppgave"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/black"
android:layout_marginBottom="16dp"/>
<EditText
android:id="@+id/et_task_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Hva skal gjøres?"
android:inputType="textCapSentences"
android:layout_marginBottom="8dp"/>
<EditText
android:id="@+id/et_task_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Beskrivelse (valgfritt)"
android:inputType="textMultiLine"
android:minLines="2"
android:gravity="top"
android:layout_marginBottom="16dp"/>
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="12dp">
<Button
android:id="@+id/btn_task_date"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sett frist" />
android:orientation="vertical"
android:padding="24dp"
android:background="@color/white">
<TextView
android:id="@+id/txt_date_preview"
android:id="@+id/txt_sheet_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Frist: -"
android:layout_marginStart="8dp"/>
</LinearLayout>
android:text="Ny Oppgave"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/black"
android:layout_marginBottom="16dp"/>
<EditText
android:id="@+id/et_task_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Hva skal gjøres?"
android:inputType="textCapSentences"
android:layout_marginBottom="8dp"/>
<EditText
android:id="@+id/et_task_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Beskrivelse (valgfritt)"
android:inputType="textMultiLine"
android:minLines="2"
android:gravity="top"
android:layout_marginBottom="16dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="12dp">
<Button
android:id="@+id/btn_task_date"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sett frist" />
<TextView
android:id="@+id/txt_date_preview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Frist: -"
android:layout_marginStart="8dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="24dp">
<Button
android:id="@+id/btn_task_users"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Velg deltakere" />
<TextView
android:id="@+id/txt_users_preview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kun meg"
android:layout_marginStart="8dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="24dp">
<Button
android:id="@+id/btn_task_users"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:id="@+id/btn_save_task"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Velg deltakere" />
<TextView
android:id="@+id/txt_users_preview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kun meg"
android:layout_marginStart="8dp"/>
android:text="Lagre Oppgave"
android:backgroundTint="@color/kbs_logo_blue"
android:textColor="@color/white"
android:layout_marginBottom="16dp"/>
</LinearLayout>
<Button
android:id="@+id/btn_save_task"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Lagre Oppgave"
android:backgroundTint="@color/kbs_logo_blue"
android:textColor="@color/white"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View file

@ -65,13 +65,32 @@
android:checked="true"
android:layout_marginBottom="24dp"/>
<Button
android:id="@+id/btn_delete_task"
<LinearLayout
android:id="@+id/layout_owner_actions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Slett oppgave"
android:backgroundTint="@color/kbs_logo_accent_red"
android:textColor="@color/white"
android:visibility="gone"/>
android:orientation="horizontal"
android:visibility="gone">
<Button
android:id="@+id/btn_delete_task"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Slett"
android:backgroundTint="@color/kbs_logo_accent_red"
android:textColor="@color/white"
android:layout_marginEnd="8dp"/>
<Button
android:id="@+id/btn_edit_task"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Endre"
android:backgroundTint="@color/kbs_logo_blue"
android:textColor="@color/white"
android:layout_marginStart="8dp"/>
</LinearLayout>
</LinearLayout>

View file

@ -1,44 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/white">
<!-- Knappen ligger nå helt øverst i fragmentet (under den globale toolbaren) -->
<Button
android:id="@+id/btn_add_calendar_event"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_full_calendar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="80dp" />
<ProgressBar
android:id="@+id/loading_full_calendar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+ Ny Kalenderhendelse"
android:backgroundTint="@color/kbs_logo_blue"
android:textColor="@color/white"
android:layout_margin="16dp"
android:layout_gravity="center"/>
<TextView
android:id="@+id/empty_view_calendar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ingen hendelser funnet"
android:layout_gravity="center"
android:visibility="gone"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- NY FAB: Flytende knapp nederst til høyre -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_add_calendar_event"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="24dp"
android:src="@android:drawable/ic_input_add"
app:backgroundTint="@color/kbs_logo_blue"
app:tint="@color/white"
android:visibility="gone"
android:contentDescription="Ny hendelse" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_full_calendar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false" />
<ProgressBar
android:id="@+id/loading_full_calendar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<TextView
android:id="@+id/empty_view_calendar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ingen hendelser funnet"
android:layout_gravity="center"
android:visibility="gone"/>
</FrameLayout>
</LinearLayout>
</FrameLayout>

View file

@ -5,31 +5,7 @@
android:orientation="vertical"
android:background="@color/kbs_very_light_blue">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="@android:color/white"
android:elevation="4dp">
<ImageView
android:id="@+id/btn_back_news"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@android:drawable/ic_menu_revert"
android:layout_centerVertical="true"
android:contentDescription="Tilbake"
android:background="?attr/selectableItemBackgroundBorderless"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Siste nytt"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@android:color/black"
android:layout_centerInParent="true"/>
</RelativeLayout>
<!-- Redundant header er fjernet. Kun kategorilisten og nyhetslisten er igjen. -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_categories"

View file

@ -13,21 +13,7 @@
android:background="@color/white"
app:tabSelectedTextColor="@color/kbs_logo_blue"
app:tabIndicatorColor="@color/kbs_logo_blue">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Mine" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tildelt andre" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fullførte" />
<!-- Faner legges til programmatisk i TasksFragment.java -->
</com.google.android.material.tabs.TabLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Ingen oppgaver tildelt"
android:textColor="@color/kbs_muted_blue_gray"
android:textSize="14sp"
android:textStyle="italic"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp" />

View file

@ -44,6 +44,15 @@
android:textSize="12sp"
android:textColor="@color/kbs_muted_blue_gray" />
<TextView
android:id="@+id/task_creator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Tildelt av: -"
android:textSize="11sp"
android:textColor="@color/kbs_logo_blue"
android:visibility="gone" />
<TextView
android:id="@+id/task_progress"
android:layout_width="match_parent"
@ -51,7 +60,7 @@
android:text="Fremdrift: 0/0"
android:textSize="11sp"
android:textStyle="italic"
android:textColor="@color/kbs_logo_blue"
android:textColor="@color/kbs_muted_blue_gray"
android:layout_marginTop="2dp"/>
</LinearLayout>