Før endringer av strømforbruket V2
This commit is contained in:
parent
f7eeba2037
commit
e89f7a3522
11 changed files with 519 additions and 164 deletions
|
|
@ -30,10 +30,12 @@ import retrofit2.Response;
|
|||
public class AddTaskBottomSheet extends BottomSheetDialogFragment {
|
||||
|
||||
private EditText etTitle, etDesc;
|
||||
private Button btnDate, btnUsers, btnSave;
|
||||
private Button btnDate, btnUsers, btnSave, btnClearDate; // NYTT
|
||||
private TextView txtSheetTitle, txtDatePreview, txtUsersPreview;
|
||||
|
||||
private Calendar dueDate = Calendar.getInstance();
|
||||
private boolean hasDate = true; // NYTT
|
||||
|
||||
private List<User> filteredUsers = new ArrayList<>();
|
||||
private List<User> selectedUsers = new ArrayList<>();
|
||||
private TaskItem taskToEdit = null;
|
||||
|
|
@ -77,6 +79,7 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment {
|
|||
etTitle = v.findViewById(R.id.et_task_title);
|
||||
etDesc = v.findViewById(R.id.et_task_desc);
|
||||
btnDate = v.findViewById(R.id.btn_task_date);
|
||||
btnClearDate = v.findViewById(R.id.btn_clear_date); // NYTT
|
||||
btnUsers = v.findViewById(R.id.btn_task_users);
|
||||
btnSave = v.findViewById(R.id.btn_save_task);
|
||||
txtDatePreview = v.findViewById(R.id.txt_date_preview);
|
||||
|
|
@ -86,10 +89,17 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment {
|
|||
txtSheetTitle.setText("Rediger Oppgave");
|
||||
etTitle.setText(taskToEdit.getTitle());
|
||||
etDesc.setText(taskToEdit.getDescription());
|
||||
dueDate.setTimeInMillis(taskToEdit.getDueDate());
|
||||
|
||||
if (taskToEdit.getDueDate() > 0) {
|
||||
dueDate.setTimeInMillis(taskToEdit.getDueDate());
|
||||
hasDate = true;
|
||||
} else {
|
||||
hasDate = false;
|
||||
}
|
||||
btnSave.setText("Oppdater Oppgave");
|
||||
} else {
|
||||
dueDate.add(Calendar.DAY_OF_MONTH, 1);
|
||||
hasDate = true;
|
||||
}
|
||||
|
||||
updateDatePreview();
|
||||
|
|
@ -97,10 +107,17 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment {
|
|||
btnDate.setOnClickListener(view -> {
|
||||
new DatePickerDialog(getContext(), (d, y, m, day) -> {
|
||||
dueDate.set(y, m, day);
|
||||
hasDate = true;
|
||||
updateDatePreview();
|
||||
}, dueDate.get(Calendar.YEAR), dueDate.get(Calendar.MONTH), dueDate.get(Calendar.DAY_OF_MONTH)).show();
|
||||
});
|
||||
|
||||
// NYTT: Knapp for å fjerne frist
|
||||
btnClearDate.setOnClickListener(v1 -> {
|
||||
hasDate = false;
|
||||
updateDatePreview();
|
||||
});
|
||||
|
||||
btnUsers.setOnClickListener(view -> showUserSelectionDialog());
|
||||
btnSave.setOnClickListener(view -> saveTask());
|
||||
|
||||
|
|
@ -114,7 +131,6 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment {
|
|||
@Override
|
||||
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
// BRUKER HJELPEKLASSEN HER:
|
||||
filteredUsers = UserFilterHelper.getFilteredUsers(response.body());
|
||||
|
||||
if (taskToEdit != null) {
|
||||
|
|
@ -173,8 +189,14 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment {
|
|||
}
|
||||
|
||||
private void updateDatePreview() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault());
|
||||
txtDatePreview.setText("Frist: " + sdf.format(dueDate.getTime()));
|
||||
if (hasDate) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault());
|
||||
txtDatePreview.setText("Frist: " + sdf.format(dueDate.getTime()));
|
||||
btnClearDate.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
txtDatePreview.setText("Ingen frist");
|
||||
btnClearDate.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveTask() {
|
||||
|
|
@ -184,6 +206,8 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment {
|
|||
return;
|
||||
}
|
||||
|
||||
long finalDueDate = hasDate ? dueDate.getTimeInMillis() : 0;
|
||||
|
||||
if (taskToEdit != null) {
|
||||
Map<String, Boolean> oldStatus = taskToEdit.getAssigneeStatus();
|
||||
taskToEdit.getAssigneeStatus().clear();
|
||||
|
|
@ -202,10 +226,10 @@ public class AddTaskBottomSheet extends BottomSheetDialogFragment {
|
|||
}
|
||||
taskToEdit.setTitle(title);
|
||||
taskToEdit.setDescription(etDesc.getText().toString());
|
||||
taskToEdit.setDueDate(dueDate.getTimeInMillis());
|
||||
taskToEdit.setDueDate(finalDueDate);
|
||||
if (listener != null) listener.onTaskUpdated(taskToEdit);
|
||||
} else {
|
||||
TaskItem newTask = new TaskItem(title, etDesc.getText().toString(), dueDate.getTimeInMillis());
|
||||
TaskItem newTask = new TaskItem(title, etDesc.getText().toString(), finalDueDate);
|
||||
if (selectedUsers.isEmpty()) {
|
||||
newTask.addAssignee(UserManager.getInstance().getUserEmail());
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.kbs.kbsintranett;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences; // NYTT
|
||||
import android.util.Log;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
|
@ -17,13 +18,18 @@ public class CacheManager {
|
|||
private static final String FILE_CALENDAR = "cache_calendar.json";
|
||||
private static final String FILE_NEWS = "cache_news.json";
|
||||
private static final String FILE_HANDBOOK = "cache_handbook.json";
|
||||
private static final String FILE_TASKS = "cache_tasks.json";
|
||||
|
||||
private static final String TAG = "CacheManager";
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
// NYTT: SharedPreferences for tidsstempler
|
||||
private static final String PREFS_CACHE = "kbs_cache_prefs";
|
||||
|
||||
// --- KALENDER ---
|
||||
public static void saveCalendarEvents(Context context, List<CalendarEvent> events) {
|
||||
saveList(context, FILE_CALENDAR, events);
|
||||
setLastFetchTime(context, "calendar"); // NYTT
|
||||
}
|
||||
|
||||
public static List<CalendarEvent> getCachedCalendarEvents(Context context) {
|
||||
|
|
@ -35,6 +41,7 @@ public class CacheManager {
|
|||
// --- NYHETER ---
|
||||
public static void saveNewsPosts(Context context, List<WpPost> posts) {
|
||||
saveList(context, FILE_NEWS, posts);
|
||||
setLastFetchTime(context, "news"); // NYTT
|
||||
}
|
||||
|
||||
public static List<WpPost> getCachedNewsPosts(Context context) {
|
||||
|
|
@ -46,6 +53,7 @@ public class CacheManager {
|
|||
// --- HÅNDBOK ---
|
||||
public static void saveHandbookItems(Context context, List<HandbookItem> items) {
|
||||
saveList(context, FILE_HANDBOOK, items);
|
||||
// Håndboken endres sjelden, trenger kanskje ikke tidssjekk, men greit å ha
|
||||
}
|
||||
|
||||
public static List<HandbookItem> getCachedHandbookItems(Context context) {
|
||||
|
|
@ -54,7 +62,47 @@ public class CacheManager {
|
|||
return list != null ? list : new ArrayList<>();
|
||||
}
|
||||
|
||||
// --- GENERISKE HJELPEMETODER ---
|
||||
// --- OPPGAVER ---
|
||||
public static void saveTasks(Context context, List<TaskItem> tasks) {
|
||||
saveList(context, FILE_TASKS, tasks);
|
||||
setLastFetchTime(context, "tasks"); // NYTT
|
||||
}
|
||||
|
||||
public static List<TaskItem> getTasks(Context context) {
|
||||
Type type = new TypeToken<List<TaskItem>>() {}.getType();
|
||||
List<TaskItem> list = loadList(context, FILE_TASKS, type);
|
||||
return list != null ? list : new ArrayList<>();
|
||||
}
|
||||
|
||||
// --- LOGIKK FOR TID ---
|
||||
|
||||
private static void setLastFetchTime(Context context, String key) {
|
||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_CACHE, Context.MODE_PRIVATE);
|
||||
prefs.edit().putLong(key + "_timestamp", System.currentTimeMillis()).apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sjekker om cachen er gyldig.
|
||||
* @param maxAgeMinutes Hvor gammel cachen kan være før vi henter på nytt (f.eks. 60 minutter).
|
||||
* Hvis push fungerer, kan vi sette denne høyt!
|
||||
*/
|
||||
public static boolean isCacheValid(Context context, String key, int maxAgeMinutes) {
|
||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_CACHE, Context.MODE_PRIVATE);
|
||||
long lastTime = prefs.getLong(key + "_timestamp", 0);
|
||||
long diff = System.currentTimeMillis() - lastTime;
|
||||
// Konverter minutter til millisekunder
|
||||
long maxDiff = maxAgeMinutes * 60 * 1000L;
|
||||
|
||||
return diff < maxDiff;
|
||||
}
|
||||
|
||||
// Metode for å tvinge oppdatering neste gang (brukes av FCM)
|
||||
public static void invalidateCache(Context context, String key) {
|
||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_CACHE, Context.MODE_PRIVATE);
|
||||
prefs.edit().remove(key + "_timestamp").apply();
|
||||
}
|
||||
|
||||
// --- GENERISKE HJELPEMETODER (Uendret) ---
|
||||
|
||||
private static <T> void saveList(Context context, String filename, List<T> list) {
|
||||
if (context == null || list == null) return;
|
||||
|
|
@ -64,7 +112,6 @@ public class CacheManager {
|
|||
FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE);
|
||||
fos.write(json.getBytes());
|
||||
fos.close();
|
||||
Log.d(TAG, "Lagret cache til " + filename);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Feil ved lagring av cache: " + filename, e);
|
||||
}
|
||||
|
|
@ -92,15 +139,4 @@ public class CacheManager {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
private static final String FILE_TASKS = "cache_tasks.json";
|
||||
|
||||
public static void saveTasks(Context context, List<TaskItem> tasks) {
|
||||
saveList(context, FILE_TASKS, tasks);
|
||||
}
|
||||
|
||||
public static List<TaskItem> getTasks(Context context) {
|
||||
Type type = new TypeToken<List<TaskItem>>() {}.getType();
|
||||
List<TaskItem> list = loadList(context, FILE_TASKS, type);
|
||||
return list != null ? list : new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
|
@ -121,15 +121,21 @@ public class HomeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
|||
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())));
|
||||
|
||||
// FIKS: Vis "Ingen frist" hvis dato er 0
|
||||
if (task.getDueDate() > 0) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("dd. MMM", Locale.getDefault());
|
||||
vh.date.setText("Frist: " + sdf.format(new Date(task.getDueDate())));
|
||||
} else {
|
||||
vh.date.setText("Ingen frist");
|
||||
}
|
||||
|
||||
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;
|
||||
boolean isOverdue = task.getDueDate() > 0 && task.getDueDate() < now && !task.isFullyCompleted() && !myStatus;
|
||||
|
||||
if (isOverdue) {
|
||||
vh.cardView.setCardBackgroundColor(ContextCompat.getColor(vh.itemView.getContext(), R.color.kbs_soft_light_pink_beige));
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ import android.content.pm.PackageManager;
|
|||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
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;
|
||||
|
|
@ -33,6 +33,7 @@ import retrofit2.Response;
|
|||
|
||||
public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickListener {
|
||||
|
||||
private static final String TAG = "HomeFragment";
|
||||
private RecyclerView recyclerView;
|
||||
private HomeAdapter adapter;
|
||||
private ProgressBar mainProgressBar;
|
||||
|
|
@ -41,8 +42,14 @@ 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 Handler timeoutHandler = new Handler(Looper.getMainLooper());
|
||||
private Runnable timeoutRunnable = this::forceStopLoading;
|
||||
|
||||
private static final int CACHE_TTL_MINUTES = 60;
|
||||
|
||||
private ActivityResultLauncher<String> requestPermissionLauncher;
|
||||
|
||||
@Override
|
||||
|
|
@ -50,7 +57,7 @@ public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickLis
|
|||
super.onCreate(savedInstanceState);
|
||||
requestPermissionLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.RequestPermission(),
|
||||
isGranted -> refreshData()
|
||||
isGranted -> refreshData(true)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -69,22 +76,82 @@ public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickLis
|
|||
recyclerView = view.findViewById(R.id.main_recycler_view);
|
||||
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
swipeRefreshLayout.setOnRefreshListener(this::refreshData);
|
||||
|
||||
refreshData();
|
||||
swipeRefreshLayout.setOnRefreshListener(() -> refreshData(true));
|
||||
|
||||
refreshData(false);
|
||||
}
|
||||
|
||||
private void refreshData() {
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
timeoutHandler.removeCallbacks(timeoutRunnable);
|
||||
}
|
||||
|
||||
private void refreshData(boolean forceNetwork) {
|
||||
if (activeNetworkCalls > 0) return;
|
||||
|
||||
boolean useCacheCalendar = !forceNetwork && CacheManager.isCacheValid(getContext(), "calendar", CACHE_TTL_MINUTES);
|
||||
boolean useCacheNews = !forceNetwork && CacheManager.isCacheValid(getContext(), "news", CACHE_TTL_MINUTES);
|
||||
boolean useCacheTasks = !forceNetwork && CacheManager.isCacheValid(getContext(), "tasks", CACHE_TTL_MINUTES);
|
||||
|
||||
// Hvis alt er i cache, last direkte
|
||||
if (useCacheCalendar && useCacheNews && useCacheTasks) {
|
||||
loadAllFromCache();
|
||||
return;
|
||||
}
|
||||
|
||||
activeNetworkCalls = 3;
|
||||
if (mainProgressBar != null) mainProgressBar.setVisibility(View.VISIBLE);
|
||||
|
||||
fetchCalendarData();
|
||||
fetchNewsData();
|
||||
fetchTaskData();
|
||||
timeoutHandler.removeCallbacks(timeoutRunnable);
|
||||
timeoutHandler.postDelayed(timeoutRunnable, 10000);
|
||||
|
||||
fetchCalendarData(useCacheCalendar);
|
||||
fetchNewsData(useCacheNews);
|
||||
fetchTaskData(useCacheTasks);
|
||||
}
|
||||
|
||||
private void fetchCalendarData() {
|
||||
private void forceStopLoading() {
|
||||
if (activeNetworkCalls > 0) {
|
||||
activeNetworkCalls = 0;
|
||||
if (mainProgressBar != null) mainProgressBar.setVisibility(View.GONE);
|
||||
if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(false);
|
||||
buildAndDisplayList();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAllFromCache() {
|
||||
if (mainProgressBar != null) mainProgressBar.setVisibility(View.VISIBLE);
|
||||
|
||||
new Thread(() -> {
|
||||
List<CalendarEvent> deviceEvents = CalendarManager.getDeviceEvents(getContext(), true);
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
if (!isAdded()) return;
|
||||
|
||||
List<CalendarEvent> cachedApi = CacheManager.getCachedCalendarEvents(getContext());
|
||||
for (CalendarEvent e : cachedApi) CalendarManager.formatEventForUI(e);
|
||||
currentEvents = CalendarManager.mergeAndSort(cachedApi, deviceEvents);
|
||||
|
||||
currentNews = CacheManager.getCachedNewsPosts(getContext());
|
||||
formatNewsDates(currentNews);
|
||||
|
||||
currentTasks = CacheManager.getTasks(getContext());
|
||||
|
||||
// FIKS: Hvis cache er tom, prøv nettverk likevel for å unngå tom skjerm
|
||||
if (currentTasks.isEmpty() && !CacheManager.isCacheValid(getContext(), "tasks", CACHE_TTL_MINUTES)) {
|
||||
fetchTaskData(false);
|
||||
}
|
||||
|
||||
if (mainProgressBar != null) mainProgressBar.setVisibility(View.GONE);
|
||||
if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(false);
|
||||
|
||||
buildAndDisplayList();
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void fetchCalendarData(boolean useCache) {
|
||||
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermissionLauncher.launch(Manifest.permission.READ_CALENDAR);
|
||||
currentEvents.clear();
|
||||
|
|
@ -93,74 +160,113 @@ public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickLis
|
|||
}
|
||||
|
||||
new Thread(() -> {
|
||||
List<CalendarEvent> deviceEvents = CalendarManager.getDeviceEvents(getContext(), true);
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback<List<CalendarEvent>>() {
|
||||
@Override
|
||||
public void onResponse(Call<List<CalendarEvent>> call, Response<List<CalendarEvent>> response) {
|
||||
List<CalendarEvent> apiEvents = new ArrayList<>();
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
apiEvents = response.body();
|
||||
CacheManager.saveCalendarEvents(getContext(), apiEvents);
|
||||
} 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) {
|
||||
try {
|
||||
if (getContext() == null) {
|
||||
checkLoadingCompleteAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
List<CalendarEvent> deviceEvents = CalendarManager.getDeviceEvents(getContext(), true);
|
||||
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
if (!isAdded()) return;
|
||||
|
||||
if (useCache) {
|
||||
List<CalendarEvent> cached = CacheManager.getCachedCalendarEvents(getContext());
|
||||
for (CalendarEvent e : cached) CalendarManager.formatEventForUI(e);
|
||||
currentEvents = CalendarManager.mergeAndSort(cached, deviceEvents);
|
||||
checkLoadingComplete();
|
||||
} else {
|
||||
RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback<List<CalendarEvent>>() {
|
||||
@Override
|
||||
public void onResponse(Call<List<CalendarEvent>> call, Response<List<CalendarEvent>> response) {
|
||||
List<CalendarEvent> apiEvents = new ArrayList<>();
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
apiEvents = response.body();
|
||||
CacheManager.saveCalendarEvents(getContext(), apiEvents);
|
||||
} 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());
|
||||
for (CalendarEvent e : cached) CalendarManager.formatEventForUI(e);
|
||||
currentEvents = CalendarManager.mergeAndSort(cached, deviceEvents);
|
||||
checkLoadingComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (Exception e) {
|
||||
checkLoadingCompleteAsync();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void fetchNewsData() {
|
||||
RetrofitClient.getApiService().getPosts().enqueue(new Callback<List<WpPost>>() {
|
||||
@Override
|
||||
public void onResponse(Call<List<WpPost>> call, Response<List<WpPost>> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
currentNews = response.body();
|
||||
CacheManager.saveNewsPosts(getContext(), currentNews);
|
||||
} else {
|
||||
currentNews = CacheManager.getCachedNewsPosts(getContext());
|
||||
private void fetchNewsData(boolean useCache) {
|
||||
if (useCache) {
|
||||
currentNews = CacheManager.getCachedNewsPosts(getContext());
|
||||
formatNewsDates(currentNews);
|
||||
checkLoadingComplete();
|
||||
} else {
|
||||
RetrofitClient.getApiService().getPosts().enqueue(new Callback<List<WpPost>>() {
|
||||
@Override
|
||||
public void onResponse(Call<List<WpPost>> call, Response<List<WpPost>> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
currentNews = response.body();
|
||||
CacheManager.saveNewsPosts(getContext(), currentNews);
|
||||
} else {
|
||||
currentNews = CacheManager.getCachedNewsPosts(getContext());
|
||||
}
|
||||
formatNewsDates(currentNews);
|
||||
checkLoadingComplete();
|
||||
}
|
||||
formatNewsDates(currentNews);
|
||||
checkLoadingComplete();
|
||||
}
|
||||
@Override
|
||||
public void onFailure(Call<List<WpPost>> call, Throwable t) {
|
||||
currentNews = CacheManager.getCachedNewsPosts(getContext());
|
||||
formatNewsDates(currentNews);
|
||||
checkLoadingComplete();
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onFailure(Call<List<WpPost>> call, Throwable t) {
|
||||
currentNews = CacheManager.getCachedNewsPosts(getContext());
|
||||
formatNewsDates(currentNews);
|
||||
checkLoadingComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
private void fetchTaskData(boolean useCache) {
|
||||
// FIKS: Hvis cache er valgt, men listen er tom, hent fra nett!
|
||||
if (useCache) {
|
||||
currentTasks = CacheManager.getTasks(getContext());
|
||||
if (currentTasks.isEmpty()) {
|
||||
fetchTaskData(false); // Rekursivt kall men tvinger nettverk
|
||||
return;
|
||||
}
|
||||
checkLoadingComplete();
|
||||
} else {
|
||||
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();
|
||||
}
|
||||
checkLoadingComplete();
|
||||
}
|
||||
@Override
|
||||
public void onFailure(Call<List<TaskItem>> call, Throwable t) {
|
||||
currentTasks = CacheManager.getTasks(getContext());
|
||||
checkLoadingComplete();
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onFailure(Call<List<TaskItem>> call, Throwable t) {
|
||||
currentTasks = CacheManager.getTasks(getContext());
|
||||
checkLoadingComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void checkLoadingCompleteAsync() {
|
||||
new Handler(Looper.getMainLooper()).post(this::checkLoadingComplete);
|
||||
}
|
||||
|
||||
private void formatNewsDates(List<WpPost> posts) {
|
||||
|
|
@ -174,17 +280,22 @@ public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickLis
|
|||
}
|
||||
}
|
||||
|
||||
private void checkLoadingComplete() {
|
||||
private synchronized void checkLoadingComplete() {
|
||||
activeNetworkCalls--;
|
||||
if (activeNetworkCalls <= 0) {
|
||||
activeNetworkCalls = 0;
|
||||
timeoutHandler.removeCallbacks(timeoutRunnable);
|
||||
|
||||
if (mainProgressBar != null) mainProgressBar.setVisibility(View.GONE);
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(false);
|
||||
|
||||
buildAndDisplayList();
|
||||
}
|
||||
}
|
||||
|
||||
private void buildAndDisplayList() {
|
||||
if (!isAdded()) return;
|
||||
|
||||
List<Object> items = new ArrayList<>();
|
||||
String myEmail = UserManager.getInstance().getUserEmail();
|
||||
|
||||
|
|
@ -201,23 +312,38 @@ public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickLis
|
|||
|
||||
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 (currentTasks != null) {
|
||||
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()));
|
||||
// NY SORTERING: Med frist først, så uten frist (alfabetisk)
|
||||
Collections.sort(myActiveTasks, (t1, t2) -> {
|
||||
boolean t1HasDate = t1.getDueDate() > 0;
|
||||
boolean t2HasDate = t2.getDueDate() > 0;
|
||||
|
||||
if (t1HasDate && !t2HasDate) return -1; // t1 først
|
||||
if (!t1HasDate && t2HasDate) return 1; // t2 først
|
||||
if (!t1HasDate && !t2HasDate) return t1.getTitle().compareToIgnoreCase(t2.getTitle()); // Alfabetisk
|
||||
|
||||
return Long.compare(t1.getDueDate(), t2.getDueDate()); // Dato
|
||||
});
|
||||
|
||||
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);
|
||||
if (currentNews != null) {
|
||||
items.addAll(currentNews);
|
||||
}
|
||||
|
||||
adapter = new HomeAdapter(items, this);
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
|
@ -230,7 +356,7 @@ public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickLis
|
|||
|
||||
@Override public void onCalendarItemClick(CalendarEvent event) {
|
||||
CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
|
||||
sheet.setOnEventChangeListener(this::refreshData);
|
||||
sheet.setOnEventChangeListener(() -> refreshData(true));
|
||||
sheet.show(getParentFragmentManager(), "CalendarDetails");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
|
|||
@Override
|
||||
public void onNewToken(@NonNull String token) {
|
||||
super.onNewToken(token);
|
||||
Log.d(TAG, "Ny FCM Token: " + token);
|
||||
AuthRepository.updateDeviceToken(token);
|
||||
}
|
||||
|
||||
|
|
@ -36,28 +35,32 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
|
|||
public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
|
||||
super.onMessageReceived(remoteMessage);
|
||||
|
||||
Log.d(TAG, "Melding mottatt fra: " + remoteMessage.getFrom());
|
||||
|
||||
// 1. Sjekk data payload (Bakgrunnsoppdatering)
|
||||
// 1. DATA PAYLOAD (Bakgrunnsoppdatering)
|
||||
if (remoteMessage.getData().size() > 0) {
|
||||
String forceRefresh = remoteMessage.getData().get("force_refresh");
|
||||
String type = remoteMessage.getData().get("type"); // "tasks_update", "news_update", "calendar_update"
|
||||
|
||||
if ("true".equalsIgnoreCase(forceRefresh)) {
|
||||
Log.d(TAG, "Mottok 'force_refresh' - oppdaterer kalender og alarmer...");
|
||||
updateCalendarAndAlarms();
|
||||
}
|
||||
Log.d(TAG, "Mottok force_refresh. Type: " + type);
|
||||
|
||||
// Hvis meldingen også har egne titler i data-feltet (valgfritt)
|
||||
String title = remoteMessage.getData().get("title");
|
||||
String body = remoteMessage.getData().get("body");
|
||||
if (title != null && body != null) {
|
||||
showNotification(title, body);
|
||||
// Uansett hva det er, ugyldiggjør cachen slik at neste gang brukeren åpner appen, hentes ferske data.
|
||||
if ("news_update".equals(type)) {
|
||||
CacheManager.invalidateCache(getApplicationContext(), "news");
|
||||
} else if ("tasks_update".equals(type)) {
|
||||
CacheManager.invalidateCache(getApplicationContext(), "tasks");
|
||||
updateTasksInBackground(); // Hent oppgaver med en gang i bakgrunnen
|
||||
} else {
|
||||
// Kalender eller generelt
|
||||
CacheManager.invalidateCache(getApplicationContext(), "calendar");
|
||||
updateCalendarAndAlarms();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Sjekk notification payload (Vises automatisk når app er i bakgrunn, men vi håndterer den her for forgrunn)
|
||||
// 2. NOTIFICATION PAYLOAD (Synlig varsel - Nyheter etc)
|
||||
// Android viser disse automatisk når appen er i bakgrunnen.
|
||||
// Denne koden kjører hvis appen er i forgrunnen, eller hvis meldingen kun er "Data" og vi vil lage varsel manuelt.
|
||||
if (remoteMessage.getNotification() != null) {
|
||||
Log.d(TAG, "Melding varsel body: " + remoteMessage.getNotification().getBody());
|
||||
showNotification(
|
||||
remoteMessage.getNotification().getTitle(),
|
||||
remoteMessage.getNotification().getBody()
|
||||
|
|
@ -66,24 +69,28 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
|
|||
}
|
||||
|
||||
private void updateCalendarAndAlarms() {
|
||||
// Vi bruker Retrofit for å hente kalenderen på nytt
|
||||
RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback<List<CalendarEvent>>() {
|
||||
@Override
|
||||
public void onResponse(Call<List<CalendarEvent>> call, Response<List<CalendarEvent>> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
// Lagre til cache først (god praksis)
|
||||
CacheManager.saveCalendarEvents(getApplicationContext(), response.body());
|
||||
|
||||
// Oppdater alarmer lokalt
|
||||
AlarmScheduler.scheduleAlarmsForEvents(getApplicationContext(), response.body());
|
||||
Log.d(TAG, "Kalender og alarmer oppdatert via Push.");
|
||||
}
|
||||
}
|
||||
@Override public void onFailure(Call<List<CalendarEvent>> call, Throwable t) {}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateTasksInBackground() {
|
||||
// Henter oppgaver stille i bakgrunnen slik at de er klare når brukeren åpner appen
|
||||
RetrofitClient.getApiService().getTasks().enqueue(new Callback<List<TaskItem>>() {
|
||||
@Override
|
||||
public void onFailure(Call<List<CalendarEvent>> call, Throwable t) {
|
||||
Log.e(TAG, "Feil ved push-oppdatering av kalender", t);
|
||||
public void onResponse(Call<List<TaskItem>> call, Response<List<TaskItem>> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
CacheManager.saveTasks(getApplicationContext(), response.body());
|
||||
}
|
||||
}
|
||||
@Override public void onFailure(Call<List<TaskItem>> call, Throwable t) {}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -99,10 +106,7 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
|
|||
Intent tapIntent = new Intent(this, MainActivity.class);
|
||||
tapIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
tapIntent,
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
this, 0, tapIntent, PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
|
|
@ -114,22 +118,15 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
|
|||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true);
|
||||
|
||||
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
|
||||
notificationManager.notify((int) System.currentTimeMillis(), builder.build());
|
||||
NotificationManagerCompat.from(this).notify((int) System.currentTimeMillis(), builder.build());
|
||||
}
|
||||
|
||||
private void createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
"KBS Kalendervarsler",
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
CHANNEL_ID, "KBS Varsler", NotificationManager.IMPORTANCE_HIGH
|
||||
);
|
||||
channel.setDescription("Varsler fra KBS Intranett");
|
||||
NotificationManager manager = getSystemService(NotificationManager.class);
|
||||
if (manager != null) {
|
||||
manager.createNotificationChannel(channel);
|
||||
}
|
||||
getSystemService(NotificationManager.class).createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -45,10 +45,13 @@ public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.ViewHolder> {
|
|||
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())));
|
||||
if (task.getDueDate() > 0) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("dd. MMM", Locale.getDefault());
|
||||
holder.date.setText("Frist: " + sdf.format(new Date(task.getDueDate())));
|
||||
} else {
|
||||
holder.date.setText("Ingen frist");
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
|
@ -56,22 +59,40 @@ public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.ViewHolder> {
|
|||
holder.creator.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
boolean myStatus = task.getParticipantStatus(currentUserEmail);
|
||||
holder.checkBox.setChecked(myStatus);
|
||||
// SJEKK: Er jeg en deltaker?
|
||||
boolean isParticipant = task.isUserParticipant(currentUserEmail);
|
||||
|
||||
if (isParticipant) {
|
||||
// Hvis jeg er deltaker: Vis checkbox og la meg endre MIN status
|
||||
holder.checkBox.setVisibility(View.VISIBLE);
|
||||
boolean myStatus = task.getParticipantStatus(currentUserEmail);
|
||||
holder.checkBox.setOnCheckedChangeListener(null); // Hindre trigging ved resirkulering
|
||||
holder.checkBox.setChecked(myStatus);
|
||||
holder.checkBox.setOnClickListener(v -> listener.onStatusChanged(task, holder.checkBox.isChecked()));
|
||||
} else {
|
||||
// Hvis jeg IKKE er deltaker (f.eks. Admin som ser på "Alle"):
|
||||
// Skjul checkboxen i listen. Admin må åpne detaljer for å endre andres status.
|
||||
holder.checkBox.setVisibility(View.INVISIBLE);
|
||||
holder.checkBox.setOnClickListener(null);
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
boolean isOverdue = task.getDueDate() < now && !task.isFullyCompleted() && !myStatus;
|
||||
// Sjekk overtid (kun hvis frist er satt)
|
||||
// Er jeg ikke deltaker, sjekker vi om hele oppgaven er ferdig
|
||||
boolean isDoneToCheck = isParticipant ? task.getParticipantStatus(currentUserEmail) : task.isFullyCompleted();
|
||||
boolean isOverdue = task.getDueDate() > 0 && task.getDueDate() < now && !task.isFullyCompleted() && !isDoneToCheck;
|
||||
|
||||
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));
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("dd. MMM", Locale.getDefault());
|
||||
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()) {
|
||||
if (isDoneToCheck || task.isFullyCompleted()) {
|
||||
holder.title.setPaintFlags(holder.title.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
|
||||
holder.title.setTextColor(Color.GRAY);
|
||||
holder.cardView.setCardBackgroundColor(Color.parseColor("#F5F5F5"));
|
||||
|
|
@ -85,7 +106,6 @@ public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.ViewHolder> {
|
|||
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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.view.LayoutInflater;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
|
|
@ -24,7 +25,7 @@ public class TaskDetailsBottomSheet extends BottomSheetDialogFragment {
|
|||
public interface OnTaskChangeListener {
|
||||
void onTaskChanged();
|
||||
void onTaskDeleted(TaskItem task);
|
||||
void onEditRequested(TaskItem task); // NYTT
|
||||
void onEditRequested(TaskItem task);
|
||||
}
|
||||
|
||||
public TaskDetailsBottomSheet(TaskItem task, OnTaskChangeListener listener) {
|
||||
|
|
@ -45,10 +46,15 @@ public class TaskDetailsBottomSheet extends BottomSheetDialogFragment {
|
|||
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);
|
||||
Button btnClose = v.findViewById(R.id.btn_close_details); // NYTT
|
||||
|
||||
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.getDueDate() > 0) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("EEEE dd. MMMM yyyy", Locale.getDefault());
|
||||
date.setText("Frist: " + sdf.format(new Date(task.getDueDate())));
|
||||
} else {
|
||||
date.setText("Ingen frist");
|
||||
}
|
||||
|
||||
if (task.getDescription() != null && !task.getDescription().isEmpty()) {
|
||||
desc.setText(task.getDescription());
|
||||
|
|
@ -56,12 +62,47 @@ public class TaskDetailsBottomSheet extends BottomSheetDialogFragment {
|
|||
}
|
||||
|
||||
participantsContainer.removeAllViews();
|
||||
UserManager userManager = UserManager.getInstance();
|
||||
String myEmail = userManager.getUserEmail();
|
||||
|
||||
boolean canManageOthers = task.getCreatedByEmail().equalsIgnoreCase(myEmail) || userManager.isAdmin();
|
||||
|
||||
for (Map.Entry<String, Boolean> 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);
|
||||
String email = entry.getKey();
|
||||
boolean isDone = entry.getValue();
|
||||
|
||||
if (canManageOthers) {
|
||||
CheckBox cb = new CheckBox(getContext());
|
||||
cb.setText(email + (isDone ? " (Fullført)" : ""));
|
||||
cb.setChecked(isDone);
|
||||
|
||||
if (isDone) cb.setTextColor(getResources().getColor(android.R.color.darker_gray));
|
||||
else cb.setTextColor(getResources().getColor(R.color.black));
|
||||
|
||||
cb.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
task.setParticipantStatus(email, isChecked);
|
||||
|
||||
cb.setText(email + (isChecked ? " (Fullført)" : ""));
|
||||
if (isChecked) cb.setTextColor(getResources().getColor(android.R.color.darker_gray));
|
||||
else cb.setTextColor(getResources().getColor(R.color.black));
|
||||
|
||||
if (listener != null) listener.onTaskChanged();
|
||||
|
||||
// Sjekk om alle er ferdige, i så fall lukk vinduet
|
||||
if (isChecked && areAllParticipantsFinished()) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
participantsContainer.addView(cb);
|
||||
|
||||
} else {
|
||||
TextView t = new TextView(getContext());
|
||||
String status = isDone ? "✅ Fullført" : "⏳ Pågår";
|
||||
t.setText("• " + email + ": " + status);
|
||||
t.setPadding(0, 8, 0, 8);
|
||||
t.setTextSize(14);
|
||||
participantsContainer.addView(t);
|
||||
}
|
||||
}
|
||||
|
||||
switchNotify.setChecked(task.isNotificationsEnabled());
|
||||
|
|
@ -70,7 +111,7 @@ public class TaskDetailsBottomSheet extends BottomSheetDialogFragment {
|
|||
if (listener != null) listener.onTaskChanged();
|
||||
});
|
||||
|
||||
if (task.getCreatedByEmail().equalsIgnoreCase(UserManager.getInstance().getUserEmail())) {
|
||||
if (canManageOthers) {
|
||||
ownerActions.setVisibility(View.VISIBLE);
|
||||
btnDelete.setOnClickListener(view -> {
|
||||
if (listener != null) listener.onTaskDeleted(task);
|
||||
|
|
@ -80,8 +121,20 @@ public class TaskDetailsBottomSheet extends BottomSheetDialogFragment {
|
|||
if (listener != null) listener.onEditRequested(task);
|
||||
dismiss();
|
||||
});
|
||||
} else {
|
||||
ownerActions.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// NYTT: Lukk-knapp funksjonalitet
|
||||
btnClose.setOnClickListener(view -> dismiss());
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
private boolean areAllParticipantsFinished() {
|
||||
for (Boolean isDone : task.getAssigneeStatus().values()) {
|
||||
if (!isDone) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import android.os.Bundle;
|
|||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
|
@ -27,6 +28,7 @@ public class TasksFragment extends Fragment implements TaskAdapter.OnTaskClickLi
|
|||
private TaskAdapter adapter;
|
||||
private TabLayout tabLayout;
|
||||
private SwipeRefreshLayout swipeRefresh;
|
||||
private CheckBox cbShowCompleted;
|
||||
private List<TaskItem> allTasks = new ArrayList<>();
|
||||
private String myEmail;
|
||||
|
||||
|
|
@ -44,6 +46,8 @@ public class TasksFragment extends Fragment implements TaskAdapter.OnTaskClickLi
|
|||
tabLayout = view.findViewById(R.id.task_tabs);
|
||||
recyclerView = view.findViewById(R.id.recycler_tasks);
|
||||
swipeRefresh = view.findViewById(R.id.swipe_refresh_tasks);
|
||||
cbShowCompleted = view.findViewById(R.id.cb_show_completed);
|
||||
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
|
||||
setupTabs();
|
||||
|
|
@ -67,14 +71,20 @@ public class TasksFragment extends Fragment implements TaskAdapter.OnTaskClickLi
|
|||
});
|
||||
|
||||
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||
@Override public void onTabSelected(TabLayout.Tab tab) { filterAndDisplay(); }
|
||||
@Override public void onTabSelected(TabLayout.Tab tab) {
|
||||
updateFilterUI(tab.getPosition());
|
||||
filterAndDisplay();
|
||||
}
|
||||
@Override public void onTabUnselected(TabLayout.Tab tab) {}
|
||||
@Override public void onTabReselected(TabLayout.Tab tab) {}
|
||||
});
|
||||
|
||||
cbShowCompleted.setOnCheckedChangeListener((buttonView, isChecked) -> filterAndDisplay());
|
||||
|
||||
swipeRefresh.setOnRefreshListener(this::fetchTasksFromServer);
|
||||
|
||||
allTasks = CacheManager.getTasks(getContext());
|
||||
updateFilterUI(tabLayout.getSelectedTabPosition());
|
||||
filterAndDisplay();
|
||||
fetchTasksFromServer();
|
||||
}
|
||||
|
|
@ -89,6 +99,14 @@ public class TasksFragment extends Fragment implements TaskAdapter.OnTaskClickLi
|
|||
}
|
||||
}
|
||||
|
||||
private void updateFilterUI(int tabIndex) {
|
||||
if (tabIndex == 3) {
|
||||
cbShowCompleted.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
cbShowCompleted.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchTasksFromServer() {
|
||||
swipeRefresh.setRefreshing(true);
|
||||
RetrofitClient.getApiService().getTasks().enqueue(new Callback<List<TaskItem>>() {
|
||||
|
|
@ -122,6 +140,27 @@ public class TasksFragment extends Fragment implements TaskAdapter.OnTaskClickLi
|
|||
if (!email.equalsIgnoreCase(myEmail)) { hasOtherParticipants = true; break; }
|
||||
}
|
||||
|
||||
// NYTT: Sjekk dynamisk om ALLE deltakerne har fullført oppgaven
|
||||
boolean allParticipantsFinished = true;
|
||||
if (t.getAssigneeStatus().isEmpty()) {
|
||||
allParticipantsFinished = false;
|
||||
} else {
|
||||
for (Boolean isDone : t.getAssigneeStatus().values()) {
|
||||
if (!isDone) {
|
||||
allParticipantsFinished = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// En oppgave er "helt ferdig" hvis flagget er satt ELLER alle har krysset av
|
||||
boolean effectivelyFinished = t.isFullyCompleted() || allParticipantsFinished;
|
||||
|
||||
// Oppdater objektet slik at det lagres riktig neste gang (valgfritt men lurt)
|
||||
if (effectivelyFinished && !t.isFullyCompleted()) {
|
||||
t.setFullyCompleted(true);
|
||||
}
|
||||
|
||||
switch (selectedTab) {
|
||||
case 0: // MINE
|
||||
if (isParticipant && !iHaveDoneIt && !t.isFullyCompleted()) filtered.add(t);
|
||||
|
|
@ -132,13 +171,37 @@ public class TasksFragment extends Fragment implements TaskAdapter.OnTaskClickLi
|
|||
case 2: // TILDELT ANDRE
|
||||
if (isCreator && !t.isFullyCompleted() && hasOtherParticipants) filtered.add(t);
|
||||
break;
|
||||
case 3: // ALLE
|
||||
if (!t.isFullyCompleted()) filtered.add(t);
|
||||
case 3: // ALLE (Admin)
|
||||
if (cbShowCompleted.isChecked()) {
|
||||
// Vis alt
|
||||
filtered.add(t);
|
||||
} else {
|
||||
// Vis kun hvis IKKE ferdig
|
||||
if (!effectivelyFinished) 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()));
|
||||
|
||||
// SORTERING
|
||||
Collections.sort(filtered, (t1, t2) -> {
|
||||
boolean t1HasDate = t1.getDueDate() > 0;
|
||||
boolean t2HasDate = t2.getDueDate() > 0;
|
||||
|
||||
if (selectedTab == 1) {
|
||||
if (t1HasDate && !t2HasDate) return -1;
|
||||
if (!t1HasDate && t2HasDate) return 1;
|
||||
if (!t1HasDate && !t2HasDate) return t1.getTitle().compareToIgnoreCase(t2.getTitle());
|
||||
return Long.compare(t2.getDueDate(), t1.getDueDate());
|
||||
}
|
||||
else {
|
||||
if (t1HasDate && !t2HasDate) return -1;
|
||||
if (!t1HasDate && t2HasDate) return 1;
|
||||
if (!t1HasDate && !t2HasDate) return t1.getTitle().compareToIgnoreCase(t2.getTitle());
|
||||
return Long.compare(t1.getDueDate(), t2.getDueDate());
|
||||
}
|
||||
});
|
||||
|
||||
adapter = new TaskAdapter(filtered, this);
|
||||
recyclerView.setAdapter(adapter);
|
||||
}
|
||||
|
|
@ -155,7 +218,7 @@ public class TasksFragment extends Fragment implements TaskAdapter.OnTaskClickLi
|
|||
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 onTaskAdded(TaskItem task) {}
|
||||
@Override public void onTaskUpdated(TaskItem task) {
|
||||
saveAndSync();
|
||||
}
|
||||
|
|
@ -169,19 +232,14 @@ public class TasksFragment extends Fragment implements TaskAdapter.OnTaskClickLi
|
|||
@Override
|
||||
public void onStatusChanged(TaskItem task, boolean isDone) {
|
||||
task.setParticipantStatus(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);
|
||||
}
|
||||
// Her trenger vi ikke logikk for "hasOthers" osv, fordi saveAndSync()
|
||||
// kaller filterAndDisplay() som nå regner ut status dynamisk.
|
||||
saveAndSync();
|
||||
}
|
||||
|
||||
private void saveAndSync() {
|
||||
CacheManager.saveTasks(getContext(), allTasks);
|
||||
filterAndDisplay();
|
||||
filterAndDisplay(); // Oppdaterer visningen umiddelbart
|
||||
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) {}
|
||||
|
|
|
|||
|
|
@ -45,18 +45,31 @@
|
|||
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_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Frist: -"
|
||||
android:layout_marginStart="8dp"/>
|
||||
|
||||
<!-- NYTT: Fjern frist knapp -->
|
||||
<Button
|
||||
android:id="@+id/btn_clear_date"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="X"
|
||||
android:textColor="#999999"
|
||||
android:visibility="gone"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
android:id="@+id/switch_notifications"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Varsle meg om denne oppgaven"
|
||||
android:text="Aktiver påminnelser til deltakere"
|
||||
android:checked="true"
|
||||
android:layout_marginBottom="24dp"/>
|
||||
|
||||
|
|
@ -70,6 +70,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<Button
|
||||
|
|
@ -93,4 +94,13 @@
|
|||
android:layout_marginStart="8dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- NY LUKK KNAPP -->
|
||||
<Button
|
||||
android:id="@+id/btn_close_details"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Lukk / Ferdig"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:textColor="@color/kbs_muted_blue_gray"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -13,9 +13,21 @@
|
|||
android:background="@color/white"
|
||||
app:tabSelectedTextColor="@color/kbs_logo_blue"
|
||||
app:tabIndicatorColor="@color/kbs_logo_blue">
|
||||
<!-- Faner legges til programmatisk i TasksFragment.java -->
|
||||
</com.google.android.material.tabs.TabLayout>
|
||||
|
||||
<!-- NY SJEKKBOKS: Vises kun under "Alle" -->
|
||||
<CheckBox
|
||||
android:id="@+id/cb_show_completed"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Vis også fullførte oppgaver"
|
||||
android:textColor="@color/kbs_muted_blue_gray"
|
||||
android:textSize="12sp"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipe_refresh_tasks"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
Loading…
Reference in a new issue