diff --git a/app/src/main/java/com/kbs/kbsintranett/HomeAdapter.java b/app/src/main/java/com/kbs/kbsintranett/HomeAdapter.java new file mode 100644 index 0000000..1a84eca --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/HomeAdapter.java @@ -0,0 +1,189 @@ +package com.kbs.kbsintranett; + +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.RecyclerView; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; +import java.util.List; + +public class HomeAdapter extends RecyclerView.Adapter { + + public static final int TYPE_HEADER = 0; + public static final int TYPE_CREATE_BUTTON = 1; + public static final int TYPE_SECTION_TITLE = 2; + public static final int TYPE_CALENDAR_ITEM = 3; + public static final int TYPE_NEWS_ITEM = 4; + + private final List items; + private final OnHomeClickListener listener; + + public interface OnHomeClickListener { + void onProfileClick(); + void onCreateEventClick(); + void onViewAllCalendarClick(); + void onViewAllNewsClick(); + void onCalendarItemClick(CalendarEvent event); + void onNewsItemClick(WpPost post); + } + + public HomeAdapter(List items, OnHomeClickListener listener) { + this.items = items; + this.listener = listener; + } + + @Override + public int getItemViewType(int position) { + Object item = items.get(position); + if (item instanceof HeaderItem) return TYPE_HEADER; + if (item instanceof CreateButtonItem) return TYPE_CREATE_BUTTON; + if (item instanceof SectionTitleItem) return TYPE_SECTION_TITLE; + if (item instanceof CalendarEvent) return TYPE_CALENDAR_ITEM; + if (item instanceof WpPost) return TYPE_NEWS_ITEM; + return -1; + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + switch (viewType) { + case TYPE_HEADER: + return new HeaderViewHolder(inflater.inflate(R.layout.item_home_header, parent, false)); + case TYPE_CREATE_BUTTON: + return new CreateButtonViewHolder(inflater.inflate(R.layout.item_home_create_btn, parent, false)); + case TYPE_SECTION_TITLE: + return new SectionTitleViewHolder(inflater.inflate(R.layout.item_home_section_title, parent, false)); + case TYPE_CALENDAR_ITEM: + return new CalendarViewHolder(inflater.inflate(R.layout.item_calendar, parent, false)); + case TYPE_NEWS_ITEM: + return new NewsViewHolder(inflater.inflate(R.layout.item_news, parent, false)); + default: + throw new IllegalArgumentException("Ugyldig viewType"); + } + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + Object item = items.get(position); + + if (holder instanceof HeaderViewHolder) { + ((HeaderViewHolder) holder).btnProfile.setOnClickListener(v -> listener.onProfileClick()); + } + else if (holder instanceof CreateButtonViewHolder) { + ((CreateButtonViewHolder) holder).btnCreate.setOnClickListener(v -> listener.onCreateEventClick()); + } + else if (holder instanceof SectionTitleViewHolder) { + SectionTitleItem section = (SectionTitleItem) item; + SectionTitleViewHolder vh = (SectionTitleViewHolder) holder; + vh.title.setText(section.title); + vh.btnViewAll.setOnClickListener(v -> { + if (section.isCalendar) listener.onViewAllCalendarClick(); + else listener.onViewAllNewsClick(); + }); + } + else if (holder instanceof CalendarViewHolder) { + CalendarEvent event = (CalendarEvent) item; + CalendarViewHolder vh = (CalendarViewHolder) holder; + vh.day.setText(event.getDay()); + vh.month.setText(event.getMonth()); + vh.time.setText(event.getTime()); + vh.title.setText(event.getTitle()); + + boolean isPrivate = event.getDescription() != null && event.getDescription().contains("#deltakere:"); + int color; + try { + color = Color.parseColor(isPrivate ? "#673AB7" : event.getCalendarColor()); + } catch (Exception e) { + color = Color.parseColor("#0069B3"); + } + vh.dateBox.setBackgroundTintList(ColorStateList.valueOf(color)); + vh.itemView.setOnClickListener(v -> listener.onCalendarItemClick(event)); + } + else if (holder instanceof NewsViewHolder) { + WpPost post = (WpPost) item; + NewsViewHolder vh = (NewsViewHolder) holder; + vh.title.setText(post.getTitleStr()); + vh.excerpt.setText(post.getExcerptStr()); + vh.date.setText(post.date); + + String cat = post.getCategoryName(); + vh.category.setText(cat); + vh.category.setVisibility(cat.isEmpty() ? View.GONE : View.VISIBLE); + + String imgUrl = post.getFeaturedImageUrl(); + if (imgUrl != null) { + vh.image.setVisibility(View.VISIBLE); + Glide.with(vh.image.getContext()).load(imgUrl).transition(DrawableTransitionOptions.withCrossFade()).centerCrop().into(vh.image); + } else { + vh.image.setVisibility(View.GONE); + } + vh.itemView.setOnClickListener(v -> listener.onNewsItemClick(post)); + } + } + + @Override + public int getItemCount() { + return items.size(); + } + + // --- VIEW HOLDERS --- + + static class HeaderViewHolder extends RecyclerView.ViewHolder { + ImageView btnProfile; + HeaderViewHolder(View v) { super(v); btnProfile = v.findViewById(R.id.btn_profile); } + } + + static class CreateButtonViewHolder extends RecyclerView.ViewHolder { + Button btnCreate; + CreateButtonViewHolder(View v) { super(v); btnCreate = v.findViewById(R.id.btn_create_event); } + } + + static class SectionTitleViewHolder extends RecyclerView.ViewHolder { + TextView title, btnViewAll; + SectionTitleViewHolder(View v) { super(v); title = v.findViewById(R.id.txt_section_title); btnViewAll = v.findViewById(R.id.btn_view_all); } + } + + static class CalendarViewHolder extends RecyclerView.ViewHolder { + TextView day, month, title, time; + LinearLayout dateBox; + CalendarViewHolder(View v) { + super(v); + day = v.findViewById(R.id.cal_day); + month = v.findViewById(R.id.cal_month); + title = v.findViewById(R.id.cal_title); + time = v.findViewById(R.id.cal_time); + dateBox = v.findViewById(R.id.date_box_background); + } + } + + static class NewsViewHolder extends RecyclerView.ViewHolder { + TextView title, excerpt, date, category; + ImageView image; + NewsViewHolder(View v) { + super(v); + title = v.findViewById(R.id.news_title); + excerpt = v.findViewById(R.id.news_excerpt); + date = v.findViewById(R.id.news_date); + category = v.findViewById(R.id.news_category); + image = v.findViewById(R.id.news_image); + } + } + + // --- WRAPPER KLASSER FOR FLATE LISTER --- + public static class HeaderItem {} + public static class CreateButtonItem {} + public static class SectionTitleItem { + String title; boolean isCalendar; + public SectionTitleItem(String t, boolean c) { this.title = t; this.isCalendar = c; } + } +} \ 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 536794b..6c50885 100644 --- a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java @@ -8,9 +8,7 @@ import android.os.Looper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; import android.widget.ProgressBar; -import android.widget.TextView; import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -32,27 +30,26 @@ import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -public class HomeFragment extends Fragment { - private ActivityResultLauncher requestPermissionLauncher; - private RecyclerView calendarRecycler; - private RecyclerView newsRecycler; +public class HomeFragment extends Fragment implements HomeAdapter.OnHomeClickListener { + + private RecyclerView recyclerView; + private HomeAdapter adapter; private ProgressBar mainProgressBar; private SwipeRefreshLayout swipeRefreshLayout; + private List currentEvents = new ArrayList<>(); + private List currentNews = new ArrayList<>(); private int activeNetworkCalls = 0; + private ActivityResultLauncher requestPermissionLauncher; + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestPermissionLauncher = registerForActivityResult( new ActivityResultContracts.RequestPermission(), - isGranted -> { - if (calendarRecycler != null) { - refreshData(); - } - } + isGranted -> refreshData() ); - // GAMMEL METODE FJERNET HERFRA (startNotificationWorker) } @Nullable @@ -66,76 +63,101 @@ public class HomeFragment extends Fragment { super.onViewCreated(view, savedInstanceState); mainProgressBar = view.findViewById(R.id.main_loading_spinner); - if (mainProgressBar != null) mainProgressBar.setVisibility(View.VISIBLE); - swipeRefreshLayout = view.findViewById(R.id.swipe_refresh_home); + recyclerView = view.findViewById(R.id.main_recycler_view); + + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); swipeRefreshLayout.setOnRefreshListener(this::refreshData); - View profileBtn = view.findViewById(R.id.btn_profile); - if (profileBtn != null) { - profileBtn.setOnClickListener(v -> Navigation.findNavController(view).navigate(R.id.navigation_profile)); - } - - Button btnCreateEvent = view.findViewById(R.id.btn_create_event); - List writeable = UserManager.getInstance().getWriteableCalendars(); - if (writeable != null && !writeable.isEmpty()) { - btnCreateEvent.setVisibility(View.VISIBLE); - btnCreateEvent.setOnClickListener(v -> { - Navigation.findNavController(view).navigate(R.id.action_home_to_create_event); - }); - } else { - btnCreateEvent.setVisibility(View.GONE); - } - - calendarRecycler = view.findViewById(R.id.recycler_calendar); - calendarRecycler.setLayoutManager(new LinearLayoutManager(getContext())); - calendarRecycler.setAdapter(new CalendarAdapter(new ArrayList<>(), event -> {})); - - TextView viewAllCalendar = view.findViewById(R.id.btn_view_all_calendar); - if (viewAllCalendar != null) { - viewAllCalendar.setOnClickListener(v -> Navigation.findNavController(view).navigate(R.id.action_home_to_calendarFull)); - } - if (android.os.Build.VERSION.SDK_INT >= 33) { if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {}).launch(Manifest.permission.POST_NOTIFICATIONS); + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); } } - newsRecycler = view.findViewById(R.id.recycler_news); - newsRecycler.setLayoutManager(new LinearLayoutManager(getContext())); - newsRecycler.setNestedScrollingEnabled(false); - newsRecycler.setAdapter(new NewsAdapter(new ArrayList<>(), item -> {})); - - TextView viewAllNews = view.findViewById(R.id.btn_view_all_news); - if (viewAllNews != null) { - viewAllNews.setOnClickListener(v -> { - Navigation.findNavController(view).navigate(R.id.action_home_to_newsFull); - }); - } - refreshData(); } - @Override - public void onResume() { - super.onResume(); - if (activeNetworkCalls == 0) { - refreshData(); - } + private void refreshData() { + if (activeNetworkCalls > 0) return; + activeNetworkCalls = 2; + if (mainProgressBar != null) mainProgressBar.setVisibility(View.VISIBLE); + + fetchCalendarData(); + fetchNewsData(); } - private void refreshData() { - activeNetworkCalls = 2; - - if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED) { - fetchCalendarEvents(calendarRecycler); - } else { - checkLoadingComplete(); + private void fetchCalendarData() { + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) { requestPermissionLauncher.launch(Manifest.permission.READ_CALENDAR); + currentEvents.clear(); + checkLoadingComplete(); + return; } - fetchNewsFromWordpress(newsRecycler); + new Thread(() -> { + List deviceEvents = CalendarManager.getDeviceEvents(getContext(), true); + new Handler(Looper.getMainLooper()).post(() -> { + RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + List 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> call, Throwable t) { + List cached = CacheManager.getCachedCalendarEvents(getContext()); + for (CalendarEvent e : cached) CalendarManager.formatEventForUI(e); + currentEvents = CalendarManager.mergeAndSort(cached, deviceEvents); + checkLoadingComplete(); + } + }); + }); + }).start(); + } + + private void fetchNewsData() { + RetrofitClient.getApiService().getPosts().enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + currentNews = response.body(); + CacheManager.saveNewsPosts(getContext(), currentNews); + } else { + currentNews = CacheManager.getCachedNewsPosts(getContext()); + } + formatNewsDates(currentNews); + checkLoadingComplete(); + } + + @Override + public void onFailure(Call> call, Throwable t) { + currentNews = CacheManager.getCachedNewsPosts(getContext()); + formatNewsDates(currentNews); + checkLoadingComplete(); + } + }); + } + + private void formatNewsDates(List posts) { + SimpleDateFormat rawFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); + SimpleDateFormat targetFormat = new SimpleDateFormat("dd. MMM yyyy", Locale.getDefault()); + for (WpPost post : posts) { + try { + Date date = rawFormat.parse(post.date); + post.date = targetFormat.format(date); + } catch (Exception ignored) {} + } } private void checkLoadingComplete() { @@ -143,140 +165,58 @@ public class HomeFragment extends Fragment { if (activeNetworkCalls <= 0) { activeNetworkCalls = 0; if (mainProgressBar != null) mainProgressBar.setVisibility(View.GONE); - if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(false); + swipeRefreshLayout.setRefreshing(false); + buildAndDisplayList(); } } - private void fetchCalendarEvents(RecyclerView recyclerView) { - new Thread(() -> { - List deviceEvents = CalendarManager.getDeviceEvents(getContext(), true); - new Handler(Looper.getMainLooper()).post(() -> fetchApiEvents(recyclerView, deviceEvents)); - }).start(); - } + private void buildAndDisplayList() { + List items = new ArrayList<>(); - private void fetchApiEvents(RecyclerView recyclerView, List deviceEvents) { - RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (!isAdded()) return; + // 1. Header + items.add(new HomeAdapter.HeaderItem()); - List apiEvents = new ArrayList<>(); - if (response.isSuccessful() && response.body() != null) { - apiEvents = response.body(); + // 2. Create Event Knapp (hvis tilgang) + if (!UserManager.getInstance().getWriteableCalendars().isEmpty()) { + items.add(new HomeAdapter.CreateButtonItem()); + } - CacheManager.saveCalendarEvents(getContext(), apiEvents); - - for (CalendarEvent e : apiEvents) { - CalendarManager.formatEventForUI(e); - } - } else { - apiEvents = CacheManager.getCachedCalendarEvents(getContext()); - for (CalendarEvent e : apiEvents) CalendarManager.formatEventForUI(e); - if (!apiEvents.isEmpty()) { - Toast.makeText(getContext(), "Server utilgjengelig. Viser lagret kalender.", Toast.LENGTH_SHORT).show(); - } - } - - updateCalendarUI(recyclerView, apiEvents, deviceEvents); - checkLoadingComplete(); - } - - @Override - public void onFailure(Call> call, Throwable t) { - if (!isAdded()) return; - - List cachedApiEvents = CacheManager.getCachedCalendarEvents(getContext()); - for (CalendarEvent e : cachedApiEvents) CalendarManager.formatEventForUI(e); - - if (!cachedApiEvents.isEmpty()) { - Toast.makeText(getContext(), "Ingen nettverk. Viser lagret kalender.", Toast.LENGTH_SHORT).show(); - } - - updateCalendarUI(recyclerView, cachedApiEvents, deviceEvents); - checkLoadingComplete(); - } - }); - } - - private void updateCalendarUI(RecyclerView recyclerView, List apiEvents, List deviceEvents) { - List merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents); - List upcomingEvents = new ArrayList<>(); + // 3. Kalender Seksjon + items.add(new HomeAdapter.SectionTitleItem("Kommende hendelser", true)); String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date()); - - for (CalendarEvent e : merged) { + int count = 0; + for (CalendarEvent e : currentEvents) { if (e.getRawDate() != null && e.getRawDate().compareTo(today) >= 0) { - upcomingEvents.add(e); + items.add(e); + count++; } + if (count >= 3) break; } - List topEvents = new ArrayList<>(); - for(int i=0; i { - CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event); - sheet.setOnEventChangeListener(HomeFragment.this::refreshData); - sheet.show(getParentFragmentManager(), "CalendarDetails"); - })); - } - - private void fetchNewsFromWordpress(RecyclerView recyclerView) { - WordPressApiService apiService = RetrofitClient.getApiService(); - apiService.getPosts().enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (getContext() == null) return; - - List postsToShow = new ArrayList<>(); - - if (response.isSuccessful() && response.body() != null) { - postsToShow = response.body(); - CacheManager.saveNewsPosts(getContext(), postsToShow); - } else { - postsToShow = CacheManager.getCachedNewsPosts(getContext()); - if (!postsToShow.isEmpty()) { - Toast.makeText(getContext(), "Server utilgjengelig. Viser lagrede nyheter.", Toast.LENGTH_SHORT).show(); - } - } - - updateNewsUI(recyclerView, postsToShow); - checkLoadingComplete(); - } - - @Override - public void onFailure(Call> call, Throwable t) { - if (getContext() == null) return; - - List cachedPosts = CacheManager.getCachedNewsPosts(getContext()); - if (!cachedPosts.isEmpty()) { - Toast.makeText(getContext(), "Ingen nettverk. Viser lagrede nyheter.", Toast.LENGTH_SHORT).show(); - } - - updateNewsUI(recyclerView, cachedPosts); - checkLoadingComplete(); - } - }); - } - - private void updateNewsUI(RecyclerView recyclerView, List posts) { - SimpleDateFormat rawFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); - rawFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); - SimpleDateFormat targetFormat = new SimpleDateFormat("dd. MMM yyyy", Locale.getDefault()); - targetFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); - - for (WpPost post : posts) { - try { - Date date = rawFormat.parse(post.date); - post.date = targetFormat.format(date); - } catch (Exception e) {} - } - - NewsAdapter adapter = new NewsAdapter(posts, post -> { - Bundle bundle = new Bundle(); - bundle.putSerializable("post_data", post); - Navigation.findNavController(getView()).navigate(R.id.action_home_to_newsDetail, bundle); - }); + adapter = new HomeAdapter(items, this); recyclerView.setAdapter(adapter); } + + // --- KLIKKHÅNDTERING (Implementert fra HomeAdapter.OnHomeClickListener) --- + + @Override public void onProfileClick() { Navigation.findNavController(getView()).navigate(R.id.navigation_profile); } + @Override public void onCreateEventClick() { Navigation.findNavController(getView()).navigate(R.id.action_home_to_create_event); } + @Override public void onViewAllCalendarClick() { Navigation.findNavController(getView()).navigate(R.id.action_home_to_calendarFull); } + @Override public void onViewAllNewsClick() { Navigation.findNavController(getView()).navigate(R.id.action_home_to_newsFull); } + + @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); + } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 4bc0253..40c7b90 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -5,145 +5,26 @@ android:layout_height="match_parent" android:background="@color/kbs_very_light_blue"> - - + + android:layout_height="match_parent" + android:clipToPadding="false" + android:paddingBottom="16dp" /> - - - - - - - - - - -