From d3b9504f9a6b250383230277bdd925e3446ed8fa Mon Sep 17 00:00:00 2001 From: ErolHaagenrud Date: Thu, 18 Dec 2025 10:21:43 +0100 Subject: [PATCH] =?UTF-8?q?F=C3=B8r=20refreshing=20p=C3=A5=20forsiden?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 14 +- .../CalendarDetailsBottomSheet.java | 28 ++- .../kbsintranett/CalendarFullFragment.java | 20 +- .../com/kbs/kbsintranett/CalendarManager.java | 64 ++++- .../kbs/kbsintranett/CreateEventFragment.java | 14 ++ .../com/kbs/kbsintranett/HomeFragment.java | 49 +++- .../kbs/kbsintranett/ImageDialogFragment.java | 90 +++++++ .../com/kbs/kbsintranett/MainActivity.java | 33 +-- .../kbs/kbsintranett/NewsDetailFragment.java | 104 +++++++- .../com/kbs/kbsintranett/ProfileFragment.java | 23 +- app/src/main/res/layout/fragment_home.xml | 229 +++++++++--------- .../main/res/layout/fragment_image_dialog.xml | 36 +++ .../main/res/layout/fragment_news_detail.xml | 12 +- app/src/main/res/layout/fragment_profile.xml | 12 +- build.gradle.kts | 4 +- 15 files changed, 530 insertions(+), 202 deletions(-) create mode 100644 app/src/main/java/com/kbs/kbsintranett/ImageDialogFragment.java create mode 100644 app/src/main/res/layout/fragment_image_dialog.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5e26931..230d7dd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,5 +1,7 @@ plugins { alias(libs.plugins.android.application) + // NY LINJE: Aktiver Google Services plugin her + id("com.google.gms.google-services") } android { @@ -10,8 +12,8 @@ android { applicationId = "com.kbs.kbsintranett" minSdk = 28 targetSdk = 34 - versionCode = 1 - versionName = "1.0" + versionCode = 2 + versionName = "1.2" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -63,4 +65,10 @@ dependencies { // Swipe Refresh Layout implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") -} \ No newline at end of file + + // NY LINJE: Firebase BOM (Bill of Materials) styrer versjoner + implementation(platform("com.google.firebase:firebase-bom:33.1.2")) + + // NY LINJE: (Valgfritt, men lurt for statistikk) + implementation("com.google.firebase:firebase-analytics") +} diff --git a/app/src/main/java/com/kbs/kbsintranett/CalendarDetailsBottomSheet.java b/app/src/main/java/com/kbs/kbsintranett/CalendarDetailsBottomSheet.java index af5ba92..b5b01c5 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CalendarDetailsBottomSheet.java +++ b/app/src/main/java/com/kbs/kbsintranett/CalendarDetailsBottomSheet.java @@ -15,23 +15,31 @@ import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.navigation.Navigation; import androidx.navigation.fragment.NavHostFragment; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.gson.JsonElement; import java.util.ArrayList; -import java.util.Collections; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class CalendarDetailsBottomSheet extends BottomSheetDialogFragment { private CalendarEvent event; + private OnEventChangeListener changeListener; + + // Interface for å varsle foreldre-fragmentet om endringer + public interface OnEventChangeListener { + void onEventChanged(); + } public CalendarDetailsBottomSheet(CalendarEvent event) { this.event = event; } + public void setOnEventChangeListener(OnEventChangeListener listener) { + this.changeListener = listener; + } + @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { @@ -41,7 +49,7 @@ public class CalendarDetailsBottomSheet extends BottomSheetDialogFragment { TextView time = view.findViewById(R.id.sheet_time); TextView desc = view.findViewById(R.id.sheet_desc); TextView loc = view.findViewById(R.id.sheet_location); - TextView calName = view.findViewById(R.id.sheet_calendar_name); // NYTT + TextView calName = view.findViewById(R.id.sheet_calendar_name); LinearLayout adminLayout = view.findViewById(R.id.layout_admin_buttons); Button btnDelete = view.findViewById(R.id.btn_delete); @@ -50,7 +58,6 @@ public class CalendarDetailsBottomSheet extends BottomSheetDialogFragment { title.setText(event.getTitle()); time.setText(event.getTime() + " (" + event.getDay() + ". " + event.getMonth() + ")"); - // NYTT: Sett navn og farge calName.setText(event.getCalendarName().toUpperCase()); try { int color = Color.parseColor(event.getCalendarColor()); @@ -75,13 +82,6 @@ public class CalendarDetailsBottomSheet extends BottomSheetDialogFragment { // Sjekk admin-rettigheter if (UserManager.getInstance().isEditorOrAbove()) { - // MERK: Selv om man er admin, bør man kanskje bare kunne slette/endre events fra kalendere - // man faktisk har skrivetilgang til i konfigurasjonen. - // Men siden admin har tilgang til alt i PHP, viser vi knappene. - // Hvis brukeren er vanlig ansatt (men ikke i f.eks service), vil skrivetilgang sjekkes - // mot 'writeable_calendars' eller ved API-kall. - // Enklest her: Vi viser knappene hvis brukeren KAN skrive til denne kalenderen. - boolean canEdit = UserManager.getInstance().getWriteableCalendars().contains(event.getCalendarName()) || UserManager.getInstance().isAdmin(); @@ -122,6 +122,12 @@ public class CalendarDetailsBottomSheet extends BottomSheetDialogFragment { public void onResponse(Call call, Response response) { if (response.isSuccessful()) { Toast.makeText(getContext(), "Slettet!", Toast.LENGTH_SHORT).show(); + + // VARSLE LISTEN OM AT NOE ER SLETTET + if (changeListener != null) { + changeListener.onEventChanged(); + } + dismiss(); } else { Toast.makeText(getContext(), "Kunne ikke slette", Toast.LENGTH_LONG).show(); diff --git a/app/src/main/java/com/kbs/kbsintranett/CalendarFullFragment.java b/app/src/main/java/com/kbs/kbsintranett/CalendarFullFragment.java index eabc6d4..3fb6d7f 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CalendarFullFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/CalendarFullFragment.java @@ -1,6 +1,8 @@ package com.kbs.kbsintranett; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -46,16 +48,25 @@ public class CalendarFullFragment extends Fragment { layoutManager = new LinearLayoutManager(getContext()); recyclerView.setLayoutManager(layoutManager); backBtn.setOnClickListener(v -> Navigation.findNavController(view).navigateUp()); + } + @Override + public void onResume() { + super.onResume(); fetchAllEvents(); } private void fetchAllEvents() { progressBar.setVisibility(View.VISIBLE); - // Hent personlige hendelser - List deviceEvents = CalendarManager.getDeviceEvents(getContext()); - // Hent felles hendelser fra WordPress Proxy (sikrer at vi får reminders) + new Thread(() -> { + // HER ER ENDRINGEN: isPreview = false (Hent alt) + List deviceEvents = CalendarManager.getDeviceEvents(getContext(), false); + new Handler(Looper.getMainLooper()).post(() -> fetchApiEvents(deviceEvents)); + }).start(); + } + + private void fetchApiEvents(List deviceEvents) { RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback>() { @Override public void onResponse(Call> call, Response> response) { @@ -65,7 +76,6 @@ public class CalendarFullFragment extends Fragment { List apiEvents = new ArrayList<>(); if (response.isSuccessful() && response.body() != null) { apiEvents = response.body(); - // Formater data for visning for (CalendarEvent e : apiEvents) { CalendarManager.formatEventForUI(e); } @@ -82,6 +92,7 @@ public class CalendarFullFragment extends Fragment { CalendarAdapter adapter = new CalendarAdapter(allEvents, event -> { CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event); + sheet.setOnEventChangeListener(CalendarFullFragment.this::fetchAllEvents); sheet.show(getParentFragmentManager(), "CalendarDetails"); }); recyclerView.setAdapter(adapter); @@ -126,5 +137,4 @@ public class CalendarFullFragment extends Fragment { layoutManager.scrollToPositionWithOffset(scrollIndex, 0); } } - } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java b/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java index dec83be..385a9c1 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java +++ b/app/src/main/java/com/kbs/kbsintranett/CalendarManager.java @@ -9,8 +9,10 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Set; import java.util.TimeZone; public class CalendarManager { @@ -44,7 +46,13 @@ public class CalendarManager { return ids; } - public static List getDeviceEvents(Context context) { + /** + * Henter hendelser fra enheten. + * @param context App Context + * @param isPreview Hvis true: Henter kun kommende måned (Raskt). Hvis false: Henter -1 til +6 mnd (Tregere). + * @return Liste med events + */ + public static List getDeviceEvents(Context context, boolean isPreview) { List deviceEvents = new ArrayList<>(); if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) { @@ -57,8 +65,20 @@ public class CalendarManager { } long now = System.currentTimeMillis(); - long startMillis = now - (365L * 24 * 60 * 60 * 1000); - long endMillis = now + (365L * 24 * 60 * 60 * 1000); + long startMillis; + long endMillis; + + if (isPreview) { + // FORSIDEN: Optimalisert for hastighet. + // Henter fra NÅ og 30 dager frem i tid. + startMillis = now; + endMillis = now + (30L * 24 * 60 * 60 * 1000); + } else { + // FULL VISNING: Bredere tidsvindu. + // 1 mnd tilbake til 6 mnd frem. + startMillis = now - (30L * 24 * 60 * 60 * 1000); + endMillis = now + (180L * 24 * 60 * 60 * 1000); + } String[] projection = new String[]{ CalendarContract.Events.TITLE, @@ -120,8 +140,7 @@ public class CalendarManager { CalendarEvent event = new CalendarEvent(title, rawStart, rawEnd, desc, loc); event.setReminderMinutes(0); - // NYTT V12.2: Sett standardfarge for lokale kalendere - event.setCalendarColor("#888888"); // Grå for lokale events + event.setCalendarColor("#888888"); event.setCalendarName("Min Kalender (Lokal)"); formatEventForUI(event); @@ -200,9 +219,24 @@ public class CalendarManager { } public static List mergeAndSort(List apiEvents, List deviceEvents) { - List all = new ArrayList<>(apiEvents); - all.addAll(deviceEvents); + List all = new ArrayList<>(); + Set uniqueKeys = new HashSet<>(); + // 1. Legg til alle API-events først (Prioritert) + for (CalendarEvent apiEvent : apiEvents) { + all.add(apiEvent); + uniqueKeys.add(generateKey(apiEvent)); + } + + // 2. Legg til Device-events KUN hvis nøkkelen ikke finnes + for (CalendarEvent deviceEvent : deviceEvents) { + String key = generateKey(deviceEvent); + if (!uniqueKeys.contains(key)) { + all.add(deviceEvent); + } + } + + // 3. Sorter alt kronologisk Collections.sort(all, (e1, e2) -> { String d1 = e1.getRawDate() != null ? e1.getRawDate() : ""; String d2 = e2.getRawDate() != null ? e2.getRawDate() : ""; @@ -212,4 +246,20 @@ public class CalendarManager { return all; } + private static String generateKey(CalendarEvent event) { + String title = event.getTitle() != null ? event.getTitle().toLowerCase().trim() : ""; + String datePart = ""; + + if (event.getRawDate() != null) { + String digits = event.getRawDate().replaceAll("[^0-9]", ""); + if (digits.length() >= 12) { + datePart = digits.substring(0, 12); + } else if (digits.length() >= 8) { + datePart = digits.substring(0, 8); + } else { + datePart = digits; + } + } + return title + "_" + datePart; + } } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java b/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java index 35cb348..0938f12 100644 --- a/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/CreateEventFragment.java @@ -139,6 +139,20 @@ public class CreateEventFragment extends Fragment { etDesc.setText(cleanDesc); etLocation.setText(event.getLocation()); + // --- FIKS 404 FEIL VED OPPDATERING --- + // 1. Sett spinneren til riktig kalender + ArrayAdapter adapter = (ArrayAdapter) spinnerCalendar.getAdapter(); + if (adapter != null) { + int position = adapter.getPosition(event.getCalendarName()); + if (position >= 0) { + spinnerCalendar.setSelection(position); + } + } + // 2. Deaktiver spinneren. Man kan ikke bytte kalender ved oppdatering (API-begrensning). + // Man må slette og opprette på nytt for å flytte. + spinnerCalendar.setEnabled(false); + // ------------------------------------- + // Dato-parsing try { SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); diff --git a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java index 9db78bf..ca11c26 100644 --- a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java @@ -3,10 +3,13 @@ package com.kbs.kbsintranett; import android.Manifest; import android.content.pm.PackageManager; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.ProgressBar; import android.widget.TextView; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -33,6 +36,8 @@ import retrofit2.Response; public class HomeFragment extends Fragment { private ActivityResultLauncher requestPermissionLauncher; private RecyclerView calendarRecycler; + private RecyclerView newsRecycler; + private ProgressBar mainProgressBar; // Variabelen som må være her @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -58,12 +63,16 @@ public class HomeFragment extends Fragment { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + // Her leter vi etter IDen som MÅ finnes i XML-filen over + mainProgressBar = view.findViewById(R.id.main_loading_spinner); + + if (mainProgressBar != null) mainProgressBar.setVisibility(View.VISIBLE); + View profileBtn = view.findViewById(R.id.btn_profile); if (profileBtn != null) { profileBtn.setOnClickListener(v -> Navigation.findNavController(view).navigate(R.id.navigation_profile)); } - // NY LOGIKK: Vis knapp hvis brukeren har tilgang til minst én kalender Button btnCreateEvent = view.findViewById(R.id.btn_create_event); List writeable = UserManager.getInstance().getWriteableCalendars(); @@ -85,19 +94,13 @@ public class HomeFragment extends Fragment { viewAllCalendar.setOnClickListener(v -> Navigation.findNavController(view).navigate(R.id.action_home_to_calendarFull)); } - if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED) { - fetchCalendarEvents(calendarRecycler); - } else { - requestPermissionLauncher.launch(Manifest.permission.READ_CALENDAR); - } - 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); } } - RecyclerView newsRecycler = view.findViewById(R.id.recycler_news); + newsRecycler = view.findViewById(R.id.recycler_news); newsRecycler.setLayoutManager(new LinearLayoutManager(getContext())); newsRecycler.setNestedScrollingEnabled(false); newsRecycler.setAdapter(new NewsAdapter(new ArrayList<>(), item -> {})); @@ -112,14 +115,32 @@ public class HomeFragment extends Fragment { fetchNewsFromWordpress(newsRecycler); } - private void fetchCalendarEvents(RecyclerView recyclerView) { - List deviceEvents = CalendarManager.getDeviceEvents(getContext()); + @Override + public void onResume() { + super.onResume(); + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED) { + fetchCalendarEvents(calendarRecycler); + } else { + requestPermissionLauncher.launch(Manifest.permission.READ_CALENDAR); + } + } + private void fetchCalendarEvents(RecyclerView recyclerView) { + new Thread(() -> { + // isPreview = true for raskere lasting + List deviceEvents = CalendarManager.getDeviceEvents(getContext(), true); + new Handler(Looper.getMainLooper()).post(() -> fetchApiEvents(recyclerView, deviceEvents)); + }).start(); + } + + private void fetchApiEvents(RecyclerView recyclerView, List deviceEvents) { RetrofitClient.getApiService().getCalendarEvents().enqueue(new Callback>() { @Override public void onResponse(Call> call, Response> response) { if (!isAdded()) return; + if (mainProgressBar != null) mainProgressBar.setVisibility(View.GONE); + List apiEvents = new ArrayList<>(); if (response.isSuccessful() && response.body() != null) { apiEvents = response.body(); @@ -145,6 +166,7 @@ public class HomeFragment extends Fragment { recyclerView.setAdapter(new CalendarAdapter(top5, event -> { CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event); + sheet.setOnEventChangeListener(() -> fetchCalendarEvents(recyclerView)); sheet.show(getParentFragmentManager(), "CalendarDetails"); })); } @@ -152,6 +174,9 @@ public class HomeFragment extends Fragment { @Override public void onFailure(Call> call, Throwable t) { if (!isAdded()) return; + + if (mainProgressBar != null) mainProgressBar.setVisibility(View.GONE); + if (!deviceEvents.isEmpty()) { List top5 = new ArrayList<>(); for(int i=0; i errorList = new ArrayList<>(); - errorList.add(new CalendarEvent("Kunne ikke laste kalender", "Sjekk nettverk", "!", "OBS", "")); - recyclerView.setAdapter(new CalendarAdapter(errorList, null)); + recyclerView.setAdapter(new CalendarAdapter(new ArrayList<>(), null)); } } }); diff --git a/app/src/main/java/com/kbs/kbsintranett/ImageDialogFragment.java b/app/src/main/java/com/kbs/kbsintranett/ImageDialogFragment.java new file mode 100644 index 0000000..51dd0d9 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/ImageDialogFragment.java @@ -0,0 +1,90 @@ +package com.kbs.kbsintranett; + +import android.app.Dialog; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ProgressBar; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; + +public class ImageDialogFragment extends DialogFragment { + + private static final String ARG_URL = "image_url"; + + public static ImageDialogFragment newInstance(String imageUrl) { + ImageDialogFragment fragment = new ImageDialogFragment(); + Bundle args = new Bundle(); + args.putString(ARG_URL, imageUrl); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onStart() { + super.onStart(); + // Gjør dialogen fullskjerm + Dialog dialog = getDialog(); + if (dialog != null) { + int width = ViewGroup.LayoutParams.MATCH_PARENT; + int height = ViewGroup.LayoutParams.MATCH_PARENT; + dialog.getWindow().setLayout(width, height); + dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK)); + } + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_image_dialog, container, false); + + ImageView imageView = view.findViewById(R.id.full_screen_image); + ImageButton closeBtn = view.findViewById(R.id.btn_close_image); + ProgressBar progressBar = view.findViewById(R.id.loading_image); + + String url = getArguments() != null ? getArguments().getString(ARG_URL) : null; + + if (url != null) { + Glide.with(this) + .load(url) + .transition(DrawableTransitionOptions.withCrossFade()) + .listener(new com.bumptech.glide.request.RequestListener() { + @Override + public boolean onLoadFailed(@Nullable com.bumptech.glide.load.engine.GlideException e, Object model, com.bumptech.glide.request.target.Target target, boolean isFirstResource) { + progressBar.setVisibility(View.GONE); + return false; + } + + @Override + public boolean onResourceReady(android.graphics.drawable.Drawable resource, Object model, com.bumptech.glide.request.target.Target target, com.bumptech.glide.load.DataSource dataSource, boolean isFirstResource) { + progressBar.setVisibility(View.GONE); + return false; + } + }) + .into(imageView); + } + + closeBtn.setOnClickListener(v -> dismiss()); + // Lukk også hvis man trykker på selve bildet + imageView.setOnClickListener(v -> dismiss()); + + return view; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + return dialog; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/MainActivity.java b/app/src/main/java/com/kbs/kbsintranett/MainActivity.java index 159d2a0..952da2c 100644 --- a/app/src/main/java/com/kbs/kbsintranett/MainActivity.java +++ b/app/src/main/java/com/kbs/kbsintranett/MainActivity.java @@ -36,14 +36,12 @@ import java.util.concurrent.TimeUnit; public class MainActivity extends AppCompatActivity { - // VIKTIG: Sørg for at denne matcher den du har i Google Cloud Console - public static final String GOOGLE_WEB_CLIENT_ID = "738325360287-cidl3plnqv9ei74vm9vm5muustj6eenb.apps.googleusercontent.com"; + public static final String GOOGLE_WEB_CLIENT_ID = "738325360287-cidl3plnqv9ei74vm9vm5muustj6eenb.apps.googleusercontent.com"; // Bytt med din egen hvis denne er feil private static final String TAG = "MainActivity"; private NavController navController; private BottomNavigationView bottomNav; - // Launcher for å spørre om varslingstillatelse (Android 13+) private ActivityResultLauncher requestPermissionLauncher; @Override @@ -52,7 +50,6 @@ public class MainActivity extends AppCompatActivity { setContentView(R.layout.activity_main); // --- 1. SETUP UI & NAVIGASJON --- - // Sjekket activity_main.xml: ID er "bottom_nav_view" bottomNav = findViewById(R.id.bottom_nav_view); NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager() @@ -62,6 +59,13 @@ public class MainActivity extends AppCompatActivity { navController = navHostFragment.getNavController(); if (bottomNav != null) { NavigationUI.setupWithNavController(bottomNav, navController); + + // --- NYTT: Håndter "Reselection" (Klikk på fanen man allerede er i) --- + bottomNav.setOnItemReselectedListener(item -> { + // Dette fjerner alt som ligger "oppå" hovedsiden i stabelen. + // F.eks: Hjem -> Kalender Full -> (Klikk Hjem) -> Hjem + navController.popBackStack(item.getItemId(), false); + }); } // Skjul meny på login-skjerm @@ -76,32 +80,26 @@ public class MainActivity extends AppCompatActivity { }); } - // --- 2. VARSLINGSOPPSETT (NYTT) --- + // --- 2. VARSLINGSOPPSETT --- createNotificationChannel(); - // Initialiser permission launcher for varsler requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) { Log.d(TAG, "Varslingstillatelse gitt!"); } else { - Log.e(TAG, "Varslingstillatelse avslått. Bruker får ikke kalendervarsler."); + Log.e(TAG, "Varslingstillatelse avslått."); } }); - // Sjekk tillatelser (både Varsler og Alarmer) checkNotificationPermission(); checkExactAlarmPermission(); - // Start bakgrunnsjobben for kalenderen scheduleCalendarWork(); - // --- 3. AUTENTISERING (GAMMELT) --- + // --- 3. AUTENTISERING --- checkLoginState(); } - /** - * Sjekker om brukeren er logget inn med Google, og gjør en silent refresh mot WP. - */ private void checkLoginState() { GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this); if (account == null) { @@ -160,8 +158,6 @@ public class MainActivity extends AppCompatActivity { } } - // --- NYE HJELPEMETODER FOR VARSLING --- - private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { CharSequence name = "KBS Kalendervarsler"; @@ -184,10 +180,6 @@ public class MainActivity extends AppCompatActivity { } } - /** - * Sjekker om appen har lov til å sette nøyaktige alarmer (SCHEDULE_EXACT_ALARM). - * Hvis ikke, spør brukeren om å gå til innstillinger. - */ private void checkExactAlarmPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); @@ -206,9 +198,6 @@ public class MainActivity extends AppCompatActivity { } } - /** - * Starter WorkManager som sjekker kalenderen hvert 15. minutt. - */ private void scheduleCalendarWork() { PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(NotificationWorker.class, 15, TimeUnit.MINUTES) .build(); diff --git a/app/src/main/java/com/kbs/kbsintranett/NewsDetailFragment.java b/app/src/main/java/com/kbs/kbsintranett/NewsDetailFragment.java index 01f8481..fc8d8a8 100644 --- a/app/src/main/java/com/kbs/kbsintranett/NewsDetailFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/NewsDetailFragment.java @@ -1,11 +1,14 @@ package com.kbs.kbsintranett; +import android.content.Context; 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.webkit.JavascriptInterface; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; @@ -17,6 +20,41 @@ import com.bumptech.glide.Glide; public class NewsDetailFragment extends Fragment { + // CSS Styling (Samme stil som håndboken, pluss bildehåndtering) + private static final String CSS_STYLE = + ""; + + // JavaScript for å fange opp bildeklikk + private static final String JS_SCRIPT = + ""; + @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { @@ -27,7 +65,6 @@ public class NewsDetailFragment extends Fragment { 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) { @@ -44,9 +81,10 @@ public class NewsDetailFragment extends Fragment { 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); + TextView author = view.findViewById(R.id.detail_author); + WebView webView = view.findViewById(R.id.detail_webview); + // Header bilde String imgUrl = post.getFeaturedImageUrl(); if (imgUrl != null) { Glide.with(this).load(imgUrl).centerCrop().into(image); @@ -57,11 +95,59 @@ public class NewsDetailFragment extends Fragment { 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()); + // Konfigurer WebView + WebSettings settings = webView.getSettings(); + settings.setJavaScriptEnabled(true); + settings.setDomStorageEnabled(true); + + // Legg til Interface for å snakke med Java + webView.addJavascriptInterface(new WebAppInterface(getContext()), "Android"); + + // Håndter linker internt (som i Håndboken) + webView.setWebViewClient(new WebViewClient() { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + // Bruk samme link-logikk som i Håndboken hvis nødvendig, + // men her lar vi linker åpnes i nettleser for enkelhets skyld foreløpig + return false; + } + }); + + // Bygg HTML + String rawContent = post.getContentStr(); + + // Vask innholdet litt hvis nødvendig (f.eks fjerne inline styles som ødelegger) + // Her legger vi bare til vår CSS og JS + String htmlData = "" + + "" + + CSS_STYLE + + JS_SCRIPT + + "" + + rawContent + + ""; + + webView.loadDataWithBaseURL("https://intranet.kbs.no", htmlData, "text/html", "UTF-8", null); + } + + // Bridge-klasse for å ta imot klikk fra JavaScript + public class WebAppInterface { + Context mContext; + + WebAppInterface(Context c) { + mContext = c; + } + + @JavascriptInterface + public void showImage(String url) { + // Må kjøres på UI-tråden + if (getActivity() != null) { + getActivity().runOnUiThread(() -> { + ImageDialogFragment dialog = ImageDialogFragment.newInstance(url); + dialog.show(getParentFragmentManager(), "image_lightbox"); + }); + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/ProfileFragment.java b/app/src/main/java/com/kbs/kbsintranett/ProfileFragment.java index 42afa4f..00fb3df 100644 --- a/app/src/main/java/com/kbs/kbsintranett/ProfileFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/ProfileFragment.java @@ -27,22 +27,24 @@ public class ProfileFragment extends Fragment { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_profile, container, false); - // 1. Finn Views ImageView closeBtn = view.findViewById(R.id.btn_close_profile); ImageView profileImage = view.findViewById(R.id.profile_image); TextView nameText = view.findViewById(R.id.profile_name); TextView emailText = view.findViewById(R.id.profile_email); TextView roleText = view.findViewById(R.id.profile_role); Button logoutBtn = view.findViewById(R.id.btn_logout); - Button updateInfoBtn = view.findViewById(R.id.btn_update_info); // NY + Button updateInfoBtn = view.findViewById(R.id.btn_update_info); + TextView versionText = view.findViewById(R.id.tv_version_info); // NYTT - // 2. Hent data fra UserManager UserManager user = UserManager.getInstance(); nameText.setText(user.getUserDisplayName()); emailText.setText(user.getUserEmail()); roleText.setText("Rolle: " + user.getUserRole()); - // 3. Last bilde med Glide + // NYTT: Sett versjonstekst + String versionInfo = "Versjon " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")"; + versionText.setText(versionInfo); + if (user.getPhotoUrl() != null) { Glide.with(this) .load(user.getPhotoUrl()) @@ -50,44 +52,33 @@ public class ProfileFragment extends Fragment { .into(profileImage); } - // 4. Håndter "Lukk" (X) knapp - Gå tilbake til forrige skjerm closeBtn.setOnClickListener(v -> { Navigation.findNavController(view).navigateUp(); }); - // 5. Håndter "Oppdater opplysninger" (Skjema ID 1) updateInfoBtn.setOnClickListener(v -> { Bundle bundle = new Bundle(); - bundle.putInt("formId", 1); // ID 1 er Ansatteopplysninger + bundle.putInt("formId", 1); Navigation.findNavController(view).navigate(R.id.action_profile_to_form, bundle); }); - // 6. Håndter utlogging logoutBtn.setOnClickListener(v -> performLogout()); return view; } private void performLogout() { - // A. Konfigurer Google Client GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) .requestIdToken(MainActivity.GOOGLE_WEB_CLIENT_ID) .requestEmail() .build(); GoogleSignInClient client = GoogleSignIn.getClient(requireActivity(), gso); - // B. Logg ut fra Google client.signOut().addOnCompleteListener(task -> { - - // C. Tøm interne data UserManager.getInstance().logout(); RetrofitClient.clearClient(); - - // D. Naviger tilbake til Login-skjermen NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); - // Denne aksjonen finnes i mobile_navigation.xml navController.navigate(R.id.action_profile_to_login); - Toast.makeText(getContext(), "Du er nå logget ut", Toast.LENGTH_SHORT).show(); }); } diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 8269ff6..2db0bab 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -1,126 +1,137 @@ - - - - - - - - - - - -