Før ny chat

This commit is contained in:
ErolHaagenrud 2025-12-12 13:29:14 +01:00
parent a66dafb6c3
commit b74c8715ef
21 changed files with 2854 additions and 412 deletions

View file

@ -15,6 +15,38 @@ import java.util.TimeZone;
public class CalendarManager {
// NY HJELPEMETODE: Finner ID-ene til kalendere som tilhører @kbs.no
private static List<String> getKbsCalendarIds(Context context) {
List<String> ids = new ArrayList<>();
String[] projection = new String[] {
CalendarContract.Calendars._ID,
CalendarContract.Calendars.ACCOUNT_NAME
};
// Vi ser etter kontoer som slutter @kbs.no
// (SQL: account_name LIKE '%@kbs.no')
String selection = CalendarContract.Calendars.ACCOUNT_NAME + " LIKE ?";
String[] selectionArgs = new String[] {"%@kbs.no"};
try (Cursor cursor = context.getContentResolver().query(
CalendarContract.Calendars.CONTENT_URI,
projection,
selection,
selectionArgs,
null
)) {
if (cursor != null) {
while (cursor.moveToNext()) {
ids.add(cursor.getString(0)); // Legg til kalender-ID
}
}
} catch (Exception e) {
e.printStackTrace();
}
return ids;
}
public static List<CalendarEvent> getDeviceEvents(Context context) {
List<CalendarEvent> deviceEvents = new ArrayList<>();
@ -23,10 +55,18 @@ public class CalendarManager {
return deviceEvents;
}
// ENDRET: Hent events fra 1 år tilbake og 1 år frem
// 1. Finn først ID-ene til KBS-kalenderne
List<String> kbsCalendarIds = getKbsCalendarIds(context);
// Hvis ingen kbs-kalendere finnes telefonen, returner tom liste
if (kbsCalendarIds.isEmpty()) {
return deviceEvents;
}
// 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
long startMillis = now - (365L * 24 * 60 * 60 * 1000);
long endMillis = now + (365L * 24 * 60 * 60 * 1000);
String[] projection = new String[]{
CalendarContract.Events.TITLE,
@ -34,21 +74,39 @@ public class CalendarManager {
CalendarContract.Events.DTEND,
CalendarContract.Events.DESCRIPTION,
CalendarContract.Events.EVENT_LOCATION,
CalendarContract.Events.ALL_DAY // Nyttig for å vite om det er heldags
CalendarContract.Events.ALL_DAY
};
String selection = CalendarContract.Events.DTSTART + " >= ? AND " + CalendarContract.Events.DTSTART + " <= ?";
String[] selectionArgs = new String[]{String.valueOf(startMillis), String.valueOf(endMillis)};
// 2. Bygg opp spørringen for å filtrere disse ID-ene
// Resultatet blir noe sånt som: "((calendar_id = ?) OR (calendar_id = ?)) AND dtstart >= ? AND dtstart <= ?"
StringBuilder selection = new StringBuilder("(");
List<String> selectionArgsList = new ArrayList<>();
for (int i = 0; i < kbsCalendarIds.size(); i++) {
selection.append(CalendarContract.Events.CALENDAR_ID).append(" = ?");
selectionArgsList.add(kbsCalendarIds.get(i));
if (i < kbsCalendarIds.size() - 1) {
selection.append(" OR ");
}
}
selection.append(") AND ");
selection.append(CalendarContract.Events.DTSTART).append(" >= ? AND ");
selection.append(CalendarContract.Events.DTSTART).append(" <= ?");
selectionArgsList.add(String.valueOf(startMillis));
selectionArgsList.add(String.valueOf(endMillis));
String[] selectionArgs = selectionArgsList.toArray(new String[0]);
try (Cursor cursor = context.getContentResolver().query(
CalendarContract.Events.CONTENT_URI,
projection,
selection,
selection.toString(),
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()) {
@ -63,12 +121,10 @@ public class CalendarManager {
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));
}
@ -84,7 +140,7 @@ public class CalendarManager {
return deviceEvents;
}
// --- ROBUST DATO-PARSING (Løser "null" problemet) ---
// --- (formatEventForUI og mergeAndSort er uendret fra forrige versjon) ---
public static void formatEventForUI(CalendarEvent event) {
if (event.getRawDate() == null) return;
@ -102,32 +158,26 @@ public class CalendarManager {
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
isoFmt.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
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());
}
@ -142,9 +192,6 @@ public class CalendarManager {
} 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);

View file

@ -0,0 +1,69 @@
package com.kbs.kbsintranett;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class CategoryAdapter extends RecyclerView.Adapter<CategoryAdapter.ViewHolder> {
private List<String> categories;
private String selectedCategory = "Alle"; // Standardvalg
private OnCategoryClickListener listener;
public interface OnCategoryClickListener {
void onCategoryClick(String category);
}
public CategoryAdapter(List<String> categories, OnCategoryClickListener listener) {
this.categories = categories;
this.listener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_category, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
String category = categories.get(position);
holder.name.setText(category);
if (category.equals(selectedCategory)) {
// Valgt stil (Blå bakgrunn, hvit tekst)
holder.name.setBackgroundResource(R.drawable.bg_category_selected);
holder.name.setTextColor(Color.WHITE);
} else {
// Ikke valgt stil (Hvit bakgrunn, mørk tekst)
holder.name.setBackgroundResource(R.drawable.bg_category_unselected);
holder.name.setTextColor(Color.parseColor("#333333"));
}
holder.itemView.setOnClickListener(v -> {
selectedCategory = category;
notifyDataSetChanged(); // Oppdater alle for å flytte markering
listener.onCategoryClick(category);
});
}
@Override
public int getItemCount() {
return categories.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView name;
public ViewHolder(View view) {
super(view);
name = view.findViewById(R.id.category_name);
}
}
}

View file

@ -2,60 +2,177 @@ package com.kbs.kbsintranett;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
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 java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class FormsListFragment extends Fragment {
private LinearLayout formsContainer;
private ProgressBar progressBar;
private TextView errorText;
private static final Pattern TITLE_NUMBER_PATTERN = Pattern.compile("^(\\d+)[.\\s-]+\\s*(.*)");
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_forms_list, container, false);
LinearLayout formsContainer = view.findViewById(R.id.forms_container);
formsContainer = view.findViewById(R.id.forms_container);
// Legger til knappene for de ulike skjemaene
addFormButton(formsContainer, "1. Ansatteopplysninger", 1);
addFormButton(formsContainer, "4. RUH (Rapport om uønsket hendelse)", 4);
addFormButton(formsContainer, "9. Sikkerhetskurs / Kompetansebevis", 9);
addFormButton(formsContainer, "10. HMS-bekreftelse", 10);
addFormButton(formsContainer, "11. Egenmelding", 11);
addFormButton(formsContainer, "12. Sjekkliste for firmabil", 12);
addFormButton(formsContainer, "14. SJA (Sikker Jobbanalyse)", 14);
addFormButton(formsContainer, "15. Fraværsvarsel", 15);
addFormButton(formsContainer, "16. Refusjon utlegg", 16);
addFormButton(formsContainer, "21. Forberedelse til medarbeidersamtale", 21);
addFormButton(formsContainer, "22. Medarbeiderundersøkelse", 22);
progressBar = new ProgressBar(getContext());
LinearLayout.LayoutParams progressParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
progressParams.gravity = Gravity.CENTER;
progressBar.setLayoutParams(progressParams);
formsContainer.addView(progressBar);
errorText = new TextView(getContext());
errorText.setTextColor(Color.RED);
errorText.setVisibility(View.GONE);
errorText.setPadding(20, 20, 20, 20);
formsContainer.addView(errorText);
fetchFormsList();
return view;
}
private void fetchFormsList() {
RetrofitClient.getApiService().getFormsListMap().enqueue(new Callback<Map<String, GravityForm>>() {
@Override
public void onResponse(Call<Map<String, GravityForm>> call, Response<Map<String, GravityForm>> response) {
if (!isAdded()) return;
progressBar.setVisibility(View.GONE);
if (response.isSuccessful() && response.body() != null) {
List<GravityForm> activeForms = new ArrayList<>();
for (GravityForm form : response.body().values()) {
// 1. Sjekk om skjemaet er aktivt (Bruker metoden i GravityForm)
if (form.getIsActive()) {
activeForms.add(form);
}
}
// 2. Sorter basert tallet i tittelen
Collections.sort(activeForms, new Comparator<GravityForm>() {
@Override
public int compare(GravityForm f1, GravityForm f2) {
int num1 = extractNumber(f1.title);
int num2 = extractNumber(f2.title);
return Integer.compare(num1, num2);
}
});
populateList(activeForms);
} else {
String msg = "Kunne ikke hente skjemaer. Kode: " + response.code();
if (response.code() == 401 || response.code() == 403) {
msg += "\n(Mangler tilgang. Er du admin?)";
}
showError(msg);
}
}
@Override
public void onFailure(Call<Map<String, GravityForm>> call, Throwable t) {
if (!isAdded()) return;
progressBar.setVisibility(View.GONE);
showError("Nettverksfeil: " + t.getMessage());
}
});
}
private void populateList(List<GravityForm> forms) {
formsContainer.removeAllViews();
if (forms.isEmpty()) {
showError("Ingen aktive skjemaer funnet.");
return;
}
for (GravityForm form : forms) {
// FIX: form.id er allerede en int i GravityForm.java, vi trenger ikke parse
int formId = form.id;
String cleanTitle = cleanTitle(form.title);
addFormButton(formsContainer, cleanTitle, formId);
}
}
private void addFormButton(LinearLayout container, String title, int formId) {
Button btn = new Button(getContext());
btn.setText(title);
btn.setBackgroundColor(Color.parseColor("#0069B3")); // KBS Blå
btn.setBackgroundColor(Color.parseColor("#0069B3"));
btn.setTextColor(Color.WHITE);
btn.setPadding(30, 30, 30, 30);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(0, 0, 0, 20); // Litt avstand mellom knappene
params.setMargins(0, 0, 0, 20);
btn.setLayoutParams(params);
btn.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putInt("formId", formId);
// HER VAR FEILEN: Endret R.id.nav_forms til riktig action ID
Navigation.findNavController(v).navigate(R.id.action_formsListFragment_to_formsDetailFragment, bundle);
});
container.addView(btn);
}
private void showError(String message) {
if (formsContainer == null) return;
formsContainer.removeAllViews();
TextView tv = new TextView(getContext());
tv.setText(message);
tv.setTextColor(Color.RED);
tv.setTextSize(16);
formsContainer.addView(tv);
}
private int extractNumber(String title) {
if (title == null) return 9999;
Matcher m = TITLE_NUMBER_PATTERN.matcher(title.trim());
if (m.find()) {
try {
return Integer.parseInt(m.group(1));
} catch (NumberFormatException e) {
return 9999;
}
}
return 9999;
}
private String cleanTitle(String title) {
if (title == null) return "";
Matcher m = TITLE_NUMBER_PATTERN.matcher(title.trim());
if (m.find()) {
return m.group(2);
}
return title;
}
}

