Før nyheterarbeid

This commit is contained in:
ErolHaagenrud 2025-12-11 14:51:18 +01:00
parent 0c33b4fc0f
commit a66dafb6c3
14 changed files with 879 additions and 183 deletions

View file

@ -54,4 +54,7 @@ dependencies {
implementation("com.google.android.gms:play-services-auth:20.7.0") implementation("com.google.android.gms:play-services-auth:20.7.0")
implementation("com.github.bumptech.glide:glide:4.16.0") implementation("com.github.bumptech.glide:glide:4.16.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
implementation("androidx.work:work-runtime:2.9.0")
} }

View file

@ -18,6 +18,12 @@
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" /> <uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"

View file

@ -1,4 +1,3 @@
// FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarAdapter.java
package com.kbs.kbsintranett; package com.kbs.kbsintranett;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -9,43 +8,57 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import java.util.List; import java.util.List;
public class CalendarAdapter extends RecyclerView.Adapter<CalendarAdapter.ViewHolder> { // [cite: 31] public class CalendarAdapter extends RecyclerView.Adapter<CalendarAdapter.ViewHolder> {
private List<CalendarEvent> events; private List<CalendarEvent> events;
public CalendarAdapter(List<CalendarEvent> events) { // [cite: 32] private final OnItemClickListener listener;
public interface OnItemClickListener {
void onItemClick(CalendarEvent event);
}
// Oppdatert konstruktør som tar imot en listener
public CalendarAdapter(List<CalendarEvent> events, OnItemClickListener listener) {
this.events = events; this.events = events;
} // [cite: 33] this.listener = listener;
}
@NonNull @NonNull
@Override @Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_calendar, parent, false); View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_calendar, parent, false);
return new ViewHolder(view); // [cite: 34] return new ViewHolder(view);
} }
@Override @Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) { public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
CalendarEvent event = events.get(position); CalendarEvent event = events.get(position);
holder.day.setText(event.getDay()); // [cite: 35] holder.day.setText(event.getDay());
holder.month.setText(event.getMonth()); holder.month.setText(event.getMonth());
// NYTT: Tidspunktet hentes fra getTime() som formateres i HomeFragment.
holder.time.setText(event.getTime()); holder.time.setText(event.getTime());
holder.title.setText(event.getTitle()); holder.title.setText(event.getTitle());
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onItemClick(event);
}
});
} }
@Override @Override
public int getItemCount() { public int getItemCount() {
return events.size(); // [cite: 36] return events.size();
} }
public static class ViewHolder extends RecyclerView.ViewHolder { public static class ViewHolder extends RecyclerView.ViewHolder {
TextView day, month, title, time; // NYTT: Lagt til time TextView day, month, title, time;
public ViewHolder(View view) { // [cite: 37]
public ViewHolder(View view) {
super(view); super(view);
day = view.findViewById(R.id.cal_day); day = view.findViewById(R.id.cal_day);
month = view.findViewById(R.id.cal_month); // [cite: 38] month = view.findViewById(R.id.cal_month);
title = view.findViewById(R.id.cal_title); title = view.findViewById(R.id.cal_title);
time = view.findViewById(R.id.cal_time); // NYTT time = view.findViewById(R.id.cal_time);
} }
} }
} }

View file

