diff --git a/app/src/main/java/com/kbs/kbsintranett/AddTaskBottomSheet.java b/app/src/main/java/com/kbs/kbsintranett/AddTaskBottomSheet.java new file mode 100644 index 0000000..ee80466 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/AddTaskBottomSheet.java @@ -0,0 +1,187 @@ +package com.kbs.kbsintranett; + +import android.app.AlertDialog; +import android.app.DatePickerDialog; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +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.BottomSheetDialogFragment; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Locale; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class AddTaskBottomSheet extends BottomSheetDialogFragment { + + private EditText etTitle, etDesc; + private Button btnDate, btnUsers, btnSave; + private TextView txtDatePreview, txtUsersPreview; + + private Calendar dueDate = Calendar.getInstance(); + private List allUsersFromApi = new ArrayList<>(); + private List filteredUsers = new ArrayList<>(); + private List selectedUsers = new ArrayList<>(); + + public interface OnTaskAddedListener { + void onTaskAdded(TaskItem task); + } + + private OnTaskAddedListener listener; + + public void setOnTaskAddedListener(OnTaskAddedListener listener) { + this.listener = listener; + } + + @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); + + etTitle = v.findViewById(R.id.et_task_title); + etDesc = v.findViewById(R.id.et_task_desc); + btnDate = v.findViewById(R.id.btn_task_date); + btnUsers = v.findViewById(R.id.btn_task_users); + btnSave = v.findViewById(R.id.btn_save_task); + txtDatePreview = v.findViewById(R.id.txt_date_preview); + txtUsersPreview = v.findViewById(R.id.txt_users_preview); + + dueDate.add(Calendar.DAY_OF_MONTH, 1); + updateDatePreview(); + + btnDate.setOnClickListener(view -> { + new DatePickerDialog(getContext(), (d, y, m, day) -> { + dueDate.set(y, m, day); + updateDatePreview(); + }, dueDate.get(Calendar.YEAR), dueDate.get(Calendar.MONTH), dueDate.get(Calendar.DAY_OF_MONTH)).show(); + }); + + btnUsers.setOnClickListener(view -> showUserSelectionDialog()); + btnSave.setOnClickListener(view -> saveTask()); + + fetchUsers(); + + return v; + } + + private void fetchUsers() { + RetrofitClient.getApiService().getUsersList().enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + allUsersFromApi = response.body(); + filterUsersByHierarchy(); + } + } + @Override + public void onFailure(Call> call, Throwable t) {} + }); + } + + 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<>(); + for (User u : allUsersFromApi) { + if (u.getEmail().equalsIgnoreCase(me.getUserEmail())) { + for (String r : u.getRoles()) myRoles.add(r.toLowerCase()); + break; + } + } + + // 3. Filtrer logikk (Identisk med CreateEventFragment) + 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")) || + (r.equals("administrasjonen") && myRoles.contains("administrasjonen")) || + (r.equals("kbs_alle") && myRoles.contains("kbs_alle"))) { + hasAccess = true; + break; + } + } + + if (hasAccess && !filteredUsers.contains(u)) { + filteredUsers.add(u); + } + } + } + + private void showUserSelectionDialog() { + if (filteredUsers.isEmpty()) { + 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)); + } + + 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"); + }) + .show(); + } + + private void updateDatePreview() { + SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()); + txtDatePreview.setText("Frist: " + sdf.format(dueDate.getTime())); + } + + private void saveTask() { + String title = etTitle.getText().toString().trim(); + if (title.isEmpty()) { + etTitle.setError("Mangler tittel"); + 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 (listener != null) listener.onTaskAdded(task); + dismiss(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/CacheManager.java b/app/src/main/java/com/kbs/kbsintranett/CacheManager.java index de254a6..2ebfa21 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CacheManager.java +++ b/app/src/main/java/com/kbs/kbsintranett/CacheManager.java @@ -92,4 +92,15 @@ public class CacheManager { return null; } } + private static final String FILE_TASKS = "cache_tasks.json"; + + public static void saveTasks(Context context, List tasks) { + saveList(context, FILE_TASKS, tasks); + } + + public static List getTasks(Context context) { + Type type = new TypeToken>() {}.getType(); + List list = loadList(context, FILE_TASKS, type); + return list != null ? list : new ArrayList<>(); + } } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/CalendarAdapter.java b/app/src/main/java/com/kbs/kbsintranett/CalendarAdapter.java index 950f4a9..fbd9c28 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CalendarAdapter.java +++ b/app/src/main/java/com/kbs/kbsintranett/CalendarAdapter.java @@ -11,73 +11,92 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import java.util.List; -public class CalendarAdapter extends RecyclerView.Adapter { +public class CalendarAdapter extends RecyclerView.Adapter { - private List events; + public static final int TYPE_EVENT = 0; + public static final int TYPE_YEAR_HEADER = 1; + + private List items; private final OnItemClickListener listener; - // Farge for individuelle/private hendelser (Deep Purple) - private static final String PRIVATE_EVENT_COLOR = "#673AB7"; - public interface OnItemClickListener { void onItemClick(CalendarEvent event); } - public CalendarAdapter(List events, OnItemClickListener listener) { - this.events = events; + public CalendarAdapter(List items, OnItemClickListener listener) { + this.items = items; this.listener = listener; } + @Override + public int getItemViewType(int position) { + return (items.get(position) instanceof String) ? TYPE_YEAR_HEADER : TYPE_EVENT; + } + @NonNull @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_calendar, parent, false); - return new ViewHolder(view); + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + if (viewType == TYPE_YEAR_HEADER) { + return new YearViewHolder(inflater.inflate(R.layout.item_calendar_year_header, parent, false)); + } else { + return new EventViewHolder(inflater.inflate(R.layout.item_calendar, parent, false)); + } } @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - CalendarEvent event = events.get(position); - holder.day.setText(event.getDay()); - holder.month.setText(event.getMonth()); - holder.time.setText(event.getTime()); - holder.title.setText(event.getTitle()); + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + Object item = items.get(position); - // NYTT: Sjekk om hendelsen er "privat" (har deltakere) - boolean isPrivate = event.getDescription() != null && event.getDescription().contains("#deltakere:"); - - try { - int color; - if (isPrivate) { - // Bruk privat farge - color = Color.parseColor(PRIVATE_EVENT_COLOR); - } else { - // Bruk kalenderens standardfarge - color = Color.parseColor(event.getCalendarColor()); - } - holder.dateBox.setBackgroundTintList(ColorStateList.valueOf(color)); - } catch (Exception e) { - // Fallback til standard blå hvis fargekoden er ugyldig - holder.dateBox.setBackgroundTintList(ColorStateList.valueOf(Color.parseColor("#0069B3"))); + if (holder instanceof YearViewHolder) { + ((YearViewHolder) holder).yearText.setText((String) item); } + else if (holder instanceof EventViewHolder) { + CalendarEvent event = (CalendarEvent) item; + EventViewHolder vh = (EventViewHolder) holder; - holder.itemView.setOnClickListener(v -> { - if (listener != null) { - listener.onItemClick(event); + vh.day.setText(event.getDay()); + vh.month.setText(event.getMonth()); + vh.time.setText(event.getTime()); + vh.title.setText(event.getTitle()); + + // --- ÅRSTALL LOGIKK: BAKGRUNNSFARGE --- + // Vi henter årstall fra datoen (yyyy-MM-dd) + String year = "2025"; + if (event.getRawDate() != null && event.getRawDate().length() >= 4) { + year = event.getRawDate().substring(0, 4); } - }); + + // Alternerende bakgrunn basert på år (partall vs oddetall) + int yearInt = Integer.parseInt(year); + if (yearInt % 2 == 0) { + vh.itemView.setBackgroundColor(Color.parseColor("#F5F7FA")); // KBS Very Light Blue + } else { + vh.itemView.setBackgroundColor(Color.WHITE); + } + + // Privat-markering og farge på datoboks + boolean isPrivate = event.getDescription() != null && event.getDescription().contains("#deltakere:"); + try { + int color = Color.parseColor(isPrivate ? "#673AB7" : event.getCalendarColor()); + vh.dateBox.setBackgroundTintList(ColorStateList.valueOf(color)); + } catch (Exception e) { + vh.dateBox.setBackgroundTintList(ColorStateList.valueOf(Color.parseColor("#0069B3"))); + } + + vh.itemView.setOnClickListener(v -> listener.onItemClick(event)); + } } @Override public int getItemCount() { - return events.size(); + return items.size(); } - public static class ViewHolder extends RecyclerView.ViewHolder { + static class EventViewHolder extends RecyclerView.ViewHolder { TextView day, month, title, time; LinearLayout dateBox; - - public ViewHolder(View view) { + EventViewHolder(View view) { super(view); day = view.findViewById(R.id.cal_day); month = view.findViewById(R.id.cal_month); @@ -86,4 +105,12 @@ public class CalendarAdapter extends RecyclerView.Adapter Navigation.findNavController(view).navigateUp()); + + // 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)); + } } @Override @@ -58,9 +62,7 @@ public class CalendarFullFragment extends Fragment { private void fetchAllEvents() { progressBar.setVisibility(View.VISIBLE); - new Thread(() -> { - // HER ER ENDRINGEN: isPreview = false (Hent alt) List deviceEvents = CalendarManager.getDeviceEvents(getContext(), false); new Handler(Looper.getMainLooper()).post(() -> fetchApiEvents(deviceEvents)); }).start(); @@ -76,65 +78,53 @@ public class CalendarFullFragment extends Fragment { List apiEvents = new ArrayList<>(); if (response.isSuccessful() && response.body() != null) { apiEvents = response.body(); - for (CalendarEvent e : apiEvents) { - CalendarManager.formatEventForUI(e); - } + for (CalendarEvent e : apiEvents) CalendarManager.formatEventForUI(e); } List allEvents = CalendarManager.mergeAndSort(apiEvents, deviceEvents); - - if (allEvents.isEmpty()) { - emptyView.setVisibility(View.VISIBLE); - recyclerView.setVisibility(View.GONE); - } else { - emptyView.setVisibility(View.GONE); - recyclerView.setVisibility(View.VISIBLE); - - CalendarAdapter adapter = new CalendarAdapter(allEvents, event -> { - CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event); - sheet.setOnEventChangeListener(CalendarFullFragment.this::fetchAllEvents); - sheet.show(getParentFragmentManager(), "CalendarDetails"); - }); - recyclerView.setAdapter(adapter); - - scrollToToday(allEvents); - } + displaySortedList(allEvents); } @Override public void onFailure(Call> call, Throwable t) { if (!isAdded()) return; progressBar.setVisibility(View.GONE); - - if (!deviceEvents.isEmpty()) { - CalendarAdapter adapter = new CalendarAdapter(deviceEvents, event -> { - CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event); - sheet.show(getParentFragmentManager(), "CalendarDetails"); - }); - recyclerView.setAdapter(adapter); - scrollToToday(deviceEvents); - } else { - emptyView.setText("Ingen hendelser funnet."); - emptyView.setVisibility(View.VISIBLE); - } + displaySortedList(deviceEvents); } }); } - private void scrollToToday(List events) { - String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date()); - int scrollIndex = 0; + private void displaySortedList(List events) { + if (events.isEmpty()) { + emptyView.setVisibility(View.VISIBLE); + recyclerView.setVisibility(View.GONE); + return; + } - for (int i = 0; i < events.size(); i++) { - String raw = events.get(i).getRawDate(); - if (raw != null && raw.compareTo(today) >= 0) { - scrollIndex = i; - break; + emptyView.setVisibility(View.GONE); + recyclerView.setVisibility(View.VISIBLE); + + List itemsWithHeaders = new ArrayList<>(); + String currentYear = ""; + + for (CalendarEvent e : events) { + String year = "Ukjent år"; + if (e.getRawDate() != null && e.getRawDate().length() >= 4) { + year = e.getRawDate().substring(0, 4); } + + if (!year.equals(currentYear)) { + itemsWithHeaders.add(year); + currentYear = year; + } + itemsWithHeaders.add(e); } - if (scrollIndex > 0) { - layoutManager.scrollToPositionWithOffset(scrollIndex, 0); - } + CalendarAdapter adapter = new CalendarAdapter(itemsWithHeaders, event -> { + CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event); + sheet.setOnEventChangeListener(CalendarFullFragment.this::fetchAllEvents); + sheet.show(getParentFragmentManager(), "CalendarDetails"); + }); + recyclerView.setAdapter(adapter); } } \ 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 4f574d0..e91f454 100644 --- a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java @@ -9,7 +9,6 @@ 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; @@ -25,7 +24,6 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; -import java.util.TimeZone; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -173,12 +171,7 @@ public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickLis private void buildAndDisplayList() { List items = new ArrayList<>(); - // 1. Create Event Knapp (hvis tilgang) - if (!UserManager.getInstance().getWriteableCalendars().isEmpty()) { - items.add(new HomeAdapter.CreateButtonItem()); - } - - // 2. Kalender Seksjon (Redusert til 3 hendelser som avtalt) + // 1. Kalender Seksjon (Knappen "Ny hendelse" er fjernet herfra) items.add(new HomeAdapter.SectionTitleItem("Kommende hendelser", true)); String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date()); int count = 0; @@ -190,7 +183,7 @@ public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickLis if (count >= 3) break; } - // 3. Nyheter Seksjon + // 2. Nyheter Seksjon items.add(new HomeAdapter.SectionTitleItem("Siste nytt", false)); items.addAll(currentNews); @@ -198,33 +191,24 @@ public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickLis recyclerView.setAdapter(adapter); } - // --- KLIKKHÅNDTERING --- - - @Override public void onProfileClick() { - Navigation.findNavController(getView()).navigate(R.id.navigation_profile); - } - + @Override public void onProfileClick() {} @Override public void onCreateEventClick() { - Navigation.findNavController(getView()).navigate(R.id.action_home_to_create_event); + Navigation.findNavController(getView()).navigate(R.id.navigation_create_event); } - @Override public void onViewAllCalendarClick() { - Navigation.findNavController(getView()).navigate(R.id.action_home_to_calendarFull); + Navigation.findNavController(getView()).navigate(R.id.navigation_calendar_full); } - @Override public void onViewAllNewsClick() { - Navigation.findNavController(getView()).navigate(R.id.action_home_to_newsFull); + Navigation.findNavController(getView()).navigate(R.id.navigation_news_full); } - @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.action_home_to_newsDetail, bundle); + Navigation.findNavController(getView()).navigate(R.id.navigation_news_detail, bundle); } } \ 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 8d173b1..a63fb42 100644 --- a/app/src/main/java/com/kbs/kbsintranett/MainActivity.java +++ b/app/src/main/java/com/kbs/kbsintranett/MainActivity.java @@ -26,6 +26,9 @@ import androidx.navigation.NavController; import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.ui.AppBarConfiguration; import androidx.navigation.ui.NavigationUI; +import androidx.work.ExistingPeriodicWorkPolicy; +import androidx.work.PeriodicWorkRequest; +import androidx.work.WorkManager; import com.google.android.gms.auth.api.signin.GoogleSignIn; import com.google.android.gms.auth.api.signin.GoogleSignInAccount; @@ -33,15 +36,17 @@ import com.google.android.gms.auth.api.signin.GoogleSignInClient; import com.google.android.gms.auth.api.signin.GoogleSignInOptions; import com.google.android.material.navigation.NavigationView; +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 static final String TAG = "MainActivity"; private NavController navController; private DrawerLayout drawerLayout; private AppBarConfiguration appBarConfiguration; - private ActivityResultLauncher requestPermissionLauncher; @Override @@ -77,13 +82,17 @@ 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); } }); } - updateNavHeader(navigationView); + setupTaskReminders(); createNotificationChannel(); + requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {}); + checkNotificationPermission(); checkExactAlarmPermission(); checkLoginState(); @@ -94,13 +103,18 @@ public class MainActivity extends AppCompatActivity { TextView name = headerView.findViewById(R.id.nav_header_name); TextView email = headerView.findViewById(R.id.nav_header_email); - new android.os.Handler().postDelayed(() -> { - UserManager user = UserManager.getInstance(); - if (user.isLoggedIn()) { - name.setText(user.getUserDisplayName()); - email.setText(user.getUserEmail()); - } - }, 2000); + UserManager user = UserManager.getInstance(); + if (user.isLoggedIn()) { + name.setText(user.getUserDisplayName()); + email.setText(user.getUserEmail()); + } + } + + private void setupTaskReminders() { + PeriodicWorkRequest taskCheck = new PeriodicWorkRequest.Builder( + TaskReminderWorker.class, 2, TimeUnit.DAYS).build(); + WorkManager.getInstance(this).enqueueUniquePeriodicWork( + "TaskReminder", ExistingPeriodicWorkPolicy.KEEP, taskCheck); } @Override @@ -128,8 +142,8 @@ public class MainActivity extends AppCompatActivity { .requestIdToken(GOOGLE_WEB_CLIENT_ID).requestEmail().build(); GoogleSignInClient client = GoogleSignIn.getClient(this, gso); client.silentSignIn().addOnSuccessListener(account -> { - AuthRepository.loginToWordPress(account.getIdToken(), account.getDisplayName(), account.getEmail(), - (account.getPhotoUrl() != null ? account.getPhotoUrl().toString() : null), + String photoUrl = (account.getPhotoUrl() != null) ? account.getPhotoUrl().toString() : null; + AuthRepository.loginToWordPress(account.getIdToken(), account.getDisplayName(), account.getEmail(), photoUrl, new AuthRepository.AuthCallback() { @Override public void onSuccess(String role) { if (navController.getCurrentDestination().getId() == R.id.navigation_login) { @@ -142,22 +156,16 @@ public class MainActivity extends AppCompatActivity { } private void navigateToLogin() { - if (navController.getCurrentDestination().getId() != R.id.navigation_login) { + if (navController != null && navController.getCurrentDestination() != null && + navController.getCurrentDestination().getId() != R.id.navigation_login) { navController.navigate(R.id.navigation_login); } } private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - CharSequence name = "KBS Kalendervarsler"; - String description = "Varsler for kalenderhendelser"; - int importance = NotificationManager.IMPORTANCE_HIGH; - NotificationChannel channel = new NotificationChannel("kbs_calendar_channel", name, importance); - channel.setDescription(description); - NotificationManager notificationManager = getSystemService(NotificationManager.class); - if (notificationManager != null) { - notificationManager.createNotificationChannel(channel); - } + NotificationChannel channel = new NotificationChannel("kbs_calendar_channel", "Varsler", NotificationManager.IMPORTANCE_HIGH); + getSystemService(NotificationManager.class).createNotificationChannel(channel); } } diff --git a/app/src/main/java/com/kbs/kbsintranett/NotificationHelper.java b/app/src/main/java/com/kbs/kbsintranett/NotificationHelper.java new file mode 100644 index 0000000..250bb8f --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/NotificationHelper.java @@ -0,0 +1,51 @@ +package com.kbs.kbsintranett; + +import android.Manifest; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import androidx.core.app.ActivityCompat; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; +import androidx.core.content.ContextCompat; + +public class NotificationHelper { + + private static final String CHANNEL_ID = "kbs_calendar_channel"; + + /** + * En enkel metode for å vise et standard KBS-varsel. + */ + public static void showNotification(Context context, String title, String message) { + // Sjekk tillatelse for Android 13+ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + return; + } + } + + // Intent for å åpne appen når man trykker på varselet + Intent intent = new Intent(context, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + PendingIntent pendingIntent = PendingIntent.getActivity( + context, + (int) System.currentTimeMillis(), + intent, + PendingIntent.FLAG_IMMUTABLE + ); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_stat_kbs) // Bruker samme ikon som kalender + .setColor(ContextCompat.getColor(context, R.color.kbs_logo_blue)) + .setContentTitle(title) + .setContentText(message) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setContentIntent(pendingIntent) + .setAutoCancel(true); + + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); + notificationManager.notify((int) System.currentTimeMillis(), builder.build()); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/TaskAdapter.java b/app/src/main/java/com/kbs/kbsintranett/TaskAdapter.java new file mode 100644 index 0000000..b25bcc8 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/TaskAdapter.java @@ -0,0 +1,84 @@ +package com.kbs.kbsintranett; + +import android.graphics.Color; +import android.graphics.Paint; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +public class TaskAdapter extends RecyclerView.Adapter { + + private List tasks; + private OnTaskClickListener listener; + private String currentUserEmail; + + public interface OnTaskClickListener { + void onTaskClick(TaskItem task); + void onStatusChanged(TaskItem task, boolean isDone); + } + + public TaskAdapter(List tasks, OnTaskClickListener listener) { + this.tasks = tasks; + this.listener = listener; + this.currentUserEmail = UserManager.getInstance().getUserEmail(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_task, parent, false); + return new ViewHolder(v); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + TaskItem task = tasks.get(position); + holder.title.setText(task.getTitle()); + + SimpleDateFormat sdf = new SimpleDateFormat("dd. MMM", Locale.getDefault()); + holder.date.setText("Frist: " + sdf.format(new Date(task.getDueDate()))); + + boolean myStatus = task.getParticipantStatus(currentUserEmail); + holder.checkBox.setChecked(myStatus); + + if (myStatus || task.isFullyCompleted()) { + holder.title.setPaintFlags(holder.title.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + holder.title.setTextColor(Color.GRAY); + } else { + 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++; + holder.progress.setText("Fremdrift: " + done + "/" + total); + + holder.checkBox.setOnClickListener(v -> listener.onStatusChanged(task, holder.checkBox.isChecked())); + holder.itemView.setOnClickListener(v -> listener.onTaskClick(task)); + } + + @Override + public int getItemCount() { return tasks.size(); } + + static class ViewHolder extends RecyclerView.ViewHolder { + TextView title, date, progress; + CheckBox checkBox; + ViewHolder(View v) { + super(v); + title = v.findViewById(R.id.task_title); + date = v.findViewById(R.id.task_date); + progress = v.findViewById(R.id.task_progress); + checkBox = v.findViewById(R.id.task_checkbox); + } + } +} \ 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 new file mode 100644 index 0000000..e71f366 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/TaskDetailsBottomSheet.java @@ -0,0 +1,82 @@ +package com.kbs.kbsintranett; + +import android.graphics.Color; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.switchmaterial.SwitchMaterial; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Map; + +public class TaskDetailsBottomSheet extends BottomSheetDialogFragment { + + private TaskItem task; + private OnTaskChangeListener listener; + + public interface OnTaskChangeListener { + void onTaskChanged(); + void onTaskDeleted(TaskItem task); + } + + public TaskDetailsBottomSheet(TaskItem task, OnTaskChangeListener listener) { + this.task = task; + this.listener = listener; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.bottom_sheet_task_details, container, false); + + TextView title = v.findViewById(R.id.detail_task_title); + TextView date = v.findViewById(R.id.detail_task_date); + 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); + Button btnDelete = v.findViewById(R.id.btn_delete_task); + + title.setText(task.getTitle()); + SimpleDateFormat sdf = new SimpleDateFormat("EEEE dd. MMMM yyyy", Locale.getDefault()); + date.setText("Frist: " + sdf.format(new Date(task.getDueDate()))); + + if (task.getDescription() != null && !task.getDescription().isEmpty()) { + desc.setText(task.getDescription()); + desc.setVisibility(View.VISIBLE); + } + + // List opp alle deltakere og deres status + for (Map.Entry entry : task.getAssigneeStatus().entrySet()) { + TextView t = new TextView(getContext()); + String status = entry.getValue() ? "✅ Fullført" : "⏳ Pågår"; + t.setText("• " + entry.getKey() + ": " + status); + t.setPadding(0, 4, 0, 4); + participantsContainer.addView(t); + } + + switchNotify.setChecked(task.isNotificationsEnabled()); + switchNotify.setOnCheckedChangeListener((btn, isChecked) -> { + task.setNotificationsEnabled(isChecked); + if (listener != null) listener.onTaskChanged(); + }); + + // Kun eier kan slette oppgaven + if (task.getCreatedByEmail().equals(UserManager.getInstance().getUserEmail())) { + btnDelete.setVisibility(View.VISIBLE); + btnDelete.setOnClickListener(view -> { + if (listener != null) listener.onTaskDeleted(task); + dismiss(); + }); + } + + return v; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/TaskItem.java b/app/src/main/java/com/kbs/kbsintranett/TaskItem.java index 35c65d3..1c22b70 100644 --- a/app/src/main/java/com/kbs/kbsintranett/TaskItem.java +++ b/app/src/main/java/com/kbs/kbsintranett/TaskItem.java @@ -1,6 +1,8 @@ package com.kbs.kbsintranett; import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; public class TaskItem implements Serializable { @@ -8,26 +10,41 @@ public class TaskItem implements Serializable { private String title; private String description; private long dueDate; - private boolean isCompleted; - private String assignedToEmail; private String createdByEmail; + private String createdByName; - public TaskItem(String title, String description, long dueDate, String assignedToEmail) { + // 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 + + public TaskItem(String title, String description, long dueDate) { this.id = UUID.randomUUID().toString(); this.title = title; this.description = description; this.dueDate = dueDate; - this.assignedToEmail = assignedToEmail; this.createdByEmail = UserManager.getInstance().getUserEmail(); - this.isCompleted = false; + this.createdByName = UserManager.getInstance().getUserDisplayName(); } - // Getters og Setters + // 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 public String getId() { return id; } public String getTitle() { return title; } public String getDescription() { return description; } public long getDueDate() { return dueDate; } - public boolean isCompleted() { return isCompleted; } - public void setCompleted(boolean completed) { isCompleted = completed; } - public String getAssignedToEmail() { return assignedToEmail; } + 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; } } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/TaskReminderWorker.java b/app/src/main/java/com/kbs/kbsintranett/TaskReminderWorker.java new file mode 100644 index 0000000..6b932b7 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/TaskReminderWorker.java @@ -0,0 +1,39 @@ +package com.kbs.kbsintranett; + +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.work.Worker; +import androidx.work.WorkerParameters; +import java.util.List; + +public class TaskReminderWorker extends Worker { + + public TaskReminderWorker(@NonNull Context context, @NonNull WorkerParameters params) { + super(context, params); + } + + @NonNull + @Override + public Result doWork() { + Context context = getApplicationContext(); + List tasks = CacheManager.getTasks(context); + String myEmail = UserManager.getInstance().getUserEmail(); + long now = System.currentTimeMillis(); + + for (TaskItem task : tasks) { + if (!task.isNotificationsEnabled() || task.isFullyCompleted() || task.getParticipantStatus(myEmail)) continue; + + long diff = task.getDueDate() - now; + + // Varsle hvis fristen er i dag (innenfor 24 timer) + if (diff > 0 && diff < 86400000L) { + NotificationHelper.showNotification(context, "Frist i dag!", "Oppgaven '" + task.getTitle() + "' forfaller snart."); + } + // Varsle hvis over frist (hver gang worker kjører, f.eks annenhver dag) + else if (diff < 0) { + NotificationHelper.showNotification(context, "Over frist!", "Oppgaven '" + task.getTitle() + "' er forsinket."); + } + } + return Result.success(); + } +} \ 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 358d67a..47d6803 100644 --- a/app/src/main/java/com/kbs/kbsintranett/TasksFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/TasksFragment.java @@ -4,20 +4,23 @@ 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 com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.tabs.TabLayout; import java.util.ArrayList; import java.util.List; -public class TasksFragment extends Fragment { +public class TasksFragment extends Fragment implements TaskAdapter.OnTaskClickListener { private RecyclerView recyclerView; - private List taskList = new ArrayList<>(); + private TaskAdapter adapter; + private TabLayout tabLayout; + private List allTasks = new ArrayList<>(); + private String myEmail; @Nullable @Override @@ -29,21 +32,91 @@ public class TasksFragment extends Fragment { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + myEmail = UserManager.getInstance().getUserEmail(); + tabLayout = view.findViewById(R.id.task_tabs); recyclerView = view.findViewById(R.id.recycler_tasks); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); FloatingActionButton fab = view.findViewById(R.id.fab_add_task); fab.setOnClickListener(v -> { - Toast.makeText(getContext(), "Ny oppgave-funksjon kommer her", Toast.LENGTH_SHORT).show(); + AddTaskBottomSheet dialog = new AddTaskBottomSheet(); + dialog.setOnTaskAddedListener(task -> { + allTasks.add(0, task); + saveAndRefresh(); + }); + dialog.show(getChildFragmentManager(), "AddTask"); + }); + + tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { + @Override public void onTabSelected(TabLayout.Tab tab) { filterAndDisplay(); } + @Override public void onTabUnselected(TabLayout.Tab tab) {} + @Override public void onTabReselected(TabLayout.Tab tab) {} }); loadTasks(); } private void loadTasks() { - // Dummy data - taskList.clear(); - taskList.add(new TaskItem("Husk å sjekke filter", "Serviceoppdrag hos kunde X", System.currentTimeMillis(), "meg@kbs.no")); - // Her vil vi senere koble på en TaskAdapter + allTasks = CacheManager.getTasks(getContext()); + filterAndDisplay(); + } + + private void filterAndDisplay() { + List filtered = new ArrayList<>(); + 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); + } + } + } + + adapter = new TaskAdapter(filtered, this); + recyclerView.setAdapter(adapter); + } + + @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) { + allTasks.remove(taskToDelete); + saveAndRefresh(); + } + }); + sheet.show(getChildFragmentManager(), "TaskDetails"); + } + + @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) { + task.setFullyCompleted(true); + } + + saveAndRefresh(); + } + + private void saveAndRefresh() { + CacheManager.saveTasks(getContext(), allTasks); + filterAndDisplay(); } } \ 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 new file mode 100644 index 0000000..f32543b --- /dev/null +++ b/app/src/main/res/layout/bottom_sheet_add_task.xml @@ -0,0 +1,84 @@ + + + + + + + + + + +