View file

@ -42,13 +42,15 @@ public class GravityField {
@SerializedName("content")
public String content;
// --- BRUKER ADAPTEREN HER ---
@JsonAdapter(InputsAdapter.class)
@SerializedName("inputs")
public List<GravityField> inputs;
// ---------------------------
@SerializedName("isHidden")
public boolean isHidden;
// NYTT: For å sjekke om feltet er Read Only (f.eks dato i refusjon)
@SerializedName("gwreadonly_enable")
public boolean readOnly;
@ -63,32 +65,19 @@ public class GravityField {
public String gpnfForm;
public static class Choice {
@SerializedName("text")
public String text;
@SerializedName("value")
public String value;
@SerializedName("text") public String text;
@SerializedName("value") public String value;
}
public static class ConditionalLogic {
@SerializedName("actionType")
public String actionType;
@SerializedName("logicType")
public String logicType;
@SerializedName("rules")
public List<Rule> rules;
@SerializedName("actionType") public String actionType;
@SerializedName("logicType") public String logicType;
@SerializedName("rules") public List<Rule> rules;
}
public static class Rule {
@SerializedName("fieldId")
public String fieldId;
@SerializedName("operator")
public String operator;
@SerializedName("value")
public String value;
@SerializedName("fieldId") public String fieldId;
@SerializedName("operator") public String operator;
@SerializedName("value") public String value;
}
}

View file

@ -13,9 +13,17 @@ public class GravityForm {
@SerializedName("description")
public String description;
// Endret til Object for å være robust mot både "1" (String) og 1 (Int) fra API
@SerializedName("is_active")
public String isActive; // "1" = Aktiv, "0" = Inaktiv
public Object isActive;
@SerializedName("fields")
public List<GravityField> fields;
// Hjelpemetode for å sjekke om skjemaet er aktivt
public boolean getIsActive() {
if (isActive == null) return false;
String s = isActive.toString();
return "1".equals(s) || "true".equalsIgnoreCase(s);
}
}

View file

@ -18,8 +18,12 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import retrofit2.Call;
import retrofit2.Callback;
@ -33,16 +37,19 @@ public class HomeFragment extends Fragment {
@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);
// Prøv å laste kalender nytt ( potensielt med personlig kalender)
if (calendarRecycler != null) {
fetchCalendarEvents(calendarRecycler);
}
}
);
// Start bakgrunnsjobb for varsling (Hvert 15. minutt)
// Start bakgrunnsjobb for varsling (kjører hver 15. minutt)
startNotificationWorker();
}
@ -56,18 +63,19 @@ public class HomeFragment extends Fragment {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Profil-knapp
// 0. Profil-knapp
View profileBtn = view.findViewById(R.id.btn_profile);
if (profileBtn != null) {
profileBtn.setOnClickListener(v -> Navigation.findNavController(view).navigate(R.id.navigation_profile));
}
// Kalender oppsett
// 1. Kalender oppsett
calendarRecycler = view.findViewById(R.id.recycler_calendar);
calendarRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
// Sett tom adapter midlertidig
calendarRecycler.setAdapter(new CalendarAdapter(new ArrayList<>(), event -> {}));
// "Se alle" knapp
// "Se alle" knapp for kalender
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));
@ -88,18 +96,29 @@ public class HomeFragment extends Fragment {
}
}
// Nyheter
// 2. Nyheter oppsett
RecyclerView newsRecycler = view.findViewById(R.id.recycler_news);
newsRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
newsRecycler.setNestedScrollingEnabled(false);
newsRecycler.setAdapter(new NewsAdapter(new ArrayList<>()));
// Sett tom adapter midlertidig
newsRecycler.setAdapter(new NewsAdapter(new ArrayList<>(), item -> {}));
// "Se alle" knapp for nyheter
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);
});
}
fetchNewsFromWordpress(newsRecycler);
}
private void fetchCalendarEvents(RecyclerView recyclerView) {
// 1. Hent personlige hendelser først
// 1. Hent personlige hendelser først (fra CalendarManager)
List<CalendarEvent> deviceEvents = CalendarManager.getDeviceEvents(getContext());
// 2. Hent API-hendelser fra WordPress
WordPressApiService apiService = RetrofitClient.getApiService();
apiService.getCalendarEvents().enqueue(new Callback<List<CalendarEvent>>() {
@Override
@ -114,13 +133,24 @@ public class HomeFragment extends Fragment {
}
}
// Flett lister
// 3. Flett listene (API + Personlig) og sorter
List<CalendarEvent> merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents);
// Vis kun topp 5
// 4. Filtrer ut hendelser som har passert (vis kun fremtidige + i dag)
// (CalendarManager henter 1 år bakover, vi filtrere for "Topp 5 kommende")
List<CalendarEvent> upcomingEvents = new ArrayList<>();
String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
for (CalendarEvent e : merged) {
if (e.getRawDate() != null && e.getRawDate().compareTo(today) >= 0) {
upcomingEvents.add(e);
}
}
// 5. Vis kun de 5 første av de kommende
List<CalendarEvent> top5 = new ArrayList<>();
for(int i=0; i<Math.min(merged.size(), 5); i++) {
top5.add(merged.get(i));
for(int i=0; i<Math.min(upcomingEvents.size(), 5); i++) {
top5.add(upcomingEvents.get(i));
}
recyclerView.setAdapter(new CalendarAdapter(top5, event -> {
@ -135,6 +165,7 @@ public class HomeFragment extends Fragment {
// Hvis API feiler, vis bare personlige events hvis vi har noen
if (!deviceEvents.isEmpty()) {
List<CalendarEvent> top5 = new ArrayList<>();
// Filtrer og plukk topp 5 fra lokale også
for(int i=0; i<Math.min(deviceEvents.size(), 5); i++) top5.add(deviceEvents.get(i));
recyclerView.setAdapter(new CalendarAdapter(top5, event -> {
@ -142,7 +173,6 @@ public class HomeFragment extends Fragment {
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));
@ -151,48 +181,55 @@ public class HomeFragment extends Fragment {
});
}
private void fetchNewsFromWordpress(RecyclerView recyclerView) {
WordPressApiService apiService = RetrofitClient.getApiService();
// Bruker getPosts som henter 5-10 innlegg med ?_embed
apiService.getPosts().enqueue(new Callback<List<WpPost>>() {
@Override
public void onResponse(Call<List<WpPost>> call, Response<List<WpPost>> response) {
if (getContext() == null) return;
if (response.isSuccessful() && response.body() != null) {
List<WpPost> wpPosts = response.body();
// Datoformatering
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 : wpPosts) {
try {
Date date = rawFormat.parse(post.date);
post.date = targetFormat.format(date); // Setter pen dato
} catch (Exception e) {}
}
// Sett adapter med Click Listener
NewsAdapter adapter = new NewsAdapter(wpPosts, post -> {
// Naviger til detaljvisning og send med post-objektet
Bundle bundle = new Bundle();
bundle.putSerializable("post_data", post); // WpPost er Serializable
Navigation.findNavController(getView()).navigate(R.id.action_home_to_newsDetail, bundle);
});
recyclerView.setAdapter(adapter);
}
}
@Override
public void onFailure(Call<List<WpPost>> call, Throwable t) {
if (getContext() == null) return;
// Ved feil, sett tom liste (eller vis feilmelding)
recyclerView.setAdapter(new NewsAdapter(new ArrayList<>(), null));
}
});
}
private void startNotificationWorker() {
// Kjører en jobb hvert 15. minutt for å sjekke om det er nye møter
// Kjører en jobb hvert 15. minutt for å sjekke om det er nye møter i HMS-kalenderen
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) {
// [Lim inn koden for nyheter fra forrige svar her, den var OK]
WordPressApiService apiService = RetrofitClient.getApiService();
apiService.getPosts().enqueue(new Callback<List<WpPost>>() {
@Override
public void onResponse(Call<List<WpPost>> call, Response<List<WpPost>> response) {
if (getContext() == null) return;
if (response.isSuccessful() && response.body() != null) {
List<WpPost> wpPosts = response.body();
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"));
for (WpPost post : wpPosts) {
String formattedDate = post.date;
try {
java.util.Date date = rawFormat.parse(post.date);
formattedDate = targetFormat.format(date);
} catch (java.text.ParseException e) {}
newsList.add(new NewsItem(post.getTitleStr(), post.getExcerptStr(), "Publisert: " + formattedDate));
}
recyclerView.setAdapter(new NewsAdapter(newsList));
}
}
@Override
public void onFailure(Call<List<WpPost>> call, Throwable t) {
if (getContext() == null) return;
List<NewsItem> errorList = new ArrayList<>();
errorList.add(new NewsItem("Kunne ikke laste nyheter", "Sjekk nettverket ditt.", "System"));
recyclerView.setAdapter(new NewsAdapter(errorList));
}
});
}
}

View file

@ -0,0 +1,30 @@
package com.kbs.kbsintranett;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
public class InputsAdapter implements JsonDeserializer<List<GravityField>> {
@Override
public List<GravityField> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonNull()) {
return new ArrayList<>();
}
// Fikser krasjen: Hvis Gravity Forms sender "" i stedet for [], returner tom liste
if (json.isJsonPrimitive() && json.getAsJsonPrimitive().isString()) {
return new ArrayList<>();
}
if (json.isJsonArray()) {
List<GravityField> list = new ArrayList<>();
for (JsonElement e : json.getAsJsonArray()) {
list.add(context.deserialize(e, GravityField.class));
}
return list;
}
return new ArrayList<>();
}
}

View file

@ -3,17 +3,34 @@ package com.kbs.kbsintranett;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
import java.util.List;
public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {
private List<NewsItem> newsList;
private List<WpPost> posts;
private OnItemClickListener listener; // NYTT
public NewsAdapter(List<NewsItem> newsList) {
this.newsList = newsList;
// Interface for klikk
public interface OnItemClickListener {
void onItemClick(WpPost post);
}
// Oppdatert konstruktør
public NewsAdapter(List<WpPost> posts, OnItemClickListener listener) {
this.posts = posts;
this.listener = listener;
}
// Overload for bakoverkompatibilitet (hvis du ikke sender listener)
public NewsAdapter(List<WpPost> posts) {
this.posts = posts;
this.listener = null;
}
@NonNull
@ -25,24 +42,62 @@ public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
NewsItem item = newsList.get(position);
holder.title.setText(item.getTitle());
holder.excerpt.setText(item.getExcerpt());
holder.author.setText("Av: " + item.getAuthor());
WpPost post = posts.get(position);
holder.title.setText(post.getTitleStr());
holder.excerpt.setText(post.getExcerptStr());
holder.date.setText(post.date);
String cat = post.getCategoryName();
if (!cat.isEmpty()) {
holder.category.setText(cat);
holder.category.setVisibility(View.VISIBLE);
} else {
holder.category.setVisibility(View.GONE);
}
String imageUrl = post.getFeaturedImageUrl();
if (imageUrl != null && !imageUrl.isEmpty()) {
holder.image.setVisibility(View.VISIBLE);
Glide.with(holder.itemView.getContext())
.load(imageUrl)
.transition(DrawableTransitionOptions.withCrossFade())
.centerCrop()
.into(holder.image);
} else {
holder.image.setVisibility(View.GONE);
}
// NYTT: Håndter klikk
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onItemClick(post);
}
});
}
@Override
public int getItemCount() {
return newsList.size();
return posts.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView title, excerpt, author;
TextView title, excerpt, date, category;
ImageView image;
public ViewHolder(View view) {
super(view);
title = view.findViewById(R.id.news_title);
excerpt = view.findViewById(R.id.news_excerpt);
author = view.findViewById(R.id.news_author);
date = view.findViewById(R.id.news_date);
category = view.findViewById(R.id.news_category);
image = view.findViewById(R.id.news_image);
}
}
// NYTT: Metode for å oppdatere listen etter filtrering
public void updateList(List<WpPost> newPosts) {
this.posts = newPosts;
notifyDataSetChanged();
}
}