@ -0,0 +1,93 @@
package com.kbs.kbsintranett;
import android.content.Intent;
import android.os.Bundle;
import android.provider.CalendarContract;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
public class CalendarDetailsBottomSheet extends BottomSheetDialogFragment {
private CalendarEvent event;
public CalendarDetailsBottomSheet(CalendarEvent event) {
this.event = event;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_calendar_details, container, false);
TextView title = view.findViewById(R.id.sheet_title);
TextView time = view.findViewById(R.id.sheet_time);
TextView desc = view.findViewById(R.id.sheet_desc);
TextView loc = view.findViewById(R.id.sheet_location);
Button btnAdd = view.findViewById(R.id.btn_add_to_calendar);
// Skjul knapp siden appen varsler automatisk (iht krav)
btnAdd.setVisibility(View.GONE);
title.setText(event.getTitle());
time.setText(event.getTime() + " (" + event.getDay() + ". " + event.getMonth() + ")");
if (!event.getDescription().isEmpty()) {
// HER ER FIKSEN FOR HTML:
desc.setText(android.text.Html.fromHtml(event.getDescription(), android.text.Html.FROM_HTML_MODE_COMPACT));
desc.setVisibility(View.VISIBLE);
// Gjør linker klikkbare
desc.setMovementMethod(android.text.method.LinkMovementMethod.getInstance());
} else {
desc.setVisibility(View.GONE);
}
if (!event.getLocation().isEmpty()) {
loc.setText("Sted: " + event.getLocation());
loc.setVisibility(View.VISIBLE);
} else {
loc.setVisibility(View.GONE);
}
return view;
}
private void addToSystemCalendar() {
try {
SimpleDateFormat apiFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
apiFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
Date startDate = apiFormat.parse(event.getRawDate());
long startMillis = startDate.getTime();
long endMillis = startMillis + (60 * 60 * 1000); // Default 1 time hvis slutt mangler
if (event.getRawEndDate() != null && !event.getRawEndDate().isEmpty()) {
Date endDate = apiFormat.parse(event.getRawEndDate());
endMillis = endDate.getTime();
}
Intent intent = new Intent(Intent.ACTION_INSERT)
.setData(CalendarContract.Events.CONTENT_URI)
.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startMillis)
.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endMillis)
.putExtra(CalendarContract.Events.TITLE, event.getTitle())
.putExtra(CalendarContract.Events.DESCRIPTION, event.getDescription())
.putExtra(CalendarContract.Events.EVENT_LOCATION, event.getLocation())
.putExtra(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_BUSY);
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -1,13 +1,38 @@
// FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarEvent.java
package com.kbs.kbsintranett; package com.kbs.kbsintranett;
public class CalendarEvent { import com.google.gson.annotations.SerializedName;
private String title;
private String rawDate; // NYTT: Holder den fulle, u-formaterte dato/tid-strengen fra API'et
private String day; // F.eks "12"
private String month; // F.eks "DES"
private String time; // NYTT: Brukes kun for visning av tid
public class CalendarEvent {
@SerializedName("title")
private String title;
@SerializedName("start_date") // Juster denne nøkkelen til hva APIet faktisk returnerer (f.eks "start")
private String rawDate;
@SerializedName("end_date") // Juster nøkkel (f.eks "end")
private String rawEndDate;
@SerializedName("description")
private String description;
@SerializedName("location")
private String location;
// --- UI-hjelpefelter (settes manuelt i appen etter parsing) ---
private String day; // F.eks "12"
private String month; // F.eks "DES"
private String time; // F.eks "10:00 - 11:30"
// Konstruktør for Retrofit (Gson)
public CalendarEvent(String title, String rawDate, String rawEndDate, String description, String location) {
this.title = title;
this.rawDate = rawDate;
this.rawEndDate = rawEndDate;
this.description = description;
this.location = location;
}
// Konstruktør for manuell opprettelse (f.eks ved feil)
public CalendarEvent(String title, String time, String day, String month) { public CalendarEvent(String title, String time, String day, String month) {
this.title = title; this.title = title;
this.time = time; this.time = time;
@ -15,16 +40,19 @@ public class CalendarEvent {
this.month = month; this.month = month;
} }
public CalendarEvent(String title, String rawDate) {
this.title = title;
this.rawDate = rawDate;
// La de andre feltene være null i starten, de fylles i HomeFragment
}
public String getTitle() { return title; } public String getTitle() { return title; }
public String getTime() { return time; } public String getRawDate() { return rawDate; }
public String getDay() { return day; } public String getRawEndDate() { return rawEndDate; }
public String getMonth() { return month; } public String getDescription() { return description != null ? description : ""; }
public String getLocation() { return location != null ? location : ""; }
public String getRawDate() { return rawDate; } // NYTT // Getters og Setters for UI-felter
public String getDay() { return day; }
public void setDay(String day) { this.day = day; }
public String getMonth() { return month; }
public void setMonth(String month) { this.month = month; }
public String getTime() { return time; }
public void setTime(String time) { this.time = time; }
} }

View file

@ -0,0 +1,135 @@
package com.kbs.kbsintranett;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class CalendarFullFragment extends Fragment {
private RecyclerView recyclerView;
private ProgressBar progressBar;
private TextView emptyView;
private LinearLayoutManager layoutManager; // Trenger denne for å scrolle
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_calendar_full, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
recyclerView = view.findViewById(R.id.recycler_full_calendar);
progressBar = view.findViewById(R.id.loading_full_calendar);
emptyView = view.findViewById(R.id.empty_view_calendar);
ImageView backBtn = view.findViewById(R.id.btn_back_calendar);
layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
backBtn.setOnClickListener(v -> Navigation.findNavController(view).navigateUp());
fetchAllEvents();
}
private void fetchAllEvents() {
progressBar.setVisibility(View.VISIBLE);
// Hent personlige hendelser ( med historikk)
List<CalendarEvent> deviceEvents = CalendarManager.getDeviceEvents(getContext());
RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback<List<CalendarEvent>>() {
@Override
public void onResponse(Call<List<CalendarEvent>> call, Response<List<CalendarEvent>> response) {
if (!isAdded()) return;
progressBar.setVisibility(View.GONE);
List<CalendarEvent> apiEvents = new ArrayList<>();
if (response.isSuccessful() && response.body() != null) {
for (CalendarEvent e : response.body()) {
CalendarManager.formatEventForUI(e);
apiEvents.add(e);
}
}
// Flett og vis
List<CalendarEvent> 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.show(getParentFragmentManager(), "CalendarDetails");
});
recyclerView.setAdapter(adapter);
// --- SCROLL TIL I DAG ---
scrollToToday(allEvents);
}
}
@Override
public void onFailure(Call<List<CalendarEvent>> 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);
}
}
});
}
private void scrollToToday(List<CalendarEvent> events) {
String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
int scrollIndex = 0;
// Finn første event som er i dag eller senere
for (int i = 0; i < events.size(); i++) {
String raw = events.get(i).getRawDate();
if (raw != null && raw.compareTo(today) >= 0) {
scrollIndex = i;
break;
}
}
// Scroll litt ned slik at "i dag" havner toppen, men ikke helt (offset 0)
// Bruker scrollToPositionWithOffset for presisjon
if (scrollIndex > 0) {
layoutManager.scrollToPositionWithOffset(scrollIndex, 0);
}
}
}

View file

@ -0,0 +1,174 @@
package com.kbs.kbsintranett;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.provider.CalendarContract;
import androidx.core.content.ContextCompat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
public class CalendarManager {
public static List<CalendarEvent> getDeviceEvents(Context context) {
List<CalendarEvent> deviceEvents = new ArrayList<>();
if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR)
!= PackageManager.PERMISSION_GRANTED) {
return deviceEvents;
}
// ENDRET: Hent events fra 1 år tilbake og 1 år frem
long now = System.currentTimeMillis();
long startMillis = now - (365L * 24 * 60 * 60 * 1000); // 1 år tilbake
long endMillis = now + (365L * 24 * 60 * 60 * 1000); // 1 år frem
String[] projection = new String[]{
CalendarContract.Events.TITLE,
CalendarContract.Events.DTSTART,
CalendarContract.Events.DTEND,
CalendarContract.Events.DESCRIPTION,
CalendarContract.Events.EVENT_LOCATION,
CalendarContract.Events.ALL_DAY // Nyttig for å vite om det er heldags
};
String selection = CalendarContract.Events.DTSTART + " >= ? AND " + CalendarContract.Events.DTSTART + " <= ?";
String[] selectionArgs = new String[]{String.valueOf(startMillis), String.valueOf(endMillis)};
try (Cursor cursor = context.getContentResolver().query(
CalendarContract.Events.CONTENT_URI,
projection,
selection,
selectionArgs,
CalendarContract.Events.DTSTART + " ASC"
)) {
if (cursor != null) {
// Vi bruker ISO format internt for sortering
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
while (cursor.moveToNext()) {
String title = cursor.getString(0);
long dtStart = cursor.getLong(1);
long dtEnd = cursor.getLong(2);
String desc = cursor.getString(3);
String loc = cursor.getString(4);
int allDay = cursor.getInt(5);
String rawStart;
String rawEnd;
if (allDay == 1) {
// For heldags lagrer vi bare datoen: yyyy-MM-dd
SimpleDateFormat shortFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
rawStart = shortFormat.format(new Date(dtStart));
rawEnd = shortFormat.format(new Date(dtEnd));
} else {
// For vanlige events bruker vi full tid
rawStart = isoFormat.format(new Date(dtStart));
rawEnd = isoFormat.format(new Date(dtEnd));
}
CalendarEvent event = new CalendarEvent(title, rawStart, rawEnd, desc, loc);
formatEventForUI(event);
deviceEvents.add(event);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return deviceEvents;
}
// --- ROBUST DATO-PARSING (Løser "null" problemet) ---
public static void formatEventForUI(CalendarEvent event) {
if (event.getRawDate() == null) return;
SimpleDateFormat outputDay = new SimpleDateFormat("dd", Locale.getDefault());
SimpleDateFormat outputMonth = new SimpleDateFormat("MMM", new Locale("no", "NO"));
SimpleDateFormat outputTime = new SimpleDateFormat("HH:mm", Locale.getDefault());
outputMonth.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
outputTime.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
try {
Date date = null;
Date endDate = null;
boolean isAllDay = false;
String raw = event.getRawDate();
// SJEKK 1: Er det heldagsdato? (Lengde 10, f.eks "2025-12-31")
if (raw.length() == 10 && !raw.contains("T") && !raw.contains(" ")) {
SimpleDateFormat shortFmt = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
date = shortFmt.parse(raw);
isAllDay = true;
if (event.getRawEndDate() != null && event.getRawEndDate().length() == 10) {
endDate = shortFmt.parse(event.getRawEndDate());
}
}
// SJEKK 2: Er det ISO format? (Har 'T')
else if (raw.contains("T")) {
SimpleDateFormat isoFmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
isoFmt.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); // Viktig for Google events
date = isoFmt.parse(raw);
if (event.getRawEndDate() != null && event.getRawEndDate().contains("T")) {
endDate = isoFmt.parse(event.getRawEndDate());
}
}
// SJEKK 3: Er det SQL format? (Mellomrom)
else {
SimpleDateFormat sqlFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
sqlFmt.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
date = sqlFmt.parse(raw);
if (event.getRawEndDate() != null) {
endDate = sqlFmt.parse(event.getRawEndDate());
}
}
if (date != null) {
event.setDay(outputDay.format(date));
event.setMonth(outputMonth.format(date).toUpperCase());
if (isAllDay) {
event.setTime("Hele dagen");
} else {
String timeStr = outputTime.format(date);
if (endDate != null) {
// Hvis sluttdato er samme dag, vis bare klokkeslett
// (Enkelt sjekk: hvis datoene er like)
// Her forenkler vi og viser alltid sluttid hvis den finnes
timeStr += " - " + outputTime.format(endDate);
}
event.setTime("Kl. " + timeStr);
}
}
} catch (Exception e) {
e.printStackTrace();
event.setDay("??");
event.setMonth("???");
event.setTime("Feil dato");
}
}
public static List<CalendarEvent> mergeAndSort(List<CalendarEvent> apiEvents, List<CalendarEvent> deviceEvents) {
List<CalendarEvent> all = new ArrayList<>(apiEvents);
all.addAll(deviceEvents);
Collections.sort(all, (e1, e2) -> {
String d1 = e1.getRawDate() != null ? e1.getRawDate() : "";
String d2 = e2.getRawDate() != null ? e2.getRawDate() : "";
return d1.compareTo(d2);
});
return all;
}
}