View file

@ -0,0 +1,67 @@
package com.kbs.kbsintranett;
import android.os.Bundle;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;
import com.bumptech.glide.Glide;
public class NewsDetailFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_news_detail, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Hent data fra argumentene (sendt fra HomeFragment/NewsFullFragment)
if (getArguments() != null) {
WpPost post = (WpPost) getArguments().getSerializable("post_data");
if (post != null) {
setupViews(view, post);
}
}
}
private void setupViews(View view, WpPost post) {
Toolbar toolbar = view.findViewById(R.id.detail_toolbar);
toolbar.setNavigationOnClickListener(v -> Navigation.findNavController(view).navigateUp());
ImageView image = view.findViewById(R.id.detail_image);
TextView title = view.findViewById(R.id.detail_title);
TextView category = view.findViewById(R.id.detail_category);
TextView date = view.findViewById(R.id.detail_date);
TextView author = view.findViewById(R.id.detail_author); // NY
TextView content = view.findViewById(R.id.detail_content);
String imgUrl = post.getFeaturedImageUrl();
if (imgUrl != null) {
Glide.with(this).load(imgUrl).centerCrop().into(image);
} else {
image.setBackgroundColor(getResources().getColor(android.R.color.darker_gray));
}
title.setText(post.getTitleStr());
category.setText(post.getCategoryName());
date.setText("Publisert: " + post.date);
// NYTT: Sett forfatter
author.setText("Av: " + post.getAuthorName());
content.setText(Html.fromHtml(post.getContentStr(), Html.FROM_HTML_MODE_COMPACT));
content.setMovementMethod(LinkMovementMethod.getInstance());
}
}