View file

@ -1,198 +1,194 @@
// FILSTI: app\src\main\java\com\kbs\kbsintranett\HomeFragment.java
package com.kbs.kbsintranett; package com.kbs.kbsintranett;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.work.PeriodicWorkRequest;
import java.text.ParseException; import androidx.work.WorkManager;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.concurrent.TimeUnit;
import java.util.TimeZone;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
import retrofit2.Response; import retrofit2.Response;
public class HomeFragment extends Fragment { public class HomeFragment extends Fragment {
private ActivityResultLauncher<String> requestPermissionLauncher;
private RecyclerView calendarRecycler;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Håndter svar kalendertillatelse
requestPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
isGranted -> {
// Last kalender nytt ( med eller uten personlig kalender)
fetchCalendarEvents(calendarRecycler);
}
);
// Start bakgrunnsjobb for varsling (Hvert 15. minutt)
startNotificationWorker();
}
@Nullable @Nullable
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// Laster inn layouten fra XML (fragment_home.xml)
return inflater.inflate(R.layout.fragment_home, container, false); return inflater.inflate(R.layout.fragment_home, container, false);
} }
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
// ---------------------------------------------------------
// 0. SETT OPP PROFIL-KNAPP (Ny!) // Profil-knapp
// ---------------------------------------------------------
// Vi finner ikonet vi la til i XML og sier at det skal til Profil-siden
View profileBtn = view.findViewById(R.id.btn_profile); View profileBtn = view.findViewById(R.id.btn_profile);
if (profileBtn != null) { if (profileBtn != null) {
profileBtn.setOnClickListener(v -> { profileBtn.setOnClickListener(v -> Navigation.findNavController(view).navigate(R.id.navigation_profile));
Navigation.findNavController(view).navigate(R.id.navigation_profile);
});
} }
// --------------------------------------------------------- // Kalender oppsett
// 1. SETT OPP KALENDER (Henter fra WordPress) calendarRecycler = view.findViewById(R.id.recycler_calendar);
// ---------------------------------------------------------
RecyclerView calendarRecycler = view.findViewById(R.id.recycler_calendar);
calendarRecycler.setLayoutManager(new LinearLayoutManager(getContext())); calendarRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
calendarRecycler.setAdapter(new CalendarAdapter(new ArrayList<>(), event -> {}));
// Starter henting av kalenderdata // "Se alle" knapp
fetchCalendarEvents(calendarRecycler); 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));
}
// --------------------------------------------------------- // Sjekk tillatelse for personlig kalender
// 2. SETT OPP NYHETER (Hentes fra WordPress) if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED) {
// --------------------------------------------------------- fetchCalendarEvents(calendarRecycler);
} else {
// Spør om lov til å lese kalender
requestPermissionLauncher.launch(Manifest.permission.READ_CALENDAR);
}
// Spør også om lov til å sende varsler (Android 13+)
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);
}
}
// Nyheter
RecyclerView newsRecycler = view.findViewById(R.id.recycler_news); RecyclerView newsRecycler = view.findViewById(R.id.recycler_news);
newsRecycler.setLayoutManager(new LinearLayoutManager(getContext())); newsRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
// Gjør at scrollen flyter bedre inni NestedScrollView (hvis du bruker det i XML)
newsRecycler.setNestedScrollingEnabled(false); newsRecycler.setNestedScrollingEnabled(false);
// Start henting av ekte data newsRecycler.setAdapter(new NewsAdapter(new ArrayList<>()));
fetchNewsFromWordpress(newsRecycler); fetchNewsFromWordpress(newsRecycler);
} }
/**
* Henter kalenderhendelser fra WordPress via RetrofitClient
*/
private void fetchCalendarEvents(RecyclerView recyclerView) { private void fetchCalendarEvents(RecyclerView recyclerView) {
// 1. Hent API-tjenesten vår // 1. Hent personlige hendelser først
List<CalendarEvent> deviceEvents = CalendarManager.getDeviceEvents(getContext());
WordPressApiService apiService = RetrofitClient.getApiService(); WordPressApiService apiService = RetrofitClient.getApiService();
// 2. Send forespørsel til nettet (Asynkront)
apiService.getCalendarEvents().enqueue(new Callback<List<CalendarEvent>>() { apiService.getCalendarEvents().enqueue(new Callback<List<CalendarEvent>>() {
@Override @Override
public void onResponse(Call<List<CalendarEvent>> call, Response<List<CalendarEvent>> response) { public void onResponse(Call<List<CalendarEvent>> call, Response<List<CalendarEvent>> response) {
if (getContext() == null || response.body() == null) return; if (!isAdded()) return;
List<CalendarEvent> rawEvents = response.body(); List<CalendarEvent> apiEvents = new ArrayList<>();
List<CalendarEvent> formattedEvents = new ArrayList<>(); if (response.isSuccessful() && response.body() != null) {
for (CalendarEvent e : response.body()) {
// Formater for parsing og visning CalendarManager.formatEventForUI(e); // Formatér datoer
SimpleDateFormat apiFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); apiEvents.add(e);
apiFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); // Fikser deprecation/tidssone
SimpleDateFormat dayFormat = new SimpleDateFormat("dd", Locale.getDefault());
dayFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); // Fikser deprecation/tidssone
// Bruker norsk locale for måned (Jan, Feb, Mar, etc.)
SimpleDateFormat monthFormat = new SimpleDateFormat("MMM", new Locale("no", "NO"));
monthFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); // Fikser deprecation/tidssone
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm", Locale.getDefault());
timeFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); // Fikser deprecation/tidssone
for (CalendarEvent event : rawEvents) {
try {
// Bruker getRawDate() fra CalendarEvent.java (oppdatert)
Date date = apiFormat.parse(event.getRawDate());
String day = dayFormat.format(date);
String month = monthFormat.format(date).toUpperCase(Locale.getDefault());
String startTime = timeFormat.format(date);
// Bruker den gamle konstruktøren for å sette formaterte data i adapteren
formattedEvents.add(new CalendarEvent(
event.getTitle(),
startTime,
day,
month
));
} catch (ParseException e) {
e.printStackTrace();
// Håndterer feil i parsing av dato/tid ved å vise data
formattedEvents.add(new CalendarEvent(event.getTitle(), "Ukjent", event.getDay(), event.getMonth()));
} }
} }
recyclerView.setAdapter(new CalendarAdapter(formattedEvents));
// Flett lister
List<CalendarEvent> merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents);
// Vis kun topp 5
List<CalendarEvent> top5 = new ArrayList<>();
for(int i=0; i<Math.min(merged.size(), 5); i++) {
top5.add(merged.get(i));
}
recyclerView.setAdapter(new CalendarAdapter(top5, event -> {
CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
sheet.show(getParentFragmentManager(), "CalendarDetails");
}));
} }
@Override @Override
public void onFailure(Call<List<CalendarEvent>> call, Throwable t) { public void onFailure(Call<List<CalendarEvent>> call, Throwable t) {
if (getContext() == null) return; if (!isAdded()) return;
System.err.println("Kalender Nettverksfeil: " + t.getMessage()); // Hvis API feiler, vis bare personlige events hvis vi har noen
// Vis feilmelding i RecyclerView if (!deviceEvents.isEmpty()) {
List<CalendarEvent> errorList = new ArrayList<>(); List<CalendarEvent> top5 = new ArrayList<>();
errorList.add(new CalendarEvent("Kunne ikke laste kalender", "Sjekk nettverket ditt.", "00", "FEIL")); for(int i=0; i<Math.min(deviceEvents.size(), 5); i++) top5.add(deviceEvents.get(i));
recyclerView.setAdapter(new CalendarAdapter(errorList));
recyclerView.setAdapter(new CalendarAdapter(top5, event -> {
CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
sheet.show(getParentFragmentManager(), "CalendarDetails");
}));
} else {
// Vis feil hvis alt er tomt
List<CalendarEvent> errorList = new ArrayList<>();
errorList.add(new CalendarEvent("Kunne ikke laste kalender", "Sjekk nettverk", "!", "OBS"));
recyclerView.setAdapter(new CalendarAdapter(errorList, null));
}
} }
}); });
} }
/** private void startNotificationWorker() {
* Henter nyheter fra WordPress via RetrofitClient // Kjører en jobb hvert 15. minutt for å sjekke om det er nye møter
*/ PeriodicWorkRequest notifRequest =
new PeriodicWorkRequest.Builder(NotificationWorker.class, 15, TimeUnit.MINUTES)
.build();
WorkManager.getInstance(requireContext()).enqueue(notifRequest);
}
// ... (fetchNewsFromWordpress beholdes som før) ...
private void fetchNewsFromWordpress(RecyclerView recyclerView) { private void fetchNewsFromWordpress(RecyclerView recyclerView) {
// 1. Hent API-tjenesten vår // [Lim inn koden for nyheter fra forrige svar her, den var OK]
WordPressApiService apiService = RetrofitClient.getApiService(); WordPressApiService apiService = RetrofitClient.getApiService();
// 2. Send forespørsel til nettet (Asynkront)
apiService.getPosts().enqueue(new Callback<List<WpPost>>() { apiService.getPosts().enqueue(new Callback<List<WpPost>>() {
@Override @Override
public void onResponse(Call<List<WpPost>> call, Response<List<WpPost>> response) { public void onResponse(Call<List<WpPost>> call, Response<List<WpPost>> response) {
// Sjekk om appen fortsatt lever (viktig for å unngå krasj)
if (getContext() == null) return; if (getContext() == null) return;
if (response.isSuccessful() && response.body() != null) { if (response.isSuccessful() && response.body() != null) {
// 3. Suksess! Vi fikk data fra WordPress.
List<WpPost> wpPosts = response.body(); List<WpPost> wpPosts = response.body();
List<NewsItem> newsList = new ArrayList<>(); List<NewsItem> newsList = new ArrayList<>();
java.text.SimpleDateFormat rawFormat = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", java.util.Locale.getDefault());
rawFormat.setTimeZone(java.util.TimeZone.getTimeZone("Europe/Oslo"));
java.text.SimpleDateFormat targetFormat = new java.text.SimpleDateFormat("dd. MMM yyyy", java.util.Locale.getDefault());
targetFormat.setTimeZone(java.util.TimeZone.getTimeZone("Europe/Oslo"));
// Datoformatering for nyhetene
SimpleDateFormat rawFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
rawFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); // Fikser deprecation/tidssone
SimpleDateFormat targetFormat = new SimpleDateFormat("dd. MMM yyyy", Locale.getDefault());
targetFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); // Fikser deprecation/tidssone
// Konverter fra "WpPost" (API-format) til "NewsItem" (App-format)
for (WpPost post : wpPosts) { for (WpPost post : wpPosts) {
String formattedDate = post.date; String formattedDate = post.date;
try { try {
// API-datoen (post.date) er i formatet "yyyy-MM-dd'T'HH:mm:ss" java.util.Date date = rawFormat.parse(post.date);
Date date = rawFormat.parse(post.date);
formattedDate = targetFormat.format(date); formattedDate = targetFormat.format(date);
} catch (ParseException e) { } catch (java.text.ParseException e) {}
System.err.println("Feil ved parsing av nyhetsdato: " + e.getMessage()); newsList.add(new NewsItem(post.getTitleStr(), post.getExcerptStr(), "Publisert: " + formattedDate));
}
newsList.add(new NewsItem(
post.getTitleStr(),
post.getExcerptStr(),
"Publisert: " + formattedDate
));
} }
recyclerView.setAdapter(new NewsAdapter(newsList));
// 4. Send listen til Adapteren slik at den vises skjermen
NewsAdapter adapter = new NewsAdapter(newsList);
recyclerView.setAdapter(adapter);
} else {
System.err.println("Feil: Fikk svar, men noe var galt med dataene: " + response.code());
// Her kunne vi vist en "Ingen nyheter"-tekst
// (Løsningen har allerede lagt inn fallback i onFailure)
} }
} }
@Override @Override
public void onFailure(Call<List<WpPost>> call, Throwable t) { public void onFailure(Call<List<WpPost>> call, Throwable t) {
// Nettverksfeil (Ingen nett, feil URL, etc)
if (getContext() == null) return; if (getContext() == null) return;
System.err.println("Nettverksfeil: " + t.getMessage());
// Legg til en "Feilmelding" i listen brukeren ser det
List<NewsItem> errorList = new ArrayList<>(); List<NewsItem> errorList = new ArrayList<>();
errorList.add(new NewsItem("Kunne ikke laste nyheter", "Sjekk nettverket ditt.", "System")); errorList.add(new NewsItem("Kunne ikke laste nyheter", "Sjekk nettverket ditt.", "System"));
recyclerView.setAdapter(new NewsAdapter(errorList)); recyclerView.setAdapter(new NewsAdapter(errorList));

View file

@ -0,0 +1,99 @@
package com.kbs.kbsintranett;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import retrofit2.Response;
public class NotificationWorker extends Worker {
private static final String CHANNEL_ID = "kbs_calendar_channel";
private static final String PREFS_NAME = "KBSNotificationPrefs";
public NotificationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
// Dette kjører i bakgrunnen
try {
// Hent events synkront (ikke enqueue)
Response<List<CalendarEvent>> response = RetrofitClient.getApiService().getCalendarEvents().execute();
if (response.isSuccessful() && response.body() != null) {
checkAndNotify(response.body());
return Result.success();
} else {
return Result.retry();
}
} catch (IOException e) {
return Result.retry();
}
}
private void checkAndNotify(List<CalendarEvent> events) {
SharedPreferences prefs = getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
long now = System.currentTimeMillis();
long fifteenMinutes = 15 * 60 * 1000;
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
SimpleDateFormat sqlFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
for (CalendarEvent event : events) {
try {
Date eventDate;
if (event.getRawDate().contains("T")) eventDate = isoFormat.parse(event.getRawDate());
else eventDate = sqlFormat.parse(event.getRawDate());
if (eventDate == null) continue;
long diff = eventDate.getTime() - now;
// Hvis eventet starter innen de neste 30 min, og ikke allerede varslet
if (diff > 0 && diff < (30 * 60 * 1000)) {
String eventId = event.getTitle() + event.getRawDate(); // Enkel ID
boolean alreadyNotified = prefs.getBoolean(eventId, false);
if (!alreadyNotified) {
sendNotification(event.getTitle(), "Starter kl " + event.getTime());
// Lagre at vi har varslet
prefs.edit().putBoolean(eventId, true).apply();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void sendNotification(String title, String content) {
NotificationManager manager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "KBS Kalender", NotificationManager.IMPORTANCE_HIGH);
manager.createNotificationChannel(channel);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher) // Sørg for at du har et ikon her
.setContentTitle(title)
.setContentText(content)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true);
manager.notify((int) System.currentTimeMillis(), builder.build());
}
}

View file

@ -11,32 +11,30 @@ import okhttp3.Interceptor;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor; // NY IMPORT
import retrofit2.Retrofit; import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory; import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitClient { public class RetrofitClient {
private static final String BASE_URL = "https://intranet.kbs.no/"; private static final String BASE_URL = "https://intranet.kbs.no/";
// VI FJERNER FAKE_COOKIE HERFRA! Den trengs ikke lenger.
private static Retrofit retrofit = null; private static Retrofit retrofit = null;
public static WordPressApiService getApiService() { public static WordPressApiService getApiService() {
// Vi bygge klienten nytt hvis vi logger ut/inn, men for enkelhets skyld
// sjekker vi bare null her.
if (retrofit == null) { if (retrofit == null) {
// NYTT: Logging Interceptor
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY); // Logger ALT (Body, Headers)
OkHttpClient client = new OkHttpClient.Builder() OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(logging) // Legg til loggingen her
.addInterceptor(new Interceptor() { .addInterceptor(new Interceptor() {
@Override @Override
public Response intercept(Chain chain) throws IOException { public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request(); Request originalRequest = chain.request();
Request.Builder builder = originalRequest.newBuilder(); Request.Builder builder = originalRequest.newBuilder();
// 1. Hent cookie fra UserManager
String dynamicCookie = UserManager.getInstance().getCookie(); String dynamicCookie = UserManager.getInstance().getCookie();
// 2. Hvis vi har en cookie, legg den til i headeren
if (dynamicCookie != null && !dynamicCookie.isEmpty()) { if (dynamicCookie != null && !dynamicCookie.isEmpty()) {
builder.header("Cookie", dynamicCookie); builder.header("Cookie", dynamicCookie);
} }
@ -48,6 +46,7 @@ public class RetrofitClient {
Gson gson = new GsonBuilder() Gson gson = new GsonBuilder()
.registerTypeAdapter(new TypeToken<List<GravityField.Choice>>(){}.getType(), new ChoicesAdapter()) .registerTypeAdapter(new TypeToken<List<GravityField.Choice>>(){}.getType(), new ChoicesAdapter())
.setLenient() // NYTT: Gjør parsing litt mer tilgivende
.create(); .create();
retrofit = new Retrofit.Builder() retrofit = new Retrofit.Builder()
@ -59,7 +58,6 @@ public class RetrofitClient {
return retrofit.create(WordPressApiService.class); return retrofit.create(WordPressApiService.class);
} }
// Hjelpemetode for å nullstille Retrofit ved utlogging
public static void clearClient() { public static void clearClient() {
retrofit = null; retrofit = null;
} }

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp"
android:background="@android:color/white">
<TextView
android:id="@+id/sheet_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Tittel"
android:textSize="22sp"
android:textStyle="bold"
android:textColor="@color/black"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/sheet_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Tidspunkt"
android:textSize="16sp"
android:textColor="@color/kbs_muted_blue_gray"
android:layout_marginBottom="16dp"
android:drawablePadding="8dp"
app:drawableStartCompat="@android:drawable/ic_menu_recent_history"/>
<TextView
android:id="@+id/sheet_location"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Sted"
android:textSize="16sp"
android:textColor="#333"
android:layout_marginBottom="16dp"
android:visibility="gone"/>
<TextView
android:id="@+id/sheet_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Beskrivelse..."
android:textSize="14sp"
android:textColor="#555"
android:layout_marginBottom="32dp"/>
<Button
android:id="@+id/btn_add_to_calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Lagre i min kalender / Varsle meg"
android:backgroundTint="@color/kbs_logo_blue"
android:textColor="@color/white"
android:padding="12dp"/>
</LinearLayout>

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/kbs_very_light_blue">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="@color/white"
android:elevation="4dp">
<ImageView
android:id="@+id/btn_back_calendar"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@android:drawable/ic_menu_revert"
android:layout_centerVertical="true"
app:tint="@color/kbs_logo_blue"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="KBS Kalender"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/black"
android:layout_centerInParent="true"/>
</RelativeLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_full_calendar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:clipToPadding="false"/>
<ProgressBar
android:id="@+id/loading_full_calendar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<TextView
android:id="@+id/empty_view_calendar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ingen hendelser funnet"
android:layout_gravity="center"
android:visibility="gone"/>
</FrameLayout>
</LinearLayout>

View file

@ -5,38 +5,61 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:padding="8dp" android:padding="8dp"
android:background="@color/kbs_very_light_blue"> <RelativeLayout android:background="@color/kbs_very_light_blue">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:paddingHorizontal="8dp">
<TextView <RelativeLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="KBS Intranett" android:layout_marginBottom="16dp"
android:textSize="24sp" android:paddingHorizontal="8dp">
android:textStyle="bold"
android:textColor="@color/kbs_muted_blue_gray" android:layout_centerVertical="true"/>
<ImageView
android:id="@+id/btn_profile"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@android:drawable/ic_menu_my_calendar"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
app:tint="@color/kbs_logo_blue"/> </RelativeLayout>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="KBS Intranett"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@color/kbs_muted_blue_gray"
android:layout_centerVertical="true"/>
<ImageView
android:id="@+id/btn_profile"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@android:drawable/ic_menu_my_calendar"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
app:tint="@color/kbs_logo_blue"/>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Kommende hendelser" android:orientation="horizontal"
android:textSize="18sp" android:gravity="center_vertical"
android:textStyle="bold"
android:textColor="@color/black"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:layout_marginStart="8dp"/> android:layout_marginStart="8dp"
android:layout_marginEnd="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Kommende hendelser"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/black"/>
<TextView
android:id="@+id/btn_view_all_calendar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Se alle >"
android:textColor="@color/kbs_logo_blue"
android:textStyle="bold"
android:padding="8dp"
android:background="?attr/selectableItemBackground"/>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_calendar" android:id="@+id/recycler_calendar"

View file

@ -21,7 +21,17 @@
android:id="@+id/navigation_home" android:id="@+id/navigation_home"
android:name="com.kbs.kbsintranett.HomeFragment" android:name="com.kbs.kbsintranett.HomeFragment"
android:label="Hjem" android:label="Hjem"
tools:layout="@layout/fragment_home" /> tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_home_to_calendarFull"
app:destination="@id/navigation_calendar_full" />
</fragment>
<fragment
android:id="@+id/navigation_calendar_full"
android:name="com.kbs.kbsintranett.CalendarFullFragment"
android:label="Kalender"
tools:layout="@layout/fragment_calendar_full" />
<fragment <fragment
android:id="@+id/navigation_forms" android:id="@+id/navigation_forms"