View file

@ -0,0 +1,144 @@
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.Toast;
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.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class NewsFullFragment extends Fragment {
private RecyclerView recyclerViewNews;
private RecyclerView recyclerViewCategories;
private ProgressBar progressBar;
private NewsAdapter newsAdapter;
private List<WpPost> allPosts = new ArrayList<>(); // Holder ALLE postene
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_news_full, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
recyclerViewNews = view.findViewById(R.id.recycler_news_full);
recyclerViewCategories = view.findViewById(R.id.recycler_categories);
progressBar = view.findViewById(R.id.loading_news_full);
ImageView backBtn = view.findViewById(R.id.btn_back_news);
// Setup Nyhetsliste
recyclerViewNews.setLayoutManager(new LinearLayoutManager(getContext()));
// Setup Kategorier (Horisontal)
recyclerViewCategories.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
setupCategories();
backBtn.setOnClickListener(v -> Navigation.findNavController(view).navigateUp());
fetchAllNews();
}
private void setupCategories() {
// Listen over kategorier du ønsket
List<String> categories = Arrays.asList(
"Alle", // Standard vis alt
"Avtaler og invitasjoner", "BHT", "Bilhold", "Cordel",
"Ferieavvikling", "Fest og moro", "Generell drift",
"HMS", "IT og sikkerhet", "Miljøfyrtårn", "Møtereferat", "SMX"
);
CategoryAdapter catAdapter = new CategoryAdapter(categories, selectedCategory -> {
filterNews(selectedCategory);
});
recyclerViewCategories.setAdapter(catAdapter);
}
private void fetchAllNews() {
progressBar.setVisibility(View.VISIBLE);
// Hent 50 siste (bør holde for en "Siste nytt" liste, ellers vi paginere)
RetrofitClient.getApiService().getAllPosts().enqueue(new Callback<List<WpPost>>() {
@Override
public void onResponse(Call<List<WpPost>> call, Response<List<WpPost>> response) {
if (!isAdded()) return;
progressBar.setVisibility(View.GONE);
if (response.isSuccessful() && response.body() != null) {
allPosts = response.body();
formatDates(allPosts);
// Vis alle i starten
newsAdapter = new NewsAdapter(new ArrayList<>(allPosts), post -> {
Bundle bundle = new Bundle();
bundle.putSerializable("post_data", post);
Navigation.findNavController(getView()).navigate(R.id.action_newsFull_to_newsDetail, bundle);
});
recyclerViewNews.setAdapter(newsAdapter);
} else {
Toast.makeText(getContext(), "Klarte ikke laste nyheter", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<List<WpPost>> call, Throwable t) {
if (!isAdded()) return;
progressBar.setVisibility(View.GONE);
Toast.makeText(getContext(), "Nettverksfeil", Toast.LENGTH_SHORT).show();
}
});
}
private void filterNews(String category) {
if (newsAdapter == null) return;
List<WpPost> filteredList = new ArrayList<>();
if (category.equals("Alle")) {
filteredList.addAll(allPosts);
} else {
for (WpPost post : allPosts) {
// Vi sjekker om kategorinavnet matcher
if (post.getCategoryName().equals(category)) {
filteredList.add(post);
}
}
}
// Oppdater adapteren med den filtrerte listen
newsAdapter.updateList(filteredList);
}
private void formatDates(List<WpPost> posts) {
SimpleDateFormat rawFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
rawFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
SimpleDateFormat targetFormat = new SimpleDateFormat("dd. MMM yyyy", Locale.getDefault());
targetFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
for (WpPost post : posts) {
try {
Date date = rawFormat.parse(post.date);
post.date = targetFormat.format(date);
} catch (Exception e) {}
}
}
}

View file

@ -16,8 +16,8 @@ import retrofit2.http.PartMap;
import retrofit2.http.Query;
public interface WordPressApiService {
// 1. Hent nyheter
@GET("wp-json/wp/v2/posts?per_page=5")
// 1. Hent nyheter - ENDRET: Lagt til &_embed for å bilde og kategori
@GET("wp-json/wp/v2/posts?per_page=10&_embed")
Call<List<WpPost>> getPosts();
// 2. Hent et spesifikt skjema med ID
@ -57,7 +57,11 @@ public interface WordPressApiService {
@Query("paging[page_size]") int pageSize
);
// 9. HENT ÉN ENKELT INNSENDING (Ny! Brukes for å hente detaljer fra underskjema)
// 9. HENT ÉN ENKELT INNSENDING
@GET("wp-json/gf/v2/entries/{entry_id}")
Call<JsonElement> getSingleEntry(@Path("entry_id") String entryId);
// 10. HENT ALLE NYHETER (F.eks 50 stk) - Brukes av "Se alle" siden
@GET("wp-json/wp/v2/posts?per_page=50&_embed")
Call<List<WpPost>> getAllPosts();
}

View file

@ -1,31 +1,106 @@
package com.kbs.kbsintranett;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
public class WpPost {
// WordPress sender tittelen som et objekt: "title": { "rendered": "Overskrift" }
public class WpPost implements Serializable {
@SerializedName("title")
public Rendered title;
@SerializedName("excerpt")
public Rendered excerpt;
@SerializedName("content")
public Rendered content;
@SerializedName("date")
public String date;
// Hjelpeklasse for å hente ut teksten inni "rendered"
public static class Rendered {
@SerializedName("_embedded")
public Embedded embedded;
public static class Rendered implements Serializable {
@SerializedName("rendered")
public String renderedString;
}
// En hjelpemetode for å ren tekst ut (fjerner HTML-koder hvis nødvendig)
public static class Embedded implements Serializable {
@SerializedName("wp:featuredmedia")
public List<Media> mediaList;
@SerializedName("wp:term")
public List<List<Term>> termList;
// NYTT: Forfatter-liste
@SerializedName("author")
public List<Author> authorList;
}
public static class Media implements Serializable {
@SerializedName("source_url")
public String sourceUrl;
}
public static class Term implements Serializable {
@SerializedName("name")
public String name;
}
// NYTT: Forfatter-klasse
public static class Author implements Serializable {
@SerializedName("name")
public String name;
}
public String getTitleStr() {
return title != null ? title.renderedString : "Uten tittel";
}
public String getExcerptStr() {
// En enkel rensing av HTML-tags (f.eks <p>)
return excerpt != null ? android.text.Html.fromHtml(excerpt.renderedString, android.text.Html.FROM_HTML_MODE_COMPACT).toString() : "";
return excerpt != null ?
android.text.Html.fromHtml(excerpt.renderedString, android.text.Html.FROM_HTML_MODE_COMPACT).toString().trim() : "";
}
public String getContentStr() {
return content != null ? content.renderedString : "";
}
public String getFeaturedImageUrl() {
if (embedded != null && embedded.mediaList != null && !embedded.mediaList.isEmpty()) {
return embedded.mediaList.get(0).sourceUrl;
}
return null;
}
// NYTT: Hent forfatternavn
public String getAuthorName() {
if (embedded != null && embedded.authorList != null && !embedded.authorList.isEmpty()) {
return embedded.authorList.get(0).name;
}
return "Ukjent"; // Fallback
}
public String getCategoryName() {
if (embedded != null && embedded.termList != null && !embedded.termList.isEmpty()) {
List<Term> categories = embedded.termList.get(0);
if (categories == null || categories.isEmpty()) return "";
List<String> priorityCategories = Arrays.asList(
"Avtaler og invitasjoner", "BHT", "Bilhold", "Cordel",
"Ferieavvikling", "Fest og moro", "Generell drift",
"HMS", "IT og sikkerhet", "Miljøfyrtårn", "Møtereferat", "SMX"
);
for (Term term : categories) {
if (priorityCategories.contains(term.name)) return term.name;
}
for (Term term : categories) {
if (term.name.contains("Alle ansatte")) return "Til info";
}
return categories.get(0).name;
}
return "";
}
}

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/kbs_logo_blue" />
<corners android:radius="20dp" />
</shape>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF" />
<stroke android:width="1dp" android:color="#DDDDDD" />
<corners android:radius="20dp" />
</shape>

View file

@ -21,6 +21,7 @@
android:textStyle="bold"
android:textColor="@color/kbs_muted_blue_gray"
android:layout_centerVertical="true"/>
<ImageView
android:id="@+id/btn_profile"
android:layout_width="40dp"
@ -69,15 +70,34 @@
android:scrollbars="vertical"
android:layout_marginBottom="16dp"/>
<TextView
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Siste nytt"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/black"
android:orientation="horizontal"
android:gravity="center_vertical"
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="Siste nytt"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/black"/>
<TextView
android:id="@+id/btn_view_all_news"
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
android:id="@+id/recycler_news"

View file

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
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:background="@android:color/white">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="250dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:contentScrim="@color/kbs_logo_blue">
<ImageView
android:id="@+id/detail_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/detail_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:navigationIcon="@android:drawable/ic_menu_revert" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/detail_category"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/kbs_logo_accent_red"
android:textStyle="bold"
android:textAllCaps="true"
android:textSize="12sp"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/detail_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@android:color/black"
android:layout_marginBottom="8dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="24dp">
<TextView
android:id="@+id/detail_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#888888"
android:textSize="12sp"
android:layout_marginEnd="16dp"/>
<TextView
android:id="@+id/detail_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#888888"
android:textStyle="italic"
android:textSize="12sp"
android:text="Av: Forfatter"/>
</LinearLayout>
<TextView
android:id="@+id/detail_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#333333"
android:textSize="16sp"
android:lineSpacingExtra="4dp"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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="@android:color/white"
android:elevation="4dp">
<ImageView
android:id="@+id/btn_back_news"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@android:drawable/ic_menu_revert"
android:layout_centerVertical="true"
android:contentDescription="Tilbake"
android:background="?attr/selectableItemBackgroundBorderless"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Siste nytt"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@android:color/black"
android:layout_centerInParent="true"/>
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_categories"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp"
android:clipToPadding="false"
android:scrollbars="none"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_news_full"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="8dp"
android:paddingHorizontal="4dp"
android:clipToPadding="false"/>
<ProgressBar
android:id="@+id/loading_news_full"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
</FrameLayout>
</LinearLayout>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/category_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kategori"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/bg_category_unselected"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />

View file

@ -3,42 +3,87 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
android:layout_marginBottom="24dp"
android:layout_marginHorizontal="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp"
app:cardBackgroundColor="@android:color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
android:orientation="vertical">
<TextView
android:id="@+id/news_title"
<ImageView
android:id="@+id/news_image"
android:layout_width="match_parent"
android:layout_height="200dp"
android:scaleType="centerCrop"
android:src="@android:drawable/ic_menu_gallery"
android:background="#EEEEEE" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Nyhets overskrift"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@android:color/black"/>
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/news_excerpt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Her kommer ingressen..."
android:textColor="@android:color/darker_gray"/>
<TextView
android:id="@+id/news_category"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="KATEGORI"
android:textSize="12sp"
android:textStyle="bold"
android:textColor="@color/kbs_logo_accent_red"
android:textAllCaps="true"
android:letterSpacing="0.05"
android:layout_marginBottom="4dp"/>
<TextView
android:id="@+id/news_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Skrevet av: Ole"
android:textSize="12sp"
android:textStyle="italic"/>
<TextView
android:id="@+id/news_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Overskrift på nyhetssaken kommer her"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@android:color/black"
android:layout_marginBottom="8dp"
android:lineSpacingExtra="2dp"/>
<TextView
android:id="@+id/news_excerpt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Her kommer en kort ingress som beskriver saken litt nærmere før man klikker seg inn..."
android:textColor="#555555"
android:textSize="14sp"
android:maxLines="3"
android:ellipsize="end"
android:layout_marginBottom="12dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:layout_width="14dp"
android:layout_height="14dp"
android:src="@android:drawable/ic_menu_my_calendar"
app:tint="#999999"
android:layout_marginEnd="6dp"/>
<TextView
android:id="@+id/news_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="23. Nov 2025"
android:textSize="12sp"
android:textColor="#999999"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View file

@ -22,9 +22,18 @@
android:name="com.kbs.kbsintranett.HomeFragment"
android:label="Hjem"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_home_to_calendarFull"
app:destination="@id/navigation_calendar_full" />
<action
android:id="@+id/action_home_to_newsFull"
app:destination="@id/navigation_news_full" />
<action
android:id="@+id/action_home_to_newsDetail"
app:destination="@id/navigation_news_detail" />
</fragment>
<fragment
@ -33,14 +42,29 @@
android:label="Kalender"
tools:layout="@layout/fragment_calendar_full" />
<fragment
android:id="@+id/navigation_news_full"
android:name="com.kbs.kbsintranett.NewsFullFragment"
android:label="Nyheter"
tools:layout="@layout/fragment_news_full">
<action
android:id="@+id/action_newsFull_to_newsDetail"
app:destination="@id/navigation_news_detail" />
</fragment>
<fragment
android:id="@+id/navigation_news_detail"
android:name="com.kbs.kbsintranett.NewsDetailFragment"
android:label="Nyhet"
tools:layout="@layout/fragment_news_detail" />
<fragment
android:id="@+id/navigation_forms"
android:name="com.kbs.kbsintranett.FormsListFragment"
android:label="Skjemaer"
tools:layout="@layout/fragment_forms">
<action
android:id="@+id/action_formsListFragment_to_formsDetailFragment"
app:destination="@id/navigation_forms_detail" />
tools:layout="@layout/fragment_forms_list"> <action
android:id="@+id/action_formsListFragment_to_formsDetailFragment"
app:destination="@id/navigation_forms_detail" />
</fragment>
<fragment

File diff suppressed because it is too large Load diff