7050 lines
276 KiB
Text
7050 lines
276 KiB
Text
Dette er kildekoden til et Android Studio-prosjekt.
|
|
Hver fil er separert med overskrifter.
|
|
|
|
|
|
============================================================
|
|
FILSTI: build.gradle.kts
|
|
============================================================
|
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
|
plugins {
|
|
alias(libs.plugins.android.application) apply false
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: settings.gradle.kts
|
|
============================================================
|
|
pluginManagement {
|
|
repositories {
|
|
google {
|
|
content {
|
|
includeGroupByRegex("com\\.android.*")
|
|
includeGroupByRegex("com\\.google.*")
|
|
includeGroupByRegex("androidx.*")
|
|
}
|
|
}
|
|
mavenCentral()
|
|
gradlePluginPortal()
|
|
}
|
|
}
|
|
dependencyResolutionManagement {
|
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
|
repositories {
|
|
google()
|
|
mavenCentral()
|
|
}
|
|
}
|
|
|
|
rootProject.name = "KBS Intranett"
|
|
include(":app")
|
|
|
|
|
|
============================================================
|
|
FILSTI: app\build.gradle.kts
|
|
============================================================
|
|
plugins {
|
|
alias(libs.plugins.android.application)
|
|
}
|
|
|
|
android {
|
|
namespace = "com.kbs.kbsintranett"
|
|
compileSdk = 34
|
|
|
|
defaultConfig {
|
|
applicationId = "com.kbs.kbsintranett"
|
|
minSdk = 28
|
|
targetSdk = 34
|
|
versionCode = 1
|
|
versionName = "1.0"
|
|
|
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
isMinifyEnabled = false
|
|
proguardFiles(
|
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
"proguard-rules.pro"
|
|
)
|
|
}
|
|
}
|
|
compileOptions {
|
|
// ENDRET: Oppgradert til Java 11 for å fikse build warnings
|
|
sourceCompatibility = JavaVersion.VERSION_11
|
|
targetCompatibility = JavaVersion.VERSION_11
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
implementation(libs.appcompat)
|
|
implementation(libs.material)
|
|
implementation(libs.activity)
|
|
implementation(libs.constraintlayout)
|
|
testImplementation(libs.junit)
|
|
androidTestImplementation(libs.ext.junit)
|
|
androidTestImplementation(libs.espresso.core)
|
|
|
|
// Nettverk og JSON-håndtering
|
|
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
|
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
|
|
implementation("com.google.code.gson:gson:2.10.1")
|
|
|
|
// Navigation Component
|
|
val navVersion = "2.8.5"
|
|
implementation("androidx.navigation:navigation-fragment:$navVersion")
|
|
implementation("androidx.navigation:navigation-ui:$navVersion")
|
|
|
|
implementation("com.google.android.gms:play-services-auth:20.7.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")
|
|
|
|
// Swipe Refresh Layout
|
|
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\proguard-rules.pro
|
|
============================================================
|
|
# Add project specific ProGuard rules here.
|
|
# You can control the set of applied configuration files using the
|
|
# proguardFiles setting in build.gradle.
|
|
#
|
|
# For more details, see
|
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
|
|
# If your project uses WebView with JS, uncomment the following
|
|
# and specify the fully qualified class name to the JavaScript interface
|
|
# class:
|
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
# public *;
|
|
#}
|
|
|
|
# Uncomment this to preserve the line number information for
|
|
# debugging stack traces.
|
|
#-keepattributes SourceFile,LineNumberTable
|
|
|
|
# If you keep the line number information, uncomment this to
|
|
# hide the original source file name.
|
|
#-renamesourcefileattribute SourceFile
|
|
|
|
============================================================
|
|
FILSTI: app\src\androidTest\java\com\kbs\kbsintranett\ExampleInstrumentedTest.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import android.content.Context;
|
|
|
|
import androidx.test.platform.app.InstrumentationRegistry;
|
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
|
|
import org.junit.Test;
|
|
import org.junit.runner.RunWith;
|
|
|
|
import static org.junit.Assert.*;
|
|
|
|
/**
|
|
* Instrumented test, which will execute on an Android device.
|
|
*
|
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
*/
|
|
@RunWith(AndroidJUnit4.class)
|
|
public class ExampleInstrumentedTest {
|
|
@Test
|
|
public void useAppContext() {
|
|
// Context of the app under test.
|
|
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
|
assertEquals("com.kbs.kbsintranett", appContext.getPackageName());
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\AndroidManifest.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
xmlns:tools="http://schemas.android.com/tools">
|
|
|
|
<uses-permission android:name="android.permission.INTERNET" />
|
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" android:maxSdkVersion="32" />
|
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
|
<uses-permission android:name="android.permission.CAMERA" />
|
|
<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
|
|
android:allowBackup="true"
|
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
|
android:fullBackupContent="@xml/backup_rules"
|
|
android:icon="@mipmap/ic_launcher"
|
|
android:label="@string/app_name"
|
|
android:roundIcon="@mipmap/ic_launcher_round"
|
|
android:supportsRtl="true"
|
|
android:theme="@style/Theme.KBSIntranett"
|
|
android:usesCleartextTraffic="true"
|
|
tools:targetApi="31">
|
|
|
|
<activity
|
|
android:name=".MainActivity"
|
|
android:configChanges="orientation|screenSize|keyboardHidden"
|
|
android:exported="true">
|
|
<intent-filter>
|
|
<action android:name="android.intent.action.MAIN" />
|
|
<category android:name="android.intent.category.LAUNCHER" />
|
|
</intent-filter>
|
|
</activity>
|
|
|
|
<activity android:name=".WebViewActivity" />
|
|
|
|
<provider
|
|
android:name="androidx.core.content.FileProvider"
|
|
android:authorities="com.kbs.kbsintranett.fileprovider"
|
|
android:exported="false"
|
|
android:grantUriPermissions="true">
|
|
<meta-data
|
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
android:resource="@xml/file_paths" />
|
|
</provider>
|
|
|
|
</application>
|
|
|
|
</manifest>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\AuthRepository.java
|
|
============================================================
|
|
// FILSTI: app\src\main\java\com\kbs\kbsintranett\AuthRepository.java
|
|
package com.kbs.kbsintranett;
|
|
|
|
import android.util.Log;
|
|
import retrofit2.Call;
|
|
import retrofit2.Callback;
|
|
import retrofit2.Response;
|
|
|
|
public class AuthRepository {
|
|
|
|
private static final String TAG = "AuthRepository";
|
|
|
|
// Interface for å gi beskjed tilbake til Activity/Fragment
|
|
public interface AuthCallback {
|
|
void onSuccess(String role);
|
|
void onError(String message);
|
|
}
|
|
|
|
/**
|
|
* Utfører selve API-kallet mot WordPress.
|
|
* Denne brukes nå av både MainActivity (Silent Sign-In) og LoginFragment (Manuell).
|
|
*/
|
|
public static void loginToWordPress(String googleIdToken, String displayName, String email, String photoUrl, AuthCallback callback) {
|
|
|
|
// 1. Lagre Google-info midlertidig
|
|
UserManager.getInstance().setUserData(displayName, email, googleIdToken, photoUrl);
|
|
|
|
// 2. Gjør klar request
|
|
LoginRequest request = new LoginRequest(googleIdToken);
|
|
|
|
// 3. Send til WordPress
|
|
RetrofitClient.getApiService().googleLogin(request).enqueue(new Callback<LoginResponse>() {
|
|
@Override
|
|
public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {
|
|
if (response.isSuccessful() && response.body() != null && response.body().success) {
|
|
// SUKSESS!
|
|
String cookie = response.body().fullCookie;
|
|
String role = response.body().role;
|
|
|
|
// NYTT: Hent utvidet info fra responsen
|
|
int userId = response.body().userId;
|
|
String fName = response.body().firstName;
|
|
String lName = response.body().lastName;
|
|
String stilling = response.body().stilling;
|
|
String mobil = response.body().mobiltelefon;
|
|
|
|
Log.d(TAG, "WordPress Login suksess! Rolle: " + role + ", UserID: " + userId);
|
|
|
|
// Lagre cookie, rolle og ID
|
|
UserManager.getInstance().setCookie(cookie);
|
|
UserManager.getInstance().setUserRole(role);
|
|
UserManager.getInstance().setUserId(userId);
|
|
|
|
// Lagre utvidet info i UserManager
|
|
UserManager.getInstance().setExtendedUserInfo(fName, lName, stilling, mobil);
|
|
|
|
callback.onSuccess(role);
|
|
|
|
} else {
|
|
Log.e(TAG, "WordPress Login nektet. Kode: " + response.code());
|
|
callback.onError("Kunne ikke logge inn på Intranettet (Kode: " + response.code() + ")");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(Call<LoginResponse> call, Throwable t) {
|
|
Log.e(TAG, "Nettverksfeil mot WP", t);
|
|
callback.onError("Nettverksfeil: " + t.getMessage());
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarAdapter.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.widget.TextView;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.recyclerview.widget.RecyclerView;
|
|
import java.util.List;
|
|
|
|
public class CalendarAdapter extends RecyclerView.Adapter<CalendarAdapter.ViewHolder> {
|
|
|
|
private List<CalendarEvent> events;
|
|
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.listener = listener;
|
|
}
|
|
|
|
@NonNull
|
|
@Override
|
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_calendar, parent, false);
|
|
return new ViewHolder(view);
|
|
}
|
|
|
|
@Override
|
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
|
CalendarEvent event = events.get(position);
|
|
holder.day.setText(event.getDay());
|
|
holder.month.setText(event.getMonth());
|
|
holder.time.setText(event.getTime());
|
|
holder.title.setText(event.getTitle());
|
|
|
|
holder.itemView.setOnClickListener(v -> {
|
|
if (listener != null) {
|
|
listener.onItemClick(event);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public int getItemCount() {
|
|
return events.size();
|
|
}
|
|
|
|
public static class ViewHolder extends RecyclerView.ViewHolder {
|
|
TextView day, month, title, time;
|
|
|
|
public ViewHolder(View view) {
|
|
super(view);
|
|
day = view.findViewById(R.id.cal_day);
|
|
month = view.findViewById(R.id.cal_month);
|
|
title = view.findViewById(R.id.cal_title);
|
|
time = view.findViewById(R.id.cal_time);
|
|
}
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarDetailsBottomSheet.java
|
|
============================================================
|
|
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 nå 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();
|
|
}
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarEvent.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import com.google.gson.annotations.SerializedName;
|
|
|
|
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) {
|
|
this.title = title;
|
|
this.time = time;
|
|
this.day = day;
|
|
this.month = month;
|
|
}
|
|
|
|
public String getTitle() { return title; }
|
|
public String getRawDate() { return rawDate; }
|
|
public String getRawEndDate() { return rawEndDate; }
|
|
public String getDescription() { return description != null ? description : ""; }
|
|
public String getLocation() { return location != null ? location : ""; }
|
|
|
|
// 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; }
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarFullFragment.java
|
|
============================================================
|
|
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 (Nå 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 på toppen, men ikke helt (offset 0)
|
|
// Bruker scrollToPositionWithOffset for presisjon
|
|
if (scrollIndex > 0) {
|
|
layoutManager.scrollToPositionWithOffset(scrollIndex, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarManager.java
|
|
============================================================
|
|
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 {
|
|
|
|
// 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 på @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<>();
|
|
|
|
if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR)
|
|
!= PackageManager.PERMISSION_GRANTED) {
|
|
return deviceEvents;
|
|
}
|
|
|
|
// 1. Finn først ID-ene til KBS-kalenderne
|
|
List<String> kbsCalendarIds = getKbsCalendarIds(context);
|
|
|
|
// Hvis ingen kbs-kalendere finnes på 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);
|
|
long endMillis = now + (365L * 24 * 60 * 60 * 1000);
|
|
|
|
String[] projection = new String[]{
|
|
CalendarContract.Events.TITLE,
|
|
CalendarContract.Events.DTSTART,
|
|
CalendarContract.Events.DTEND,
|
|
CalendarContract.Events.DESCRIPTION,
|
|
CalendarContract.Events.EVENT_LOCATION,
|
|
CalendarContract.Events.ALL_DAY
|
|
};
|
|
|
|
// 2. Bygg opp spørringen for å filtrere på 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.toString(),
|
|
selectionArgs,
|
|
CalendarContract.Events.DTSTART + " ASC"
|
|
)) {
|
|
if (cursor != null) {
|
|
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) {
|
|
SimpleDateFormat shortFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
|
rawStart = shortFormat.format(new Date(dtStart));
|
|
rawEnd = shortFormat.format(new Date(dtEnd));
|
|
} else {
|
|
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;
|
|
}
|
|
|
|
// --- (formatEventForUI og mergeAndSort er uendret fra forrige versjon) ---
|
|
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();
|
|
|
|
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());
|
|
}
|
|
}
|
|
else if (raw.contains("T")) {
|
|
SimpleDateFormat isoFmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
|
|
isoFmt.setTimeZone(TimeZone.getTimeZone("Europe/Oslo"));
|
|
date = isoFmt.parse(raw);
|
|
if (event.getRawEndDate() != null && event.getRawEndDate().contains("T")) {
|
|
endDate = isoFmt.parse(event.getRawEndDate());
|
|
}
|
|
}
|
|
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) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\CategoryAdapter.java
|
|
============================================================
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\ChoicesAdapter.java
|
|
============================================================
|
|
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 ChoicesAdapter implements JsonDeserializer<List<GravityField.Choice>> {
|
|
@Override
|
|
public List<GravityField.Choice> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
|
// Hvis feltet er "null" eller en tom tekststreng, returner en tom liste
|
|
if (json.isJsonNull() || (json.isJsonPrimitive() && json.getAsString().isEmpty())) {
|
|
return new ArrayList<>();
|
|
}
|
|
|
|
// Hvis det faktisk er en liste (Array), les den som vanlig
|
|
if (json.isJsonArray()) {
|
|
List<GravityField.Choice> list = new ArrayList<>();
|
|
for (JsonElement e : json.getAsJsonArray()) {
|
|
list.add(context.deserialize(e, GravityField.Choice.class));
|
|
}
|
|
return list;
|
|
}
|
|
|
|
// Hvis vi får noe annet rart (f.eks. en tekst som ikke er tom), ignorer det for å unngå krasj
|
|
return new ArrayList<>();
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\ConditionalLogicAdapter.java
|
|
============================================================
|
|
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;
|
|
|
|
public class ConditionalLogicAdapter implements JsonDeserializer<GravityField.ConditionalLogic> {
|
|
@Override
|
|
public GravityField.ConditionalLogic deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
|
// Hvis feltet er en streng (f.eks tom streng ""), returner null
|
|
if (json.isJsonPrimitive() && json.getAsJsonPrimitive().isString()) {
|
|
return null;
|
|
}
|
|
|
|
// Hvis det er et objekt, bruk standard deserialisering
|
|
if (json.isJsonObject()) {
|
|
// Vi må manuelt deserialisere for å unngå uendelig løkke hvis vi bare kaller context.deserialize på samme type
|
|
// Enkleste måte er å la Gson gjøre jobben på innholdet
|
|
return new com.google.gson.Gson().fromJson(json, GravityField.ConditionalLogic.class);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\FormsFragment.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import android.Manifest;
|
|
import android.animation.LayoutTransition;
|
|
import android.app.Activity;
|
|
import android.app.AlertDialog;
|
|
import android.app.DatePickerDialog;
|
|
import android.app.TimePickerDialog;
|
|
import android.content.Intent;
|
|
import android.content.pm.PackageManager;
|
|
import android.database.Cursor;
|
|
import android.graphics.Color;
|
|
import android.graphics.Typeface;
|
|
import android.net.Uri;
|
|
import android.os.Bundle;
|
|
import android.os.Environment;
|
|
import android.provider.OpenableColumns;
|
|
import android.text.Editable;
|
|
import android.text.Html;
|
|
import android.text.InputType;
|
|
import android.text.TextUtils;
|
|
import android.text.TextWatcher;
|
|
import android.util.Log;
|
|
import android.view.LayoutInflater;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.widget.ArrayAdapter;
|
|
import android.widget.Button;
|
|
import android.widget.CheckBox;
|
|
import android.widget.EditText;
|
|
import android.widget.ImageView;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.ProgressBar;
|
|
import android.widget.RadioButton;
|
|
import android.widget.RadioGroup;
|
|
import android.widget.ScrollView;
|
|
import android.widget.Spinner;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
import androidx.activity.result.ActivityResultLauncher;
|
|
import androidx.activity.result.contract.ActivityResultContracts;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.core.content.ContextCompat;
|
|
import androidx.core.content.FileProvider;
|
|
import androidx.fragment.app.Fragment;
|
|
|
|
import com.google.gson.JsonElement;
|
|
import com.google.gson.JsonObject;
|
|
import com.google.gson.JsonArray;
|
|
import com.google.gson.JsonParser;
|
|
|
|
import org.json.JSONArray;
|
|
import org.json.JSONException;
|
|
import org.json.JSONObject;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.net.URLEncoder;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Calendar;
|
|
import java.util.Collections;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
import okhttp3.MediaType;
|
|
import okhttp3.MultipartBody;
|
|
import okhttp3.OkHttpClient;
|
|
import okhttp3.Request;
|
|
import okhttp3.RequestBody;
|
|
import okhttp3.Response;
|
|
import okio.BufferedSink;
|
|
import okio.Okio;
|
|
import okio.Source;
|
|
|
|
public class FormsFragment extends Fragment {
|
|
|
|
private static final String TAG = "FormsFragment";
|
|
private static final String BASE_URL_GF = "https://intranet.kbs.no/wp-json/gf/v2";
|
|
|
|
// SKJEMA ID-er
|
|
private static final int ID_ANSATTEOPPLYSNINGER = 1;
|
|
private static final int ID_RUH = 4;
|
|
private static final int ID_SIKKERHETSKURS = 9;
|
|
private static final int ID_HMS_BEKREFTELSE = 10;
|
|
private static final int ID_EGENMELDING = 11;
|
|
private static final int ID_SJA = 14;
|
|
private static final int ID_FRAVARSVARSEL = 15;
|
|
private static final int ID_REFUSJON_UTLEGG = 16;
|
|
|
|
private int formId = 1;
|
|
|
|
private LinearLayout formContainer;
|
|
private LinearLayout historyContainer;
|
|
private View historyWrapper; // Wrapper for historikk-modulen
|
|
private TextView txtStatus;
|
|
private TextView lblHistory;
|
|
private ProgressBar loadingSpinner;
|
|
private ImageView btnToggleHistory;
|
|
|
|
// --- HOVEDSKJEMA STATE ---
|
|
private Map<String, View> fieldWrappers = new HashMap<>();
|
|
private Map<String, View> inputViews = new HashMap<>();
|
|
private Map<String, Boolean> requiredFieldsMap = new HashMap<>();
|
|
private Map<String, Uri> fileUploads = new HashMap<>();
|
|
|
|
// --- NESTED FORM (BARN) STATE ---
|
|
private Map<String, View> childInputViews = new HashMap<>();
|
|
private Map<String, Boolean> childRequiredFieldsMap = new HashMap<>();
|
|
private Map<String, Uri> childFileUploads = new HashMap<>();
|
|
// Lagring av Nested Entries
|
|
private List<NestedEntry> nestedEntries = new ArrayList<>();
|
|
private LinearLayout nestedEntriesContainer;
|
|
private TextView totalAmountView;
|
|
|
|
// --- FILOPPLASTING & KAMERA ---
|
|
private String pendingFileFieldId = null;
|
|
private boolean isSelectingForChild = false;
|
|
private Uri currentPhotoUri = null;
|
|
|
|
private ActivityResultLauncher<Intent> filePickerLauncher;
|
|
private ActivityResultLauncher<Uri> takePictureLauncher;
|
|
private ActivityResultLauncher<String> requestPermissionLauncher;
|
|
|
|
private GravityForm currentForm;
|
|
private final OkHttpClient client = new OkHttpClient();
|
|
|
|
private static final Pattern TITLE_PATTERN = Pattern.compile("^(\\d+)[.\\s-]+\\s*(.*)");
|
|
|
|
@Override
|
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
filePickerLauncher = registerForActivityResult(
|
|
new ActivityResultContracts.StartActivityForResult(),
|
|
result -> {
|
|
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
|
|
Uri uri = result.getData().getData();
|
|
if (uri != null && pendingFileFieldId != null) {
|
|
handleFileSelection(pendingFileFieldId, uri, isSelectingForChild);
|
|
}
|
|
}
|
|
}
|
|
);
|
|
takePictureLauncher = registerForActivityResult(
|
|
new ActivityResultContracts.TakePicture(),
|
|
success -> {
|
|
if (success && currentPhotoUri != null && pendingFileFieldId != null) {
|
|
handleFileSelection(pendingFileFieldId, currentPhotoUri, isSelectingForChild);
|
|
} else if (!success) {
|
|
currentPhotoUri = null;
|
|
}
|
|
}
|
|
);
|
|
requestPermissionLauncher = registerForActivityResult(
|
|
new ActivityResultContracts.RequestPermission(),
|
|
isGranted -> {
|
|
if (isGranted) {
|
|
openCamera();
|
|
} else {
|
|
Toast.makeText(getContext(), "Kameratillatelse er påkrevd for å ta bilde.", Toast.LENGTH_LONG).show();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
View view = inflater.inflate(R.layout.fragment_forms, container, false);
|
|
formContainer = view.findViewById(R.id.form_container);
|
|
historyContainer = view.findViewById(R.id.historyContainer);
|
|
historyWrapper = view.findViewById(R.id.history_wrapper);
|
|
txtStatus = view.findViewById(R.id.txt_status);
|
|
lblHistory = view.findViewById(R.id.lbl_history);
|
|
loadingSpinner = view.findViewById(R.id.loading_spinner);
|
|
|
|
btnToggleHistory = view.findViewById(R.id.btn_toggle_history);
|
|
if (btnToggleHistory != null) {
|
|
btnToggleHistory.setOnClickListener(v -> toggleHistoryVisibility());
|
|
}
|
|
|
|
// --- FIKS FOR NULLPOINTER EXCEPTION PÅ LAYOUTTRANSITION ---
|
|
if (view instanceof ViewGroup) {
|
|
LayoutTransition transition = ((ViewGroup) view).getLayoutTransition();
|
|
if (transition == null) {
|
|
transition = new LayoutTransition();
|
|
((ViewGroup) view).setLayoutTransition(transition);
|
|
}
|
|
transition.enableTransitionType(LayoutTransition.CHANGING);
|
|
}
|
|
// ----------------------------------------------------------
|
|
|
|
if (formContainer == null) {
|
|
formContainer = new LinearLayout(getContext());
|
|
}
|
|
|
|
if (getArguments() != null) {
|
|
int argId = getArguments().getInt("formId", 0);
|
|
if (argId != 0) formId = argId;
|
|
}
|
|
|
|
fetchFormStructure();
|
|
|
|
return view;
|
|
}
|
|
|
|
// --- UI LOGIKK FOR DELT SKJERM ---
|
|
|
|
private void expandFormModule() {
|
|
if (historyWrapper != null && historyWrapper.getVisibility() == View.VISIBLE) {
|
|
historyWrapper.setVisibility(View.GONE);
|
|
if (btnToggleHistory != null) {
|
|
btnToggleHistory.setImageResource(android.R.drawable.arrow_down_float);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void toggleHistoryVisibility() {
|
|
if (historyWrapper == null || btnToggleHistory == null) return;
|
|
|
|
if (historyWrapper.getVisibility() == View.VISIBLE) {
|
|
// Skjul historikk
|
|
historyWrapper.setVisibility(View.GONE);
|
|
btnToggleHistory.setImageResource(android.R.drawable.arrow_down_float);
|
|
} else {
|
|
// Vis historikk
|
|
historyWrapper.setVisibility(View.VISIBLE);
|
|
btnToggleHistory.setImageResource(android.R.drawable.arrow_up_float);
|
|
}
|
|
}
|
|
|
|
private void attachInteractionListener(View view) {
|
|
if (view == null) return;
|
|
view.setOnTouchListener((v, event) -> {
|
|
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
|
expandFormModule();
|
|
}
|
|
return false;
|
|
});
|
|
view.setOnFocusChangeListener((v, hasFocus) -> {
|
|
if (hasFocus) {
|
|
expandFormModule();
|
|
}
|
|
});
|
|
if (view.isClickable()) {
|
|
view.setOnClickListener(v -> expandFormModule());
|
|
}
|
|
}
|
|
|
|
// ----------------------------------
|
|
|
|
private void fetchFormStructure() {
|
|
if (loadingSpinner != null) loadingSpinner.setVisibility(View.VISIBLE);
|
|
updateStatus("Laster skjema...");
|
|
|
|
WordPressApiService api = RetrofitClient.getApiService();
|
|
api.getForm(formId).enqueue(new retrofit2.Callback<GravityForm>() {
|
|
@Override
|
|
public void onResponse(retrofit2.Call<GravityForm> call, retrofit2.Response<GravityForm> response) {
|
|
if (response.isSuccessful() && response.body() != null) {
|
|
currentForm = response.body();
|
|
if (getActivity() != null) {
|
|
getActivity().runOnUiThread(() -> {
|
|
if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE);
|
|
renderDynamicForm(currentForm);
|
|
fetchFormEntries();
|
|
});
|
|
}
|
|
} else {
|
|
updateStatus("Feil ved lasting av skjema.");
|
|
if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(retrofit2.Call<GravityForm> call, Throwable t) {
|
|
updateStatus("Nettverksfeil (Skjema): " + t.getMessage());
|
|
if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void renderDynamicForm(GravityForm form) {
|
|
if (formContainer == null) return;
|
|
formContainer.removeAllViews();
|
|
fieldWrappers.clear();
|
|
inputViews.clear();
|
|
requiredFieldsMap.clear();
|
|
fileUploads.clear();
|
|
nestedEntries.clear();
|
|
updateStatus("");
|
|
|
|
// Reset visibility of history on new load
|
|
if (historyWrapper != null) {
|
|
historyWrapper.setVisibility(View.VISIBLE);
|
|
if (btnToggleHistory != null) btnToggleHistory.setImageResource(android.R.drawable.arrow_up_float);
|
|
}
|
|
|
|
TextView title = new TextView(getContext());
|
|
title.setText(getCleanTitle(form.title));
|
|
title.setTextSize(24);
|
|
title.setTypeface(null, Typeface.BOLD);
|
|
title.setTextColor(Color.BLACK);
|
|
title.setPadding(0, 0, 0, 20);
|
|
formContainer.addView(title);
|
|
|
|
if (form.description != null && !form.description.isEmpty()) {
|
|
TextView formDesc = new TextView(getContext());
|
|
String cleanDesc = form.description.replaceFirst("^\\d+\\.\\s*", "");
|
|
formDesc.setText(cleanDesc);
|
|
formDesc.setPadding(0, 0, 0, 40);
|
|
formContainer.addView(formDesc);
|
|
}
|
|
|
|
if (form.fields == null) return;
|
|
for (GravityField field : form.fields) {
|
|
if ("hidden".equals(field.type) || field.isHidden || "hidden".equals(field.visibility)) {
|
|
continue;
|
|
}
|
|
|
|
LinearLayout fieldWrapper = new LinearLayout(getContext());
|
|
fieldWrapper.setOrientation(LinearLayout.VERTICAL);
|
|
fieldWrapper.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
|
fieldWrapper.setPadding(0, 10, 0, 20);
|
|
|
|
fieldWrappers.put(String.valueOf(field.id), fieldWrapper);
|
|
|
|
if ("section".equals(field.type)) {
|
|
addSectionHeader(fieldWrapper, field.label, field.description);
|
|
formContainer.addView(fieldWrapper);
|
|
continue;
|
|
}
|
|
|
|
if ("html".equals(field.type)) {
|
|
if (field.content != null && !field.content.isEmpty()) {
|
|
TextView htmlView = new TextView(getContext());
|
|
htmlView.setText(Html.fromHtml(field.content, Html.FROM_HTML_MODE_COMPACT));
|
|
fieldWrapper.addView(htmlView);
|
|
}
|
|
formContainer.addView(fieldWrapper);
|
|
continue;
|
|
}
|
|
|
|
TextView label = new TextView(getContext());
|
|
String labelText = field.label;
|
|
if (field.isRequired) labelText += " *";
|
|
label.setText(labelText);
|
|
label.setTextColor(Color.DKGRAY);
|
|
label.setTypeface(null, Typeface.BOLD);
|
|
label.setPadding(0, 10, 0, 5);
|
|
fieldWrapper.addView(label);
|
|
|
|
if ("form".equals(field.type)) {
|
|
renderNestedFormField(fieldWrapper, field);
|
|
}
|
|
else if ("product".equals(field.type) && "calculation".equals(field.inputType)) {
|
|
renderTotalSumField(fieldWrapper, field);
|
|
}
|
|
else if ("time".equals(field.type)) {
|
|
renderTimeField(fieldWrapper, field, inputViews, requiredFieldsMap);
|
|
} else if ("fileupload".equals(field.type)) {
|
|
renderFileUploadField(fieldWrapper, field, inputViews, requiredFieldsMap, false);
|
|
} else if (field.inputs != null && !field.inputs.isEmpty()) {
|
|
if ("consent".equals(field.type)) {
|
|
renderConsentField(fieldWrapper, field, inputViews, requiredFieldsMap);
|
|
} else if ("checkbox".equals(field.type) || "multi_choice".equals(field.type)) {
|
|
renderCheckboxField(fieldWrapper, field, inputViews, requiredFieldsMap);
|
|
} else {
|
|
renderCompositeField(fieldWrapper, field, inputViews, requiredFieldsMap);
|
|
}
|
|
} else if ("radio".equals(field.type)) {
|
|
renderRadioField(fieldWrapper, field, inputViews, requiredFieldsMap);
|
|
} else if ("select".equals(field.type)) {
|
|
renderSelectField(fieldWrapper, field, inputViews, requiredFieldsMap);
|
|
} else if ("textarea".equals(field.type)) {
|
|
renderTextAreaField(fieldWrapper, field, inputViews, requiredFieldsMap);
|
|
} else if ("date".equals(field.type)) {
|
|
renderDateField(fieldWrapper, field, inputViews, requiredFieldsMap);
|
|
} else if ("consent".equals(field.type)) {
|
|
renderConsentField(fieldWrapper, field, inputViews, requiredFieldsMap);
|
|
} else {
|
|
renderTextField(fieldWrapper, field, inputViews, requiredFieldsMap);
|
|
}
|
|
|
|
if (field.description != null && !field.description.isEmpty()) {
|
|
TextView desc = new TextView(getContext());
|
|
desc.setText(Html.fromHtml(field.description, Html.FROM_HTML_MODE_COMPACT));
|
|
desc.setTextSize(12);
|
|
desc.setTextColor(Color.GRAY);
|
|
fieldWrapper.addView(desc);
|
|
}
|
|
|
|
formContainer.addView(fieldWrapper);
|
|
}
|
|
|
|
Button dynamicSubmit = new Button(getContext());
|
|
dynamicSubmit.setText("Send inn skjema");
|
|
dynamicSubmit.setTextColor(Color.WHITE);
|
|
dynamicSubmit.setBackgroundColor(Color.parseColor("#0069B3")); // KBS Blå
|
|
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
|
params.setMargins(0, 60, 0, 20);
|
|
dynamicSubmit.setLayoutParams(params);
|
|
dynamicSubmit.setOnClickListener(v -> submitDynamicForm());
|
|
formContainer.addView(dynamicSubmit);
|
|
|
|
evaluateAllConditionalLogic();
|
|
}
|
|
|
|
// --- NESTED FORM LOGIKK ---
|
|
|
|
private void renderNestedFormField(LinearLayout container, GravityField field) {
|
|
nestedEntriesContainer = new LinearLayout(getContext());
|
|
nestedEntriesContainer.setOrientation(LinearLayout.VERTICAL);
|
|
nestedEntriesContainer.setPadding(0, 10, 0, 10);
|
|
container.addView(nestedEntriesContainer);
|
|
|
|
Button btnAdd = new Button(getContext());
|
|
btnAdd.setText("Legg til vedlegg");
|
|
btnAdd.setBackgroundColor(Color.parseColor("#53AFE9"));
|
|
btnAdd.setTextColor(Color.WHITE);
|
|
btnAdd.setOnClickListener(v -> {
|
|
expandFormModule(); // Trigger expand
|
|
int childFormId = 18;
|
|
if (field.gpnfForm != null) {
|
|
try {
|
|
childFormId = Integer.parseInt(field.gpnfForm);
|
|
} catch (NumberFormatException e) { e.printStackTrace(); }
|
|
}
|
|
// NYTT: Sender med felt-ID fra hovedskjemaet (f.eks "25")
|
|
openChildFormDialog(childFormId, field.id);
|
|
});
|
|
container.addView(btnAdd);
|
|
|
|
EditText hiddenIds = new EditText(getContext());
|
|
hiddenIds.setVisibility(View.GONE);
|
|
inputViews.put(field.id, hiddenIds);
|
|
}
|
|
|
|
private void renderTotalSumField(LinearLayout container, GravityField field) {
|
|
totalAmountView = new TextView(getContext());
|
|
totalAmountView.setText("Kr 0,00");
|
|
totalAmountView.setTextSize(18);
|
|
totalAmountView.setTypeface(null, Typeface.BOLD);
|
|
totalAmountView.setPadding(10, 10, 10, 10);
|
|
container.addView(totalAmountView);
|
|
}
|
|
|
|
// NY SIGNATUR: Tar imot parentFieldId
|
|
private void openChildFormDialog(int childFormId, String parentFieldId) {
|
|
if (getActivity() == null) return;
|
|
ProgressBar pBar = new ProgressBar(getContext());
|
|
AlertDialog loadingDialog = new AlertDialog.Builder(getContext())
|
|
.setView(pBar)
|
|
.setMessage("Laster skjema...")
|
|
.setCancelable(false)
|
|
.show();
|
|
RetrofitClient.getApiService().getForm(childFormId).enqueue(new retrofit2.Callback<GravityForm>() {
|
|
@Override
|
|
public void onResponse(retrofit2.Call<GravityForm> call, retrofit2.Response<GravityForm> response) {
|
|
loadingDialog.dismiss();
|
|
if (response.isSuccessful() && response.body() != null) {
|
|
showChildFormDialog(response.body(), parentFieldId);
|
|
} else {
|
|
Toast.makeText(getContext(), "Kunne ikke hente underskjema", Toast.LENGTH_SHORT).show();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(retrofit2.Call<GravityForm> call, Throwable t) {
|
|
loadingDialog.dismiss();
|
|
Toast.makeText(getContext(), "Feil: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
|
}
|
|
});
|
|
}
|
|
|
|
// NY SIGNATUR: Tar imot parentFieldId
|
|
private void showChildFormDialog(GravityForm childForm, String parentFieldId) {
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
|
childInputViews.clear();
|
|
childRequiredFieldsMap.clear();
|
|
childFileUploads.clear();
|
|
|
|
ScrollView scrollView = new ScrollView(getContext());
|
|
LinearLayout layout = new LinearLayout(getContext());
|
|
layout.setOrientation(LinearLayout.VERTICAL);
|
|
layout.setPadding(30, 30, 30, 30);
|
|
scrollView.addView(layout);
|
|
for (GravityField field : childForm.fields) {
|
|
if ("hidden".equals(field.type) || field.isHidden) continue;
|
|
LinearLayout wrapper = new LinearLayout(getContext());
|
|
wrapper.setOrientation(LinearLayout.VERTICAL);
|
|
wrapper.setPadding(0, 10, 0, 20);
|
|
|
|
TextView label = new TextView(getContext());
|
|
String lText = field.label;
|
|
if (field.isRequired) lText += " *";
|
|
label.setText(lText);
|
|
label.setTypeface(null, Typeface.BOLD);
|
|
wrapper.addView(label);
|
|
if ("fileupload".equals(field.type)) {
|
|
renderFileUploadField(wrapper, field, childInputViews, childRequiredFieldsMap, true);
|
|
} else if ("product".equals(field.type)) {
|
|
EditText input = new EditText(getContext());
|
|
input.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
|
input.setHint("Kr 0.00");
|
|
wrapper.addView(input);
|
|
childInputViews.put(field.id, input);
|
|
childRequiredFieldsMap.put(field.id, field.isRequired);
|
|
} else {
|
|
renderTextField(wrapper, field, childInputViews, childRequiredFieldsMap);
|
|
}
|
|
layout.addView(wrapper);
|
|
}
|
|
|
|
builder.setView(scrollView);
|
|
builder.setPositiveButton("Legg til vedlegg", null);
|
|
builder.setNegativeButton("Avbryt", (d, w) -> d.dismiss());
|
|
AlertDialog dialog = builder.create();
|
|
dialog.show();
|
|
|
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
|
submitChildForm(childForm.id, dialog, parentFieldId);
|
|
});
|
|
}
|
|
|
|
// NY SIGNATUR: Tar imot parentFieldId og sender den som meta
|
|
private void submitChildForm(int childFormId, AlertDialog dialog, String parentFieldId) {
|
|
JSONObject inputValues = new JSONObject();
|
|
for (Map.Entry<String, View> entry : childInputViews.entrySet()) {
|
|
String val = getInputValueGeneric(entry.getValue());
|
|
if (!val.isEmpty()) {
|
|
try {
|
|
inputValues.put("input_" + entry.getKey(), val);
|
|
} catch (JSONException e) { e.printStackTrace(); }
|
|
}
|
|
}
|
|
|
|
if (!childFileUploads.isEmpty()) {
|
|
List<MultipartBody.Part> fileParts = new ArrayList<>();
|
|
Map<String, RequestBody> textParts = new HashMap<>();
|
|
|
|
try {
|
|
JSONArray names = inputValues.names();
|
|
if (names != null) {
|
|
for (int i = 0; i < names.length(); i++) {
|
|
String key = names.getString(i);
|
|
String val = inputValues.getString(key);
|
|
textParts.put(key, RequestBody.create(MultipartBody.FORM, val));
|
|
}
|
|
}
|
|
|
|
// --- HER ER FIKSEN FOR BUGGEN ---
|
|
// Vi legger ved en ekstra parameter som forteller Gravity Forms at dette
|
|
// er en nested entry som hører til et bestemt felt (f.eks "25").
|
|
if (parentFieldId != null) {
|
|
textParts.put("gpnf_entry_nested_form_field", RequestBody.create(MultipartBody.FORM, parentFieldId));
|
|
}
|
|
// ---------------------------------
|
|
|
|
for (Map.Entry<String, Uri> fileEntry : childFileUploads.entrySet()) {
|
|
String fieldId = fileEntry.getKey();
|
|
Uri uri = fileEntry.getValue();
|
|
if (uri != null) {
|
|
MultipartBody.Part part = getFilePart("input_" + fieldId, uri);
|
|
if (part != null) fileParts.add(part);
|
|
}
|
|
}
|
|
|
|
Toast.makeText(getContext(), "Laster opp vedlegg...", Toast.LENGTH_SHORT).show();
|
|
RetrofitClient.getApiService().submitMultipartForm(childFormId, textParts, fileParts).enqueue(new retrofit2.Callback<JsonElement>() {
|
|
@Override
|
|
public void onResponse(retrofit2.Call<JsonElement> call, retrofit2.Response<JsonElement> response) {
|
|
if (response.isSuccessful() && response.body() != null) {
|
|
try {
|
|
JsonObject json = response.body().getAsJsonObject();
|
|
if (json.has("is_valid") && json.get("is_valid").getAsBoolean()) {
|
|
String entryId = json.has("entry_id") ? json.get("entry_id").getAsString() : "";
|
|
|
|
// NB: Tilpass ID-ene her hvis skjema 18 endres.
|
|
// ID 3 = Beskrivelse, ID 4 = Beløp
|
|
String desc = getInputValueGeneric(childInputViews.get("3"));
|
|
String price = getInputValueGeneric(childInputViews.get("4"));
|
|
addNestedEntry(entryId, desc, price);
|
|
dialog.dismiss();
|
|
} else {
|
|
Toast.makeText(getContext(), "Ugyldig respons fra server", Toast.LENGTH_SHORT).show();
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
Toast.makeText(getContext(), "Feil ved parsing av svar", Toast.LENGTH_SHORT).show();
|
|
}
|
|
} else {
|
|
Toast.makeText(getContext(), "Feil ved opplasting", Toast.LENGTH_SHORT).show();
|
|
}
|
|
}
|
|
@Override
|
|
public void onFailure(retrofit2.Call<JsonElement> call, Throwable t) {
|
|
Toast.makeText(getContext(), "Nettverksfeil", Toast.LENGTH_SHORT).show();
|
|
}
|
|
});
|
|
} catch (Exception e) { e.printStackTrace(); }
|
|
}
|
|
}
|
|
|
|
private void addNestedEntry(String entryId, String description, String price) {
|
|
nestedEntries.add(new NestedEntry(entryId, description, price));
|
|
refreshNestedList();
|
|
}
|
|
|
|
private void refreshNestedList() {
|
|
if (nestedEntriesContainer == null) return;
|
|
nestedEntriesContainer.removeAllViews();
|
|
|
|
double total = 0;
|
|
List<String> ids = new ArrayList<>();
|
|
for (NestedEntry entry : nestedEntries) {
|
|
ids.add(entry.id);
|
|
String cleanPrice = entry.price.replaceAll("[^0-9,.]", "").replace(",", ".");
|
|
try {
|
|
if (!cleanPrice.isEmpty()) total += Double.parseDouble(cleanPrice);
|
|
} catch (NumberFormatException e) { }
|
|
|
|
LinearLayout row = new LinearLayout(getContext());
|
|
row.setOrientation(LinearLayout.HORIZONTAL);
|
|
row.setPadding(10, 10, 10, 10);
|
|
|
|
TextView txt = new TextView(getContext());
|
|
txt.setText(entry.description + " (" + entry.price + ")");
|
|
txt.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1));
|
|
|
|
row.addView(txt);
|
|
nestedEntriesContainer.addView(row);
|
|
}
|
|
|
|
if (totalAmountView != null) {
|
|
totalAmountView.setText("Totalt: Kr " + String.format("%.2f", total));
|
|
}
|
|
|
|
View hiddenField = inputViews.get("25");
|
|
if (hiddenField instanceof EditText) {
|
|
((EditText)hiddenField).setText(TextUtils.join(",", ids));
|
|
}
|
|
}
|
|
|
|
// --- FELLES METODER (FILE UPLOAD M/ CAMERA STØTTE) ---
|
|
|
|
private void renderFileUploadField(LinearLayout container, GravityField field, Map<String, View> viewsMap, Map<String, Boolean> reqMap, boolean isChild) {
|
|
LinearLayout fileLayout = new LinearLayout(getContext());
|
|
fileLayout.setOrientation(LinearLayout.HORIZONTAL);
|
|
|
|
Button btnUpload = new Button(getContext());
|
|
btnUpload.setText("Velg fil / Ta bilde");
|
|
btnUpload.setOnClickListener(v -> {
|
|
// Setter state før vi viser dialog
|
|
pendingFileFieldId = field.id;
|
|
isSelectingForChild = isChild;
|
|
expandFormModule(); // Trigger expand
|
|
showFileSourceDialog();
|
|
});
|
|
TextView txtFileName = new TextView(getContext());
|
|
txtFileName.setText("Ingen fil valgt");
|
|
txtFileName.setPadding(20, 0, 0, 0);
|
|
txtFileName.setTextColor(Color.GRAY);
|
|
btnUpload.setTag(txtFileName);
|
|
|
|
fileLayout.addView(btnUpload);
|
|
fileLayout.addView(txtFileName);
|
|
container.addView(fileLayout);
|
|
|
|
viewsMap.put(field.id, btnUpload);
|
|
reqMap.put(field.id, field.isRequired);
|
|
}
|
|
|
|
// Hjelpemetode for å vise dialog
|
|
private void showFileSourceDialog() {
|
|
String[] options = {"Ta bilde", "Velg fil"};
|
|
new AlertDialog.Builder(getContext())
|
|
.setTitle("Last opp vedlegg")
|
|
.setItems(options, (dialog, which) -> {
|
|
if (which == 0) {
|
|
// Ta bilde - SJEKKER PERMISSION FØRST
|
|
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)
|
|
== PackageManager.PERMISSION_GRANTED) {
|
|
openCamera();
|
|
} else {
|
|
// Spør om lov
|
|
requestPermissionLauncher.launch(Manifest.permission.CAMERA);
|
|
}
|
|
} else {
|
|
// Velg fil
|
|
if (filePickerLauncher != null) {
|
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
intent.setType("*/*");
|
|
String[] mimeTypes = {"image/jpeg", "image/png", "application/pdf"};
|
|
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
|
|
filePickerLauncher.launch(intent);
|
|
}
|
|
}
|
|
})
|
|
.show();
|
|
}
|
|
|
|
private void openCamera() {
|
|
currentPhotoUri = createImageUri();
|
|
if (currentPhotoUri != null && takePictureLauncher != null) {
|
|
try {
|
|
takePictureLauncher.launch(currentPhotoUri);
|
|
} catch (Exception e) {
|
|
Toast.makeText(getContext(), "Kunne ikke starte kamera: " + e.getMessage(), Toast.LENGTH_SHORT).show();
|
|
Log.e(TAG, "Camera launch failed", e);
|
|
}
|
|
} else {
|
|
Toast.makeText(getContext(), "Kunne ikke opprette bildefil", Toast.LENGTH_SHORT).show();
|
|
}
|
|
}
|
|
|
|
private Uri createImageUri() {
|
|
try {
|
|
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
|
|
String imageFileName = "JPEG_" + timeStamp + "_";
|
|
File storageDir = requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
|
|
File image = File.createTempFile(imageFileName, ".jpg", storageDir);
|
|
return FileProvider.getUriForFile(requireContext(), "com.kbs.kbsintranett.fileprovider", image);
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private void handleFileSelection(String fieldId, Uri uri, boolean isChild) {
|
|
if (isChild) {
|
|
childFileUploads.put(fieldId, uri);
|
|
} else {
|
|
fileUploads.put(fieldId, uri);
|
|
}
|
|
|
|
Map<String, View> targetMap = isChild ? childInputViews : inputViews;
|
|
View view = targetMap.get(fieldId);
|
|
if (view instanceof Button) {
|
|
TextView txtView = (TextView) view.getTag();
|
|
if (txtView != null) {
|
|
txtView.setText(getFileName(uri));
|
|
txtView.setTextColor(Color.BLACK);
|
|
}
|
|
}
|
|
}
|
|
|
|
private String getFileName(Uri uri) {
|
|
String result = null;
|
|
if (uri.getScheme().equals("content")) {
|
|
try (Cursor cursor = getContext().getContentResolver().query(uri, null, null, null, null)) {
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
|
if(index >= 0) result = cursor.getString(index);
|
|
}
|
|
} catch (Exception e) {}
|
|
}
|
|
if (result == null) {
|
|
result = uri.getPath();
|
|
int cut = result.lastIndexOf('/');
|
|
if (cut != -1) result = result.substring(cut + 1);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private String getCleanTitle(String title) {
|
|
if (title == null) return "";
|
|
Matcher m = TITLE_PATTERN.matcher(title.trim());
|
|
if (m.find()) {
|
|
return m.group(2);
|
|
}
|
|
return title;
|
|
}
|
|
|
|
// --- STANDARD RENDER METODER ---
|
|
|
|
private void renderTimeField(LinearLayout container, GravityField field, Map<String, View> views, Map<String, Boolean> req) {
|
|
EditText timeInput = new EditText(getContext());
|
|
timeInput.setFocusable(false);
|
|
timeInput.setClickable(true);
|
|
timeInput.setHint("00:00");
|
|
timeInput.setOnClickListener(v -> {
|
|
expandFormModule(); // Trigger expand
|
|
Calendar mcurrentTime = Calendar.getInstance();
|
|
int hour = mcurrentTime.get(Calendar.HOUR_OF_DAY);
|
|
int minute = mcurrentTime.get(Calendar.MINUTE);
|
|
new TimePickerDialog(getContext(), (timePicker, selectedHour, selectedMinute) -> {
|
|
timeInput.setText(String.format("%02d:%02d", selectedHour, selectedMinute));
|
|
evaluateAllConditionalLogic();
|
|
}, hour, minute, true).show();
|
|
});
|
|
container.addView(timeInput);
|
|
views.put(field.id, timeInput);
|
|
req.put(field.id, field.isRequired);
|
|
}
|
|
|
|
private void renderTextField(LinearLayout container, GravityField field, Map<String, View> views, Map<String, Boolean> req) {
|
|
EditText input = new EditText(getContext());
|
|
input.setPadding(30, 30, 30, 30);
|
|
input.setBackgroundResource(android.R.drawable.edit_text);
|
|
|
|
if ("number".equals(field.type) || "phone".equals(field.type)) {
|
|
input.setInputType(InputType.TYPE_CLASS_PHONE);
|
|
} else if ("email".equals(field.type)) {
|
|
input.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
|
|
} else {
|
|
input.setInputType(InputType.TYPE_CLASS_TEXT);
|
|
}
|
|
|
|
if (views == inputViews) {
|
|
UserManager user = UserManager.getInstance();
|
|
String lowerLabel = field.label.toLowerCase();
|
|
if (lowerLabel.contains("e-post")) input.setText(user.getUserEmail());
|
|
if (lowerLabel.contains("navn") || lowerLabel.contains("melder")) input.setText(user.getUserDisplayName());
|
|
if (lowerLabel.contains("stilling")) input.setText(user.getStilling());
|
|
if (lowerLabel.contains("mobil")) input.setText(user.getMobiltelefon());
|
|
}
|
|
|
|
input.addTextChangedListener(new TextWatcher() {
|
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
|
public void afterTextChanged(Editable s) { evaluateAllConditionalLogic(); }
|
|
});
|
|
attachInteractionListener(input); // Add listener
|
|
|
|
container.addView(input);
|
|
views.put(field.id, input);
|
|
req.put(field.id, field.isRequired);
|
|
}
|
|
|
|
private void renderTextAreaField(LinearLayout container, GravityField field, Map<String, View> views, Map<String, Boolean> req) {
|
|
EditText input = new EditText(getContext());
|
|
input.setBackgroundResource(android.R.drawable.edit_text);
|
|
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
|
|
input.setMinLines(3);
|
|
input.setGravity(android.view.Gravity.TOP | android.view.Gravity.START);
|
|
|
|
attachInteractionListener(input); // Add listener
|
|
|
|
container.addView(input);
|
|
views.put(field.id, input);
|
|
req.put(field.id, field.isRequired);
|
|
}
|
|
|
|
private void renderRadioField(LinearLayout container, GravityField field, Map<String, View> views, Map<String, Boolean> req) {
|
|
RadioGroup group = new RadioGroup(getContext());
|
|
if (field.choices != null) {
|
|
for (GravityField.Choice choice : field.choices) {
|
|
RadioButton rb = new RadioButton(getContext());
|
|
rb.setText(choice.text);
|
|
rb.setTag(choice.value);
|
|
// Also trigger expand on RadioButton click
|
|
rb.setOnClickListener(v -> {
|
|
expandFormModule();
|
|
evaluateAllConditionalLogic();
|
|
});
|
|
group.addView(rb);
|
|
}
|
|
}
|
|
// Fallback listener
|
|
group.setOnCheckedChangeListener((g, i) -> {
|
|
expandFormModule();
|
|
evaluateAllConditionalLogic();
|
|
});
|
|
container.addView(group);
|
|
views.put(field.id, group);
|
|
req.put(field.id, field.isRequired);
|
|
}
|
|
|
|
private void renderSelectField(LinearLayout container, GravityField field, Map<String, View> views, Map<String, Boolean> req) {
|
|
Spinner spinner = new Spinner(getContext());
|
|
// Spinner touch listener is tricky, usually set onTouchListener works
|
|
spinner.setOnTouchListener((v, event) -> {
|
|
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
|
expandFormModule();
|
|
}
|
|
return false;
|
|
});
|
|
List<String> labels = new ArrayList<>();
|
|
labels.add("- Velg -");
|
|
if (field.choices != null) {
|
|
for (GravityField.Choice c : field.choices) labels.add(c.text);
|
|
}
|
|
ArrayAdapter<String> adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, labels);
|
|
spinner.setAdapter(adapter);
|
|
container.addView(spinner);
|
|
views.put(field.id, spinner);
|
|
req.put(field.id, field.isRequired);
|
|
}
|
|
|
|
private void renderConsentField(LinearLayout container, GravityField field, Map<String, View> views, Map<String, Boolean> req) {
|
|
CheckBox checkBox = new CheckBox(getContext());
|
|
String cbText = (field.checkboxLabel != null && !field.checkboxLabel.isEmpty()) ? field.checkboxLabel : field.label;
|
|
checkBox.setText(cbText);
|
|
String inputId = (field.inputs != null && !field.inputs.isEmpty()) ? field.inputs.get(0).id : field.id;
|
|
|
|
checkBox.setTag("1");
|
|
checkBox.setOnCheckedChangeListener((b, c) -> {
|
|
expandFormModule();
|
|
evaluateAllConditionalLogic();
|
|
});
|
|
container.addView(checkBox);
|
|
views.put(inputId, checkBox);
|
|
req.put(inputId, field.isRequired);
|
|
}
|
|
|
|
private void renderCheckboxField(LinearLayout container, GravityField field, Map<String, View> views, Map<String, Boolean> req) {
|
|
if (field.inputs != null) {
|
|
for (int i = 0; i < field.inputs.size(); i++) {
|
|
GravityField inputDef = field.inputs.get(i);
|
|
CheckBox checkBox = new CheckBox(getContext());
|
|
checkBox.setText(inputDef.label);
|
|
|
|
String value = "1";
|
|
// Fallback
|
|
if (field.choices != null && i < field.choices.size()) {
|
|
value = field.choices.get(i).value;
|
|
}
|
|
checkBox.setTag(value);
|
|
checkBox.setOnCheckedChangeListener((b, c) -> {
|
|
expandFormModule();
|
|
evaluateAllConditionalLogic();
|
|
});
|
|
container.addView(checkBox);
|
|
views.put(inputDef.id, checkBox);
|
|
req.put(inputDef.id, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void renderDateField(LinearLayout container, GravityField field, Map<String, View> views, Map<String, Boolean> req) {
|
|
EditText dateInput = new EditText(getContext());
|
|
// --- DATO LØSNING (Fix for Read Only / Dagens Dato) ---
|
|
if (field.readOnly || (formId == ID_REFUSJON_UTLEGG && "28".equals(field.id))) {
|
|
// Sett dagens dato
|
|
SimpleDateFormat df = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault());
|
|
dateInput.setText(df.format(new Date()));
|
|
|
|
// Gjør den "read-only" men synlig
|
|
dateInput.setFocusable(false);
|
|
dateInput.setClickable(false);
|
|
dateInput.setEnabled(false);
|
|
dateInput.setTextColor(Color.BLACK);
|
|
} else {
|
|
// Vanlig dato-velger
|
|
dateInput.setFocusable(false);
|
|
dateInput.setClickable(true);
|
|
dateInput.setHint("dd.mm.yyyy");
|
|
dateInput.setOnClickListener(v -> {
|
|
expandFormModule(); // Trigger expand
|
|
Calendar c = Calendar.getInstance();
|
|
new DatePickerDialog(getContext(), (view, year, month, dayOfMonth) -> {
|
|
dateInput.setText(String.format("%02d.%02d.%d", dayOfMonth, month + 1, year));
|
|
evaluateAllConditionalLogic();
|
|
}, c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH)).show();
|
|
});
|
|
}
|
|
|
|
dateInput.setPadding(30, 30, 30, 30);
|
|
dateInput.setBackgroundResource(android.R.drawable.edit_text);
|
|
|
|
container.addView(dateInput);
|
|
views.put(field.id, dateInput);
|
|
req.put(field.id, field.isRequired);
|
|
}
|
|
|
|
private void renderCompositeField(LinearLayout container, GravityField parentField, Map<String, View> views, Map<String, Boolean> req) {
|
|
UserManager user = UserManager.getInstance();
|
|
boolean isPersonalia = (formId == ID_ANSATTEOPPLYSNINGER);
|
|
|
|
List<GravityField> inputs = new ArrayList<>(parentField.inputs);
|
|
if ("address".equals(parentField.type)) {
|
|
Collections.sort(inputs, (f1, f2) -> Integer.compare(getAddressScore(f1.label), getAddressScore(f2.label)));
|
|
}
|
|
|
|
for (GravityField subField : inputs) {
|
|
if (subField.isHidden || "hidden".equals(subField.visibility)) continue;
|
|
TextView subLabel = new TextView(getContext());
|
|
String subLabelText = subField.label;
|
|
boolean isSubRequired = parentField.isRequired;
|
|
if ("address".equals(parentField.type) && subField.id.endsWith(".2")) {
|
|
isSubRequired = false;
|
|
}
|
|
if (isSubRequired) subLabelText += " *";
|
|
|
|
subLabel.setText(subLabelText);
|
|
subLabel.setTextColor(Color.GRAY);
|
|
subLabel.setTextSize(12);
|
|
subLabel.setPadding(0, 10, 0, 0);
|
|
container.addView(subLabel);
|
|
|
|
EditText subInput = new EditText(getContext());
|
|
subInput.setPadding(30, 30, 30, 30);
|
|
subInput.setBackgroundResource(android.R.drawable.edit_text);
|
|
subInput.setInputType(InputType.TYPE_CLASS_TEXT);
|
|
if (isPersonalia && parentField.label.toLowerCase().contains("navn") && !parentField.label.toLowerCase().contains("pårørende")) {
|
|
String lowerSub = subField.label.toLowerCase();
|
|
if (lowerSub.contains("fornavn")) subInput.setText(user.getFirstName());
|
|
else if (lowerSub.contains("etternavn")) subInput.setText(user.getLastName());
|
|
}
|
|
|
|
attachInteractionListener(subInput);
|
|
container.addView(subInput);
|
|
views.put(subField.id, subInput);
|
|
req.put(subField.id, isSubRequired);
|
|
}
|
|
}
|
|
|
|
private void addSectionHeader(LinearLayout container, String title, String descText) {
|
|
TextView sectionHeader = new TextView(getContext());
|
|
sectionHeader.setText(title);
|
|
sectionHeader.setTextSize(18);
|
|
sectionHeader.setTypeface(null, Typeface.BOLD);
|
|
sectionHeader.setTextColor(Color.parseColor("#0069B3"));
|
|
sectionHeader.setPadding(0, 20, 0, 5);
|
|
container.addView(sectionHeader);
|
|
if (descText != null && !descText.isEmpty()) {
|
|
TextView desc = new TextView(getContext());
|
|
desc.setText(Html.fromHtml(descText, Html.FROM_HTML_MODE_COMPACT));
|
|
desc.setTextSize(12);
|
|
desc.setTextColor(Color.GRAY);
|
|
container.addView(desc);
|
|
}
|
|
|
|
View line = new View(getContext());
|
|
line.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 2));
|
|
line.setBackgroundColor(Color.LTGRAY);
|
|
line.setPadding(0,0,0,20);
|
|
container.addView(line);
|
|
}
|
|
|
|
private int getAddressScore(String label) {
|
|
if (label == null) return 99;
|
|
String l = label.toLowerCase();
|
|
if (l.contains("adresselinje 1")) return 1;
|
|
if (l.contains("adresselinje 2")) return 2;
|
|
if (l.contains("postnummer") || l.contains("zip")) return 3;
|
|
if (l.contains("poststed") || l.contains("city")) return 4;
|
|
if (l.contains("land") || l.contains("country")) return 5;
|
|
return 99;
|
|
}
|
|
|
|
private void evaluateAllConditionalLogic() {
|
|
if (currentForm == null || currentForm.fields == null) return;
|
|
for (GravityField field : currentForm.fields) {
|
|
if (field.conditionalLogic == null) {
|
|
setViewVisibility(field.id, true);
|
|
continue;
|
|
}
|
|
|
|
boolean isMatch = evaluateLogic(field.conditionalLogic);
|
|
boolean show = "show".equalsIgnoreCase(field.conditionalLogic.actionType);
|
|
boolean shouldBeVisible = (show && isMatch) || (!show && !isMatch);
|
|
setViewVisibility(field.id, shouldBeVisible);
|
|
}
|
|
}
|
|
|
|
private boolean evaluateLogic(GravityField.ConditionalLogic logic) {
|
|
if (logic.rules == null || logic.rules.isEmpty()) return true;
|
|
boolean isAll = "all".equalsIgnoreCase(logic.logicType);
|
|
boolean aggregatedResult = isAll;
|
|
|
|
for (GravityField.Rule rule : logic.rules) {
|
|
String val = getInputValue(rule.fieldId);
|
|
boolean ruleMatch = checkRule(val, rule.operator, rule.value);
|
|
|
|
if (isAll) {
|
|
aggregatedResult = aggregatedResult && ruleMatch;
|
|
if (!aggregatedResult) break;
|
|
} else {
|
|
aggregatedResult = aggregatedResult ||
|
|
ruleMatch;
|
|
if (aggregatedResult) break;
|
|
}
|
|
}
|
|
return aggregatedResult;
|
|
}
|
|
|
|
private boolean checkRule(String actualValue, String operator, String targetValue) {
|
|
if (actualValue == null) actualValue = "";
|
|
if (targetValue == null) targetValue = "";
|
|
|
|
switch (operator.toLowerCase()) {
|
|
case "is": return actualValue.equalsIgnoreCase(targetValue);
|
|
case "isnot": return !actualValue.equalsIgnoreCase(targetValue);
|
|
case "contains": return actualValue.toLowerCase().contains(targetValue.toLowerCase());
|
|
case "starts_with": return actualValue.toLowerCase().startsWith(targetValue.toLowerCase());
|
|
case "ends_with": return actualValue.toLowerCase().endsWith(targetValue.toLowerCase());
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
private String getInputValue(String fieldId) {
|
|
View view = inputViews.get(fieldId);
|
|
return getInputValueGeneric(view);
|
|
}
|
|
|
|
private String getInputValueGeneric(View view) {
|
|
if (view == null) return "";
|
|
if (view instanceof EditText) return ((EditText) view).getText().toString();
|
|
if (view instanceof RadioGroup) {
|
|
int id = ((RadioGroup) view).getCheckedRadioButtonId();
|
|
if (id != -1) {
|
|
View rb = view.findViewById(id);
|
|
if (rb != null && rb.getTag() != null) return rb.getTag().toString();
|
|
}
|
|
}
|
|
if (view instanceof Spinner) {
|
|
if (((Spinner) view).getSelectedItemPosition() == 0) return "";
|
|
Object item = ((Spinner) view).getSelectedItem();
|
|
return item != null ? item.toString() : "";
|
|
}
|
|
if (view instanceof CheckBox) {
|
|
CheckBox cb = (CheckBox) view;
|
|
if (cb.isChecked()) {
|
|
return cb.getTag() != null ?
|
|
cb.getTag().toString() : "1";
|
|
}
|
|
return "";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
private void setViewVisibility(String fieldId, boolean visible) {
|
|
View wrapper = fieldWrappers.get(fieldId);
|
|
if (wrapper != null) {
|
|
wrapper.setVisibility(visible ? View.VISIBLE : View.GONE);
|
|
}
|
|
}
|
|
|
|
// --- SUBMISSION ---
|
|
|
|
private void submitDynamicForm() {
|
|
JSONObject inputValues = new JSONObject();
|
|
boolean hasValues = false;
|
|
|
|
Log.d(TAG, "submitDynamicForm: Starting validation...");
|
|
|
|
for (Map.Entry<String, View> entry : inputViews.entrySet()) {
|
|
String fieldId = entry.getKey();
|
|
View view = entry.getValue();
|
|
|
|
View wrapper = fieldWrappers.get(fieldId);
|
|
if (wrapper == null) {
|
|
if (!view.isShown()) continue;
|
|
} else {
|
|
if (wrapper.getVisibility() != View.VISIBLE) continue;
|
|
}
|
|
|
|
String val = getInputValueGeneric(view);
|
|
Boolean req = requiredFieldsMap.get(fieldId);
|
|
if (req != null && req && val.isEmpty() && !(view instanceof Button)) {
|
|
Log.d(TAG, "Validation failed for field " + fieldId);
|
|
if (view instanceof EditText) {
|
|
((EditText)view).setError("Må fylles ut");
|
|
view.requestFocus();
|
|
} else {
|
|
Toast.makeText(getContext(), "Fyll ut alle felt", Toast.LENGTH_SHORT).show();
|
|
}
|
|
return;
|
|
}
|
|
if (!val.isEmpty()) {
|
|
try {
|
|
GravityField fieldDef = getGravityFieldById(fieldId);
|
|
if (fieldDef != null && "date".equals(fieldDef.type)) {
|
|
val = formatDateForApi(val);
|
|
}
|
|
|
|
inputValues.put("input_" + fieldId, val);
|
|
hasValues = true;
|
|
} catch (JSONException e) {}
|
|
}
|
|
}
|
|
|
|
if (!hasValues && fileUploads.isEmpty()) {
|
|
Log.d(TAG, "Submit aborted: Form is empty");
|
|
Toast.makeText(getContext(), "Skjemaet er tomt", Toast.LENGTH_SHORT).show();
|
|
return;
|
|
}
|
|
|
|
updateStatus("Sender inn...");
|
|
String cookie = UserManager.getInstance().getCookie();
|
|
Log.d(TAG, "Preparing submission payload: " + inputValues.toString());
|
|
|
|
if (!fileUploads.isEmpty()) {
|
|
Log.d(TAG, "Submitting as Multipart...");
|
|
sendMultipart(inputValues);
|
|
} else {
|
|
Log.d(TAG, "Submitting as JSON...");
|
|
RequestBody body = RequestBody.create(MediaType.parse("application/json"), inputValues.toString());
|
|
String url = BASE_URL_GF + "/forms/" + formId + "/submissions";
|
|
Request request = new Request.Builder().url(url).post(body).header("Cookie", cookie).build();
|
|
client.newCall(request).enqueue(new okhttp3.Callback() {
|
|
public void onFailure(okhttp3.Call call, IOException e) {
|
|
Log.e(TAG, "JSON submit failed", e);
|
|
updateStatus("Feil: " + e.getMessage());
|
|
}
|
|
public void onResponse(okhttp3.Call call, Response response) {
|
|
Log.d(TAG, "JSON response code: " + response.code());
|
|
if (response.isSuccessful()) {
|
|
if (getActivity() != null) getActivity().runOnUiThread(() -> {
|
|
Toast.makeText(getContext(), "Sendt!", Toast.LENGTH_LONG).show();
|
|
fetchFormEntries();
|
|
updateStatus("OK");
|
|
clearInputs();
|
|
});
|
|
} else {
|
|
try {
|
|
String errBody = response.body() != null ? response.body().string() : "No body";
|
|
Log.e(TAG, "Server error body: " + errBody);
|
|
updateStatus("Feil (" + response.code() + "): " + errBody);
|
|
} catch(Exception e){}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
private String formatDateForApi(String dateStr) {
|
|
if (dateStr == null || dateStr.isEmpty()) return "";
|
|
try {
|
|
SimpleDateFormat displayFormat = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault());
|
|
Date date = displayFormat.parse(dateStr);
|
|
SimpleDateFormat apiFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
|
return apiFormat.format(date);
|
|
} catch (Exception e) {
|
|
return dateStr;
|
|
}
|
|
}
|
|
|
|
private GravityField getGravityFieldById(String id) {
|
|
if (currentForm == null || currentForm.fields == null) return null;
|
|
for (GravityField f : currentForm.fields) {
|
|
if (f.id.equals(id)) return f;
|
|
if (f.inputs != null) {
|
|
for (GravityField sub : f.inputs) {
|
|
if (sub.id.equals(id)) return sub;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private void sendMultipart(JSONObject inputValues) {
|
|
List<MultipartBody.Part> fileParts = new ArrayList<>();
|
|
Map<String, RequestBody> textParts = new HashMap<>();
|
|
try {
|
|
JSONArray names = inputValues.names();
|
|
if (names != null) {
|
|
for(int i=0; i<names.length(); i++) {
|
|
String k = names.getString(i);
|
|
textParts.put(k, RequestBody.create(MultipartBody.FORM, inputValues.getString(k)));
|
|
}
|
|
}
|
|
for (Map.Entry<String, Uri> entry : fileUploads.entrySet()) {
|
|
MultipartBody.Part part = getFilePart("input_" + entry.getKey(), entry.getValue());
|
|
if (part != null) fileParts.add(part);
|
|
}
|
|
RetrofitClient.getApiService().submitMultipartForm(formId, textParts, fileParts).enqueue(new retrofit2.Callback<JsonElement>() {
|
|
public void onResponse(retrofit2.Call<JsonElement> call, retrofit2.Response<JsonElement> response) {
|
|
if (response.isSuccessful()) {
|
|
if (getActivity() != null) getActivity().runOnUiThread(() -> {
|
|
Toast.makeText(getContext(), "Sendt!", Toast.LENGTH_LONG).show();
|
|
fetchFormEntries();
|
|
updateStatus("OK");
|
|
clearInputs();
|
|
});
|
|
} else {
|
|
updateStatus("Feil: " + response.code());
|
|
}
|
|
}
|
|
public void onFailure(retrofit2.Call<JsonElement> call, Throwable t) { updateStatus("Feil: " + t.getMessage()); }
|
|
});
|
|
} catch (Exception e) {}
|
|
}
|
|
|
|
private MultipartBody.Part getFilePart(String partName, Uri uri) {
|
|
try {
|
|
InputStream inputStream = getContext().getContentResolver().openInputStream(uri);
|
|
String fileName = getFileName(uri);
|
|
RequestBody requestBody = new RequestBody() {
|
|
@Override public MediaType contentType() { return MediaType.parse("application/octet-stream");
|
|
}
|
|
@Override public void writeTo(BufferedSink sink) throws IOException {
|
|
try (Source source = Okio.source(inputStream)) { sink.writeAll(source);
|
|
}
|
|
}
|
|
};
|
|
return MultipartBody.Part.createFormData(partName, fileName, requestBody);
|
|
} catch (Exception e) { return null;
|
|
}
|
|
}
|
|
|
|
private void fetchFormEntries() {
|
|
UserManager user = UserManager.getInstance();
|
|
String cookie = user.getCookie();
|
|
int userId = user.getUserId();
|
|
|
|
if (cookie == null) return;
|
|
String searchJson = "{\"field_filters\":[{\"key\":\"created_by\",\"value\":\"" + userId + "\"}]}";
|
|
String encodedSearch = "";
|
|
try {
|
|
encodedSearch = URLEncoder.encode(searchJson, "UTF-8");
|
|
} catch (UnsupportedEncodingException e) { e.printStackTrace(); }
|
|
|
|
String url = BASE_URL_GF + "/entries?form_ids=" + formId + "&search=" + encodedSearch;
|
|
Request request = new Request.Builder().url(url).header("Cookie", cookie).build();
|
|
|
|
client.newCall(request).enqueue(new okhttp3.Callback() {
|
|
@Override
|
|
public void onFailure(@NonNull okhttp3.Call call, @NonNull IOException e) {
|
|
Log.e(TAG, "Kunne ikke hente historikk", e);
|
|
}
|
|
|
|
@Override
|
|
public void onResponse(@NonNull okhttp3.Call call, @NonNull Response response) throws IOException {
|
|
if (response.isSuccessful()) {
|
|
String jsonStr = response.body().string();
|
|
try {
|
|
JSONObject json = new JSONObject(jsonStr);
|
|
if (json.has("entries")) {
|
|
JSONArray entries = json.getJSONArray("entries");
|
|
if (getActivity() != null) {
|
|
getActivity().runOnUiThread(() -> {
|
|
showHistory(entries);
|
|
if (formId == ID_ANSATTEOPPLYSNINGER && entries.length() > 0)
|
|
{
|
|
try {
|
|
prefillFormFromHistory(entries.getJSONObject(0));
|
|
} catch (JSONException e) { e.printStackTrace(); }
|
|
}
|
|
});
|
|
}
|
|
}
|
|
} catch (JSONException e) { e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private void showHistory(JSONArray entries) {
|
|
if (historyContainer == null) return;
|
|
historyContainer.removeAllViews();
|
|
|
|
if (entries.length() == 0) {
|
|
if (lblHistory != null) lblHistory.setVisibility(View.GONE);
|
|
return;
|
|
} else {
|
|
if (lblHistory != null) lblHistory.setVisibility(View.VISIBLE);
|
|
}
|
|
|
|
try {
|
|
// Vis flere oppføringer siden vi nå har scrolle-mulighet øverst
|
|
int count = Math.min(entries.length(), 20);
|
|
for (int i = 0; i < count; i++) {
|
|
JSONObject entry = entries.getJSONObject(i);
|
|
String date = entry.optString("date_created");
|
|
|
|
// Prøv å finne en bedre tittel enn bare dato (f.eks Prosjektnavn eller Sted)
|
|
String titleText = "Innsendt: " + date;
|
|
TextView item = new TextView(getContext());
|
|
item.setText(titleText);
|
|
item.setPadding(10, 20, 10, 20);
|
|
item.setBackgroundResource(android.R.drawable.list_selector_background);
|
|
item.setTextSize(14);
|
|
// Add click listener to show details
|
|
item.setOnClickListener(v -> showEntryDetails(entry));
|
|
View line = new View(getContext());
|
|
line.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1));
|
|
line.setBackgroundColor(Color.LTGRAY);
|
|
|
|
historyContainer.addView(item);
|
|
historyContainer.addView(line);
|
|
}
|
|
} catch (JSONException e) { e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
// NY METODE: Vis detaljer i dialog med delingsknapp
|
|
private void showEntryDetails(JSONObject entry) {
|
|
// HVIS dette er Form 16 (Refusjon), må vi først hente vedleggene (som ligger i Form 18)
|
|
if (formId == ID_REFUSJON_UTLEGG) {
|
|
Log.d(TAG, "Form 16 detected. Checking for child entries...");
|
|
String nestedIds = entry.optString("25"); // Felt 25 i Form 16 inneholder ID-ene til vedleggene
|
|
|
|
if (!nestedIds.isEmpty()) {
|
|
Log.d(TAG, "Nested IDs found: " + nestedIds);
|
|
List<String> ids = new ArrayList<>();
|
|
|
|
// ROBUST PARSING AV ID-LISTE
|
|
if (nestedIds.startsWith("[") && nestedIds.endsWith("]")) {
|
|
// Dette er en JSON-array streng (f.eks [101, 102])
|
|
try {
|
|
JSONArray jsonArray = new JSONArray(nestedIds);
|
|
for(int i=0; i<jsonArray.length(); i++) {
|
|
ids.add(jsonArray.getString(i));
|
|
}
|
|
} catch(JSONException e) {
|
|
Log.e(TAG, "Failed to parse nested IDs as JSON array", e);
|
|
}
|
|
} else {
|
|
// Dette er en komma-separert streng (f.eks "101, 102")
|
|
for (String id : nestedIds.split(",")) {
|
|
ids.add(id.trim());
|
|
}
|
|
}
|
|
|
|
if (!ids.isEmpty()) {
|
|
// Vis en "Laster..." dialog mens vi henter data
|
|
AlertDialog loadingDialog = new AlertDialog.Builder(getContext())
|
|
.setMessage("Henter vedleggsdetaljer...")
|
|
.setCancelable(false)
|
|
.show();
|
|
|
|
// Start rekursiv henting
|
|
StringBuilder accumulatedHtml = new StringBuilder();
|
|
StringBuilder accumulatedText = new StringBuilder();
|
|
|
|
// Legg først til basis-info fra hovedskjemaet
|
|
appendBasicInfo(entry, accumulatedHtml, accumulatedText);
|
|
|
|
fetchChildEntriesRecursive(ids, 0, accumulatedHtml, accumulatedText, loadingDialog);
|
|
return; // Stopp her, dialogen vises når data er hentet
|
|
}
|
|
} else {
|
|
Log.d(TAG, "No nested IDs found in field 25.");
|
|
}
|
|
}
|
|
|
|
// --- STANDARD VISNING (For alle andre skjemaer) ---
|
|
StringBuilder htmlBuilder = new StringBuilder();
|
|
StringBuilder textBuilder = new StringBuilder();
|
|
appendBasicInfo(entry, htmlBuilder, textBuilder);
|
|
showFinalDialog(htmlBuilder, textBuilder);
|
|
}
|
|
|
|
// Hjelpemetode for å legge til info fra hovedskjemaet
|
|
private void appendBasicInfo(JSONObject entry, StringBuilder html, StringBuilder text) {
|
|
try {
|
|
String date = entry.optString("date_created");
|
|
html.append("<b>Innsendt:</b> ").append(date).append("<br><br>");
|
|
text.append("Innsendt: ").append(date).append("\n\n");
|
|
|
|
if (currentForm != null && currentForm.fields != null) {
|
|
for (GravityField field : currentForm.fields) {
|
|
if ("section".equals(field.type) || "html".equals(field.type) || "captcha".equals(field.type)) continue;
|
|
// Hopp over felt 25 (Vedleggs-IDer) i Form 16, siden vi viser det bedre senere
|
|
if (formId == ID_REFUSJON_UTLEGG && "25".equals(field.id)) continue;
|
|
|
|
String value = "";
|
|
if (field.inputs != null && !field.inputs.isEmpty()) {
|
|
for (GravityField input : field.inputs) {
|
|
String subVal = entry.optString(input.id);
|
|
if (!subVal.isEmpty()) value += " " + subVal;
|
|
}
|
|
} else {
|
|
value = entry.optString(String.valueOf(field.id));
|
|
}
|
|
|
|
if (!value.trim().isEmpty()) {
|
|
if ("fileupload".equals(field.type)) {
|
|
// Håndter filopplastinger (single/multiple)
|
|
// Her antar vi enkel URL, men for sikkerhets skyld bruker vi extractUrl
|
|
String cleanUrl = extractUrl(value);
|
|
if (cleanUrl.startsWith("http")) {
|
|
html.append("<b>").append(field.label).append(":</b><br>")
|
|
.append("<a href=\"").append(cleanUrl).append("\">Åpne fil</a><br><br>");
|
|
text.append(field.label).append(":\n").append(cleanUrl).append("\n\n");
|
|
} else {
|
|
html.append("<b>").append(field.label).append(":</b><br>").append(value).append("<br><br>");
|
|
text.append(field.label).append(":\n").append(value).append("\n\n");
|
|
}
|
|
} else {
|
|
html.append("<b>").append(field.label).append(":</b><br>").append(value).append("<br><br>");
|
|
text.append(field.label).append(":\n").append(value).append("\n\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception e) {}
|
|
}
|
|
|
|
// Rekursiv metode for å hente barn-oppføringer (Vedlegg)
|
|
private void fetchChildEntriesRecursive(List<String> ids, int index, StringBuilder html, StringBuilder text, AlertDialog loader) {
|
|
if (index >= ids.size()) {
|
|
// Alle ferdige! Vis dialogen.
|
|
loader.dismiss();
|
|
showFinalDialog(html, text);
|
|
return;
|
|
}
|
|
|
|
String entryId = ids.get(index);
|
|
Log.d(TAG, "Fetching child entry ID: " + entryId);
|
|
|
|
RetrofitClient.getApiService().getSingleEntry(entryId).enqueue(new retrofit2.Callback<JsonElement>() {
|
|
@Override
|
|
public void onResponse(retrofit2.Call<JsonElement> call, retrofit2.Response<JsonElement> response) {
|
|
if (response.isSuccessful() && response.body() != null) {
|
|
try {
|
|
JsonObject json = response.body().getAsJsonObject();
|
|
// I Form 18: Felt 1 = Fil, Felt 3 = Beskrivelse, Felt 4 = Beløp
|
|
|
|
// Parse Description and Price
|
|
String desc = json.has("3") ? json.get("3").getAsString() : "Uten beskrivelse";
|
|
String price = json.has("4") ? json.get("4").getAsString() : "";
|
|
|
|
html.append("<b>Vedlegg ").append(index + 1).append(":</b><br>");
|
|
text.append("Vedlegg ").append(index + 1).append(":\n");
|
|
|
|
html.append(desc).append(" (").append(price).append(")<br>");
|
|
text.append(desc).append(" (").append(price).append(")\n");
|
|
|
|
// Parse File Field (ID 1) - Can be multiple files!
|
|
if (json.has("1")) {
|
|
JsonElement fileEl = json.get("1");
|
|
if (fileEl.isJsonArray()) {
|
|
// It is a real JSON array
|
|
JsonArray arr = fileEl.getAsJsonArray();
|
|
for (int i = 0; i < arr.size(); i++) {
|
|
String url = arr.get(i).getAsString().replace("\\/", "/");
|
|
html.append("<a href=\"").append(url).append("\">Åpne fil ").append(i+1).append("</a><br>");
|
|
text.append(url).append("\n");
|
|
}
|
|
} else {
|
|
// It is a string. Check if it's a JSON string array like "[\"http...\"]"
|
|
String rawString = fileEl.getAsString();
|
|
if (rawString.startsWith("[") && rawString.endsWith("]")) {
|
|
try {
|
|
JSONArray arr = new JSONArray(rawString);
|
|
for (int i = 0; i < arr.length(); i++) {
|
|
String url = arr.getString(i).replace("\\/", "/");
|
|
html.append("<a href=\"").append(url).append("\">Åpne fil ").append(i+1).append("</a><br>");
|
|
text.append(url).append("\n");
|
|
}
|
|
} catch (JSONException ex) {
|
|
// Fallback: simple cleanup
|
|
String clean = extractUrl(rawString);
|
|
html.append("<a href=\"").append(clean).append("\">Åpne fil</a><br>");
|
|
text.append(clean).append("\n");
|
|
}
|
|
} else {
|
|
// Just a plain URL string
|
|
String clean = extractUrl(rawString);
|
|
if(clean.startsWith("http")) {
|
|
html.append("<a href=\"").append(clean).append("\">Åpne fil</a><br>");
|
|
text.append(clean).append("\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
html.append("<br>");
|
|
text.append("\n");
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error parsing child entry", e);
|
|
}
|
|
} else {
|
|
Log.e(TAG, "Failed to fetch child entry: " + response.code());
|
|
}
|
|
// Gå til neste (uansett om denne feilet eller ei)
|
|
fetchChildEntriesRecursive(ids, index + 1, html, text, loader);
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(retrofit2.Call<JsonElement> call, Throwable t) {
|
|
Log.e(TAG, "Network error fetching child entry", t);
|
|
// Hopp over ved feil
|
|
fetchChildEntriesRecursive(ids, index + 1, html, text, loader);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void showFinalDialog(StringBuilder htmlBuilder, StringBuilder textBuilder) {
|
|
ScrollView scroll = new ScrollView(getContext());
|
|
TextView text = new TextView(getContext());
|
|
text.setMovementMethod(android.text.method.LinkMovementMethod.getInstance());
|
|
text.setText(Html.fromHtml(htmlBuilder.toString(), Html.FROM_HTML_MODE_COMPACT));
|
|
text.setPadding(40, 40, 40, 40);
|
|
scroll.addView(text);
|
|
|
|
new AlertDialog.Builder(getContext())
|
|
.setTitle("Detaljer")
|
|
.setView(scroll)
|
|
.setPositiveButton("Lukk", null)
|
|
.setNeutralButton("Del", (d, w) -> shareEntryDetails(textBuilder.toString()))
|
|
.create()
|
|
.show();
|
|
}
|
|
|
|
// Hjelpemetode for å rydde opp i URLer fra JSON (f.eks ["http://..."] -> http://...)
|
|
private String extractUrl(String rawValue) {
|
|
if (rawValue == null) return "";
|
|
String clean = rawValue.replace("[", "")
|
|
.replace("]", "")
|
|
.replace("\"", "")
|
|
.replace("\\/", "/");
|
|
return clean.trim();
|
|
}
|
|
|
|
private void shareEntryDetails(String text) {
|
|
Intent sendIntent = new Intent();
|
|
sendIntent.setAction(Intent.ACTION_SEND);
|
|
sendIntent.putExtra(Intent.EXTRA_TEXT, text);
|
|
sendIntent.setType("text/plain");
|
|
|
|
Intent shareIntent = Intent.createChooser(sendIntent, "Del innsending via...");
|
|
startActivity(shareIntent);
|
|
}
|
|
|
|
private void prefillFormFromHistory(JSONObject latestEntry) {
|
|
if (latestEntry == null) return;
|
|
for (Map.Entry<String, View> entry : inputViews.entrySet()) {
|
|
String fieldId = entry.getKey();
|
|
View view = entry.getValue();
|
|
|
|
if (latestEntry.has(fieldId)) {
|
|
String value = latestEntry.optString(fieldId);
|
|
if (value == null || value.isEmpty()) continue;
|
|
|
|
if (view instanceof EditText) {
|
|
((EditText) view).setText(value);
|
|
} else if (view instanceof RadioGroup) {
|
|
RadioGroup group = (RadioGroup) view;
|
|
for (int i = 0; i < group.getChildCount(); i++) {
|
|
View child = group.getChildAt(i);
|
|
if (child instanceof RadioButton) {
|
|
Object tag = child.getTag();
|
|
if (tag != null && tag.toString().equalsIgnoreCase(value)) {
|
|
((RadioButton) child).setChecked(true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else if (view instanceof CheckBox) {
|
|
if ("1".equals(value) || "true".equalsIgnoreCase(value) || ((CheckBox)view).getText().toString().equals(value)) {
|
|
((CheckBox) view).setChecked(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
updateStatus("Skjemaet er forhåndsutfylt fra din siste innsending.");
|
|
evaluateAllConditionalLogic();
|
|
}
|
|
|
|
private void clearInputs() {
|
|
for (View view : inputViews.values()) {
|
|
if (view instanceof EditText) {
|
|
((EditText) view).setText("");
|
|
} else if (view instanceof CheckBox) {
|
|
((CheckBox) view).setChecked(false);
|
|
} else if (view instanceof RadioGroup) {
|
|
((RadioGroup) view).clearCheck();
|
|
} else if (view instanceof Button) {
|
|
Object tag = view.getTag();
|
|
if (tag instanceof TextView) {
|
|
((TextView) tag).setText("Ingen fil valgt");
|
|
}
|
|
}
|
|
}
|
|
fileUploads.clear();
|
|
nestedEntries.clear();
|
|
if (nestedEntriesContainer != null) nestedEntriesContainer.removeAllViews();
|
|
if (totalAmountView != null) totalAmountView.setText("Kr 0,00");
|
|
}
|
|
|
|
private static class NestedEntry {
|
|
String id;
|
|
String description;
|
|
String price;
|
|
NestedEntry(String id, String d, String p) { this.id = id; this.description = d; this.price = p;
|
|
}
|
|
}
|
|
|
|
private void updateStatus(String msg) {
|
|
if (getActivity() != null && txtStatus != null) {
|
|
getActivity().runOnUiThread(() -> txtStatus.setText(msg));
|
|
}
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\FormsListFragment.java
|
|
============================================================
|
|
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 androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
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 SwipeRefreshLayout swipeRefreshLayout;
|
|
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);
|
|
|
|
formsContainer = view.findViewById(R.id.forms_container);
|
|
swipeRefreshLayout = view.findViewById(R.id.swipe_refresh);
|
|
|
|
// Opprett en ProgressBar manuelt for første gangs lasting
|
|
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);
|
|
|
|
// Sett opp listener for swipe
|
|
swipeRefreshLayout.setOnRefreshListener(() -> {
|
|
fetchFormsList();
|
|
});
|
|
|
|
// Hent data første gang
|
|
fetchFormsList();
|
|
|
|
return view;
|
|
}
|
|
|
|
private void fetchFormsList() {
|
|
// Skjul feilmelding før ny henting
|
|
if (errorText != null) errorText.setVisibility(View.GONE);
|
|
|
|
RetrofitClient.getApiService().getFormsList().enqueue(new Callback<List<GravityForm>>() {
|
|
@Override
|
|
public void onResponse(Call<List<GravityForm>> call, Response<List<GravityForm>> response) {
|
|
if (!isAdded()) return;
|
|
|
|
// Stopp lasting-indikatorer
|
|
progressBar.setVisibility(View.GONE);
|
|
swipeRefreshLayout.setRefreshing(false);
|
|
|
|
if (response.isSuccessful() && response.body() != null) {
|
|
List<GravityForm> allForms = response.body();
|
|
List<GravityForm> activeForms = new ArrayList<>();
|
|
|
|
for (GravityForm form : allForms) {
|
|
if (form.getIsActive()) {
|
|
// NYTT: Hardkodet filtrering av skjemaer som ikke skal vises i listen
|
|
// ID 10 = HMS-bekreftelse (Skal ligge i Håndbok/separat flyt)
|
|
// ID 18 = Refusjon-vedlegg (Er et underskjema som brukes av ID 16)
|
|
if (form.id == 10 || form.id == 18) {
|
|
continue;
|
|
}
|
|
|
|
activeForms.add(form);
|
|
}
|
|
}
|
|
|
|
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. Prøv å logge ut og inn igjen.)";
|
|
}
|
|
showError(msg);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(Call<List<GravityForm>> call, Throwable t) {
|
|
if (!isAdded()) return;
|
|
|
|
// Stopp lasting-indikatorer
|
|
progressBar.setVisibility(View.GONE);
|
|
swipeRefreshLayout.setRefreshing(false);
|
|
|
|
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) {
|
|
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"));
|
|
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);
|
|
btn.setLayoutParams(params);
|
|
|
|
btn.setOnClickListener(v -> {
|
|
Bundle bundle = new Bundle();
|
|
bundle.putInt("formId", formId);
|
|
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;
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\FormSubmission.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import com.google.gson.annotations.SerializedName;
|
|
import java.util.Map;
|
|
|
|
public class FormSubmission {
|
|
// Gravity Forms krever at dataene ligger inni "input_values"
|
|
@SerializedName("input_values")
|
|
public Map<String, String> inputValues;
|
|
|
|
public FormSubmission(Map<String, String> inputValues) {
|
|
this.inputValues = inputValues;
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\GravityEntryResponse.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import com.google.gson.annotations.SerializedName;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
public class GravityEntryResponse {
|
|
@SerializedName("total_count")
|
|
public int totalCount;
|
|
|
|
@SerializedName("entries")
|
|
public List<Map<String, String>> entries;
|
|
// Vi bruker Map<String, String> fordi Gravity Forms returnerer alle feltverdier som nøkkel/verdi par i roten av objektet.
|
|
// F.eks: { "id": "100", "form_id": "1", "1.3": "Ola", "1.6": "Nordmann" }
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\GravityField.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import com.google.gson.annotations.JsonAdapter;
|
|
import com.google.gson.annotations.SerializedName;
|
|
import java.util.List;
|
|
|
|
public class GravityField {
|
|
@SerializedName("id")
|
|
public String id;
|
|
|
|
@SerializedName("type")
|
|
public String type;
|
|
|
|
@SerializedName("inputType")
|
|
public String inputType;
|
|
|
|
@SerializedName("label")
|
|
public String label;
|
|
|
|
@SerializedName("adminLabel")
|
|
public String adminLabel;
|
|
|
|
@SerializedName("description")
|
|
public String description;
|
|
|
|
@SerializedName("defaultValue")
|
|
public String defaultValue;
|
|
|
|
@SerializedName("isRequired")
|
|
public boolean isRequired;
|
|
|
|
@SerializedName("checkboxLabel")
|
|
public String checkboxLabel;
|
|
|
|
@SerializedName("visibility")
|
|
public String visibility;
|
|
|
|
@JsonAdapter(ChoicesAdapter.class)
|
|
@SerializedName("choices")
|
|
public List<Choice> choices;
|
|
|
|
@SerializedName("content")
|
|
public String content;
|
|
|
|
// --- BRUKER ADAPTEREN HER ---
|
|
@JsonAdapter(InputsAdapter.class)
|
|
@SerializedName("inputs")
|
|
public List<GravityField> inputs;
|
|
// ---------------------------
|
|
|
|
@SerializedName("isHidden")
|
|
public boolean isHidden;
|
|
|
|
@SerializedName("gwreadonly_enable")
|
|
public boolean readOnly;
|
|
|
|
@JsonAdapter(ConditionalLogicAdapter.class)
|
|
@SerializedName("conditionalLogic")
|
|
public ConditionalLogic conditionalLogic;
|
|
|
|
@SerializedName("gppa-values-templates")
|
|
public java.util.Map<String, String> gppaTemplates;
|
|
|
|
@SerializedName("gpnfForm")
|
|
public String gpnfForm;
|
|
|
|
public static class Choice {
|
|
@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;
|
|
}
|
|
|
|
public static class Rule {
|
|
@SerializedName("fieldId") public String fieldId;
|
|
@SerializedName("operator") public String operator;
|
|
@SerializedName("value") public String value;
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\GravityForm.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import com.google.gson.annotations.SerializedName;
|
|
import java.util.List;
|
|
|
|
public class GravityForm {
|
|
@SerializedName("id")
|
|
public int id;
|
|
|
|
@SerializedName("title")
|
|
public String title;
|
|
|
|
@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 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);
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookAdapter.java
|
|
============================================================
|
|
// FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookAdapter.java
|
|
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 java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
public class HandbookAdapter extends RecyclerView.Adapter<HandbookAdapter.ViewHolder> {
|
|
|
|
private List<HandbookItem> fullList;
|
|
private List<HandbookItem> filteredList;
|
|
private OnItemClickListener listener;
|
|
|
|
public interface OnItemClickListener {
|
|
void onItemClick(HandbookItem item);
|
|
}
|
|
|
|
public HandbookAdapter(List<HandbookItem> items, OnItemClickListener listener) {
|
|
this.fullList = items;
|
|
this.filteredList = new ArrayList<>(items);
|
|
this.listener = listener;
|
|
}
|
|
|
|
@NonNull
|
|
@Override
|
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_handbook, parent, false);
|
|
return new ViewHolder(view);
|
|
}
|
|
|
|
@Override
|
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
|
HandbookItem item = filteredList.get(position);
|
|
holder.title.setText(item.getTitle());
|
|
|
|
// Håndter ingress: Kutt etter 80 tegn hvis den er veldig lang
|
|
String desc = item.getDescription();
|
|
if (desc == null) desc = "";
|
|
if (desc.length() > 100) {
|
|
desc = desc.substring(0, 100) + "...";
|
|
}
|
|
holder.desc.setText(desc);
|
|
|
|
// Mapp ikon-type til ressurs (Utvidet liste fra PHP v3.0)
|
|
int iconRes = R.drawable.ic_handbook_general; // Fallback
|
|
String type = item.getIconType() != null ? item.getIconType() : "";
|
|
|
|
switch (type) {
|
|
case "car": iconRes = R.drawable.ic_handbook_car; break;
|
|
case "health": iconRes = R.drawable.ic_handbook_health; break;
|
|
case "people": iconRes = R.drawable.ic_handbook_people; break;
|
|
case "warning": iconRes = R.drawable.ic_handbook_warning; break;
|
|
case "doc": iconRes = R.drawable.ic_handbook_doc; break;
|
|
case "card": iconRes = R.drawable.ic_handbook_doc; break; // Bruker doc inntil videre
|
|
case "computer": iconRes = R.drawable.ic_handbook_general; break;
|
|
case "calendar": iconRes = R.drawable.ic_handbook_general; break;
|
|
case "money": iconRes = R.drawable.ic_handbook_doc; break;
|
|
case "helmet": iconRes = R.drawable.ic_handbook_warning; break;
|
|
case "trash": iconRes = R.drawable.ic_handbook_general; break;
|
|
case "book": iconRes = R.drawable.ic_book; break; // Gjenbruk eksisterende ic_book
|
|
case "chat": iconRes = R.drawable.ic_handbook_people; break;
|
|
default: iconRes = R.drawable.ic_handbook_general; break;
|
|
}
|
|
holder.icon.setImageResource(iconRes);
|
|
|
|
holder.itemView.setOnClickListener(v -> listener.onItemClick(item));
|
|
}
|
|
|
|
@Override
|
|
public int getItemCount() {
|
|
return filteredList.size();
|
|
}
|
|
|
|
public void filter(String query) {
|
|
filteredList.clear();
|
|
if (query.isEmpty()) {
|
|
filteredList.addAll(fullList);
|
|
} else {
|
|
String q = query.toLowerCase();
|
|
for (HandbookItem item : fullList) {
|
|
if (item.getTitle().toLowerCase().contains(q) ||
|
|
(item.getDescription() != null && item.getDescription().toLowerCase().contains(q))) {
|
|
filteredList.add(item);
|
|
}
|
|
}
|
|
}
|
|
notifyDataSetChanged();
|
|
}
|
|
|
|
public static class ViewHolder extends RecyclerView.ViewHolder {
|
|
TextView title, desc;
|
|
ImageView icon;
|
|
|
|
public ViewHolder(View view) {
|
|
super(view);
|
|
title = view.findViewById(R.id.title);
|
|
desc = view.findViewById(R.id.desc);
|
|
icon = view.findViewById(R.id.icon);
|
|
}
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookDetailFragment.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import android.content.Intent;
|
|
import android.net.Uri;
|
|
import android.os.Bundle;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.webkit.WebResourceRequest;
|
|
import android.webkit.WebSettings;
|
|
import android.webkit.WebView;
|
|
import android.webkit.WebViewClient;
|
|
import android.widget.ProgressBar;
|
|
import android.widget.Toast;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.appcompat.widget.Toolbar;
|
|
import androidx.fragment.app.Fragment;
|
|
import androidx.navigation.Navigation;
|
|
|
|
import com.google.gson.JsonObject;
|
|
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
import retrofit2.Call;
|
|
import retrofit2.Callback;
|
|
import retrofit2.Response;
|
|
|
|
public class HandbookDetailFragment extends Fragment {
|
|
|
|
private int pageId;
|
|
private String pageTitle;
|
|
private WebView webView;
|
|
private ProgressBar progressBar;
|
|
|
|
// --- CSS: DESIGN MED MER LUFT ---
|
|
private static final String CSS_STYLE =
|
|
"<style>" +
|
|
"body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #333333; line-height: 1.6; padding: 16px; margin: 0; }" +
|
|
|
|
"h1 { color: #0069B3; font-size: 24px; border-bottom: 2px solid #0069B3; padding-bottom: 10px; margin-top: 0; }" +
|
|
"p, ul, li { margin-bottom: 12px; }" +
|
|
"a { color: #0069B3; font-weight: bold; text-decoration: none; }" +
|
|
|
|
// --- BOKSEN ---
|
|
".trekkspill { " +
|
|
" background-color: #fff; " +
|
|
" border: 1px solid #ddd; " +
|
|
" border-radius: 4px; " +
|
|
" max-height: 58px; " + // LUKKET
|
|
" overflow: hidden; " +
|
|
" transition: max-height 0.4s ease; " +
|
|
|
|
// LUFT OG AVSTAND
|
|
" margin-top: 32px; " +
|
|
" margin-bottom: 16px; " +
|
|
"}" +
|
|
|
|
// --- KNAPPEN ---
|
|
".trekkspill > a[id^='fl-accordion--label-'] { " +
|
|
" display: flex; " +
|
|
" justify-content: space-between; " +
|
|
" align-items: center; " +
|
|
" background-color: #f2f2f2; " +
|
|
" color: #0069B3; " +
|
|
" padding: 15px; " +
|
|
" font-weight: bold; " +
|
|
" cursor: pointer; " +
|
|
" width: 100%; " +
|
|
" height: 58px; " +
|
|
" box-sizing: border-box; " +
|
|
" pointer-events: auto; " +
|
|
"}" +
|
|
|
|
// --- PLUSS-TEGN ---
|
|
".trekkspill > a[id^='fl-accordion--label-']::after { " +
|
|
" content: '+'; " +
|
|
" font-size: 24px; " +
|
|
" font-weight: bold; " +
|
|
" color: #0069B3; " +
|
|
"}" +
|
|
|
|
// --- ÅPEN TILSTAND ---
|
|
".trekkspill.open { " +
|
|
" max-height: 4000px; " + // ÅPEN
|
|
" overflow: visible; " +
|
|
" border-color: #0069B3; " +
|
|
"}" +
|
|
|
|
".trekkspill.open > a[id^='fl-accordion--label-'] { " +
|
|
" background-color: #0069B3; " +
|
|
" color: #ffffff; " +
|
|
"}" +
|
|
|
|
".trekkspill.open > a[id^='fl-accordion--label-']::after { " +
|
|
" content: '-'; " +
|
|
" color: #ffffff; " +
|
|
"}" +
|
|
|
|
// --- RYDDEJOBB ---
|
|
".trekkspill > a[id^='fl-accordion--icon-'] { display: none !important; }" +
|
|
".trekkspill > h2 { display: none; }" +
|
|
".trekkspill > p, .trekkspill > ul, .trekkspill > div { padding: 10px 15px; }" +
|
|
|
|
"</style>";
|
|
|
|
// --- JAVASCRIPT: AUTOSCROLL ---
|
|
private static final String JS_SCRIPT =
|
|
"<script>" +
|
|
"document.onclick = function(e) {\n" +
|
|
" var target = e.target.closest('a[id^=\"fl-accordion--label-\"]');\n" +
|
|
" \n" +
|
|
" if (target) {\n" +
|
|
" e.preventDefault();\n" +
|
|
" \n" +
|
|
" var currentBox = target.closest('.trekkspill');\n" +
|
|
" \n" +
|
|
" if (currentBox) {\n" +
|
|
" var wasOpen = currentBox.classList.contains('open');\n" +
|
|
" \n" +
|
|
" // LUKK ALLE ANDRE\n" +
|
|
" var allOpenBoxes = document.querySelectorAll('.trekkspill.open');\n" +
|
|
" allOpenBoxes.forEach(function(box) {\n" +
|
|
" box.classList.remove('open');\n" +
|
|
" });\n" +
|
|
" \n" +
|
|
" // ÅPNE DEN VALGTE\n" +
|
|
" if (!wasOpen) {\n" +
|
|
" currentBox.classList.add('open');\n" +
|
|
" \n" +
|
|
" // AUTOSCROLL: Økt til 300ms for å vente på CSS-animasjonen\n" +
|
|
" setTimeout(function() {\n" +
|
|
" currentBox.scrollIntoView({behavior: 'smooth', block: 'start'});\n" +
|
|
" }, 300);\n" +
|
|
" }\n" +
|
|
" }\n" +
|
|
" return false;\n" +
|
|
" }\n" +
|
|
"};" +
|
|
"</script>";
|
|
|
|
@Nullable
|
|
@Override
|
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
return inflater.inflate(R.layout.fragment_handbook_detail, container, false);
|
|
}
|
|
|
|
@Override
|
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
super.onViewCreated(view, savedInstanceState);
|
|
|
|
if (getArguments() != null) {
|
|
pageId = getArguments().getInt("page_id");
|
|
pageTitle = getArguments().getString("page_title");
|
|
}
|
|
|
|
Toolbar toolbar = view.findViewById(R.id.detail_toolbar);
|
|
toolbar.setTitle(pageTitle != null ? pageTitle : "Håndbok");
|
|
toolbar.setNavigationOnClickListener(v -> Navigation.findNavController(view).navigateUp());
|
|
|
|
webView = view.findViewById(R.id.detail_webview);
|
|
progressBar = view.findViewById(R.id.detail_loading);
|
|
|
|
setupWebView();
|
|
fetchContent();
|
|
}
|
|
|
|
private void setupWebView() {
|
|
WebSettings settings = webView.getSettings();
|
|
settings.setJavaScriptEnabled(true);
|
|
settings.setDomStorageEnabled(true);
|
|
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
|
|
|
|
webView.setWebViewClient(new WebViewClient() {
|
|
@Override
|
|
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
|
return handleLinkClick(request.getUrl().toString());
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
|
return handleLinkClick(url);
|
|
}
|
|
});
|
|
}
|
|
|
|
private boolean handleLinkClick(String url) {
|
|
// Ignorer klikk på accordion-lenker
|
|
if (url.endsWith("#")) return true;
|
|
|
|
String lowerUrl = url.toLowerCase();
|
|
int formIdToOpen = 0;
|
|
|
|
// --- SPESIALHÅNDTERING: Link til Skjemaer basert på URL-nøkkelord ---
|
|
|
|
// ID 1: Ansatteopplysninger
|
|
if (lowerUrl.contains("ansatteopplysninger")) {
|
|
formIdToOpen = 1;
|
|
}
|
|
// ID 2: Vernerunde
|
|
else if (lowerUrl.contains("vernerunde")) {
|
|
formIdToOpen = 2;
|
|
}
|
|
// ID 4: RUH (Rapport om uønsket hendelse)
|
|
else if (lowerUrl.contains("uonsket-hendelse") || lowerUrl.contains("/ruh")) {
|
|
formIdToOpen = 4;
|
|
}
|
|
// ID 5: Lån av verktøy/henger
|
|
else if (lowerUrl.contains("lan-av") || lowerUrl.contains("verktoy")) {
|
|
formIdToOpen = 5;
|
|
}
|
|
// ID 6: Avviksmelding
|
|
else if (lowerUrl.contains("avviksmelding") || lowerUrl.contains("/avvik")) {
|
|
formIdToOpen = 6;
|
|
}
|
|
// ID 9: Sikkerhetskurs / Kompetansebevis
|
|
else if (lowerUrl.contains("sikkerhetskurs") || lowerUrl.contains("kompetansebevis")) {
|
|
formIdToOpen = 9;
|
|
}
|
|
// ID 10: HMS Bekreftelse
|
|
else if (lowerUrl.contains("hms-bekreftelse") || lowerUrl.contains("hms-policy")) {
|
|
formIdToOpen = 10;
|
|
}
|
|
// ID 11: Egenmelding
|
|
else if (lowerUrl.contains("egenmelding")) {
|
|
formIdToOpen = 11;
|
|
}
|
|
// ID 12: Sjekkliste firmabil
|
|
else if (lowerUrl.contains("sjekkliste") && (lowerUrl.contains("bil") || lowerUrl.contains("kjoretoy"))) {
|
|
formIdToOpen = 12;
|
|
}
|
|
// ID 14: SJA (Sikker Jobbanalyse)
|
|
else if (lowerUrl.contains("sja") || lowerUrl.contains("jobbanalyse")) {
|
|
formIdToOpen = 14;
|
|
}
|
|
// ID 15: Fraværsvarsel
|
|
else if (lowerUrl.contains("fravaersvarsel") || lowerUrl.contains("fravarsvarsel")) {
|
|
formIdToOpen = 15;
|
|
}
|
|
// ID 16: Refusjon utlegg
|
|
else if (lowerUrl.contains("refusjon") || lowerUrl.contains("utlegg")) {
|
|
formIdToOpen = 16;
|
|
}
|
|
// ID 21: Medarbeidersamtale
|
|
else if (lowerUrl.contains("medarbeidersamtale")) {
|
|
formIdToOpen = 21;
|
|
}
|
|
// ID 22: Medarbeiderundersøkelse
|
|
else if (lowerUrl.contains("medarbeiderundersokelse")) {
|
|
formIdToOpen = 22;
|
|
}
|
|
|
|
// Hvis vi fant et skjema, naviger dit internt
|
|
if (formIdToOpen > 0) {
|
|
Bundle bundle = new Bundle();
|
|
bundle.putInt("formId", formIdToOpen);
|
|
Navigation.findNavController(getView()).navigate(R.id.action_handbook_to_form, bundle);
|
|
return true;
|
|
}
|
|
|
|
// --- STANDARD INTERN NAVIGASJON ---
|
|
if (url.contains("intranet.kbs.no") || url.startsWith("/")) {
|
|
int targetId = extractIdFromUrl(url);
|
|
if (targetId > 0) {
|
|
navigateToPage(targetId, "Laster...");
|
|
return true;
|
|
}
|
|
|
|
progressBar.setVisibility(View.VISIBLE);
|
|
RetrofitClient.getApiService().lookupPageId(url).enqueue(new Callback<JsonObject>() {
|
|
@Override
|
|
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
|
|
if (!isAdded()) return;
|
|
progressBar.setVisibility(View.GONE);
|
|
|
|
if (response.isSuccessful() && response.body() != null) {
|
|
int id = response.body().get("id").getAsInt();
|
|
if (id > 0) {
|
|
navigateToPage(id, "Laster...");
|
|
} else {
|
|
openExternal(url);
|
|
}
|
|
} else {
|
|
openExternal(url);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(Call<JsonObject> call, Throwable t) {
|
|
if (!isAdded()) return;
|
|
progressBar.setVisibility(View.GONE);
|
|
openExternal(url);
|
|
}
|
|
});
|
|
return true;
|
|
} else {
|
|
// Ekstern lenke
|
|
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
|
startActivity(browserIntent);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private void navigateToPage(int id, String title) {
|
|
Bundle bundle = new Bundle();
|
|
bundle.putInt("page_id", id);
|
|
bundle.putString("page_title", title);
|
|
Navigation.findNavController(getView()).navigate(R.id.action_handbook_to_detail, bundle);
|
|
}
|
|
|
|
private void openExternal(String url) {
|
|
Intent intent = new Intent(getContext(), WebViewActivity.class);
|
|
intent.putExtra(WebViewActivity.EXTRA_URL, url);
|
|
intent.putExtra(WebViewActivity.EXTRA_TITLE, "KBS Intranett");
|
|
startActivity(intent);
|
|
}
|
|
|
|
private int extractIdFromUrl(String url) {
|
|
Pattern p = Pattern.compile("[?&](p|page_id|post)=([0-9]+)");
|
|
Matcher m = p.matcher(url);
|
|
if (m.find()) {
|
|
try {
|
|
return Integer.parseInt(m.group(2));
|
|
} catch (NumberFormatException e) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private void fetchContent() {
|
|
progressBar.setVisibility(View.VISIBLE);
|
|
RetrofitClient.getApiService().getHandbookPage(pageId).enqueue(new Callback<HandbookPage>() {
|
|
@Override
|
|
public void onResponse(Call<HandbookPage> call, Response<HandbookPage> response) {
|
|
if (!isAdded()) return;
|
|
progressBar.setVisibility(View.GONE);
|
|
|
|
if (response.isSuccessful() && response.body() != null) {
|
|
HandbookPage page = response.body();
|
|
|
|
if (getView() != null) {
|
|
Toolbar toolbar = getView().findViewById(R.id.detail_toolbar);
|
|
if (toolbar != null) toolbar.setTitle(page.title);
|
|
}
|
|
|
|
String htmlContent = "<!DOCTYPE html><html><head>" +
|
|
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">" +
|
|
CSS_STYLE +
|
|
JS_SCRIPT +
|
|
"</head><body>";
|
|
|
|
htmlContent += "<h1>" + page.title + "</h1>";
|
|
|
|
if (page.content != null) {
|
|
htmlContent += page.content;
|
|
} else {
|
|
htmlContent += "<p>Ingen innhold funnet.</p>";
|
|
}
|
|
htmlContent += "</body></html>";
|
|
|
|
webView.loadDataWithBaseURL("https://intranet.kbs.no", htmlContent, "text/html", "UTF-8", null);
|
|
|
|
} else {
|
|
Toast.makeText(getContext(), "Kunne ikke laste innhold.", Toast.LENGTH_SHORT).show();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(Call<HandbookPage> call, Throwable t) {
|
|
if (!isAdded()) return;
|
|
progressBar.setVisibility(View.GONE);
|
|
Toast.makeText(getContext(), "Nettverksfeil", Toast.LENGTH_SHORT).show();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookFragment.java
|
|
============================================================
|
|
// FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookFragment.java
|
|
package com.kbs.kbsintranett;
|
|
|
|
import android.os.Bundle;
|
|
import android.text.Editable;
|
|
import android.text.TextWatcher;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.widget.EditText;
|
|
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; // Viktig
|
|
import androidx.recyclerview.widget.GridLayoutManager;
|
|
import androidx.recyclerview.widget.RecyclerView;
|
|
import java.util.List;
|
|
import retrofit2.Call;
|
|
import retrofit2.Callback;
|
|
import retrofit2.Response;
|
|
|
|
public class HandbookFragment extends Fragment {
|
|
|
|
private RecyclerView recyclerView;
|
|
private ProgressBar progressBar;
|
|
private EditText searchField;
|
|
private HandbookAdapter adapter;
|
|
|
|
@Nullable
|
|
@Override
|
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
return inflater.inflate(R.layout.fragment_handbook, container, false);
|
|
}
|
|
|
|
@Override
|
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
super.onViewCreated(view, savedInstanceState);
|
|
|
|
recyclerView = view.findViewById(R.id.recycler_handbook);
|
|
progressBar = view.findViewById(R.id.progressBar);
|
|
searchField = view.findViewById(R.id.search_field);
|
|
|
|
recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
|
|
|
searchField.addTextChangedListener(new TextWatcher() {
|
|
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
|
@Override public void afterTextChanged(Editable s) {
|
|
if (adapter != null) adapter.filter(s.toString());
|
|
}
|
|
});
|
|
|
|
fetchHandbook();
|
|
}
|
|
|
|
private void fetchHandbook() {
|
|
RetrofitClient.getApiService().getHandbookItems().enqueue(new Callback<List<HandbookItem>>() {
|
|
@Override
|
|
public void onResponse(Call<List<HandbookItem>> call, Response<List<HandbookItem>> response) {
|
|
if (!isAdded()) return;
|
|
progressBar.setVisibility(View.GONE);
|
|
|
|
if (response.isSuccessful() && response.body() != null) {
|
|
adapter = new HandbookAdapter(response.body(), item -> {
|
|
// NYTT: Naviger til Native Detail Fragment
|
|
Bundle bundle = new Bundle();
|
|
bundle.putInt("page_id", item.getId());
|
|
bundle.putString("page_title", item.getTitle());
|
|
|
|
Navigation.findNavController(getView())
|
|
.navigate(R.id.action_handbook_to_detail, bundle);
|
|
});
|
|
recyclerView.setAdapter(adapter);
|
|
} else {
|
|
String msg = "Kunne ikke laste håndboken. Kode: " + response.code();
|
|
if (response.code() == 404) msg += "\n(Fant ikke foreldresiden 'interninstruks-hms')";
|
|
Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(Call<List<HandbookItem>> call, Throwable t) {
|
|
if (!isAdded()) return;
|
|
progressBar.setVisibility(View.GONE);
|
|
Toast.makeText(getContext(), "Nettverksfeil", Toast.LENGTH_SHORT).show();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookItem.java
|
|
============================================================
|
|
// FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookItem.java
|
|
package com.kbs.kbsintranett;
|
|
|
|
import com.google.gson.annotations.SerializedName;
|
|
import java.io.Serializable;
|
|
|
|
public class HandbookItem implements Serializable {
|
|
@SerializedName("id")
|
|
private int id; // NYTT
|
|
|
|
@SerializedName("title")
|
|
private String title;
|
|
|
|
@SerializedName("desc")
|
|
private String description;
|
|
|
|
@SerializedName("icon_type")
|
|
private String iconType;
|
|
|
|
@SerializedName("url")
|
|
private String url;
|
|
|
|
public int getId() { return id; }
|
|
public String getTitle() { return title; }
|
|
public String getDescription() { return description; }
|
|
public String getIconType() { return iconType; }
|
|
public String getUrl() { return url; }
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookPage.java
|
|
============================================================
|
|
// FILSTI: app\src\main\java\com\kbs\kbsintranett\HandbookPage.java
|
|
package com.kbs.kbsintranett;
|
|
|
|
import com.google.gson.annotations.SerializedName;
|
|
|
|
public class HandbookPage {
|
|
@SerializedName("id")
|
|
public int id;
|
|
|
|
@SerializedName("title")
|
|
public String title;
|
|
|
|
@SerializedName("content")
|
|
public String content; // Dette er HTML-strengen
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\HomeFragment.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import android.Manifest;
|
|
import android.content.pm.PackageManager;
|
|
import android.os.Bundle;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
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.Nullable;
|
|
import androidx.core.content.ContextCompat;
|
|
import androidx.fragment.app.Fragment;
|
|
import androidx.navigation.Navigation;
|
|
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;
|
|
import retrofit2.Response;
|
|
|
|
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 på kalendertillatelse
|
|
requestPermissionLauncher = registerForActivityResult(
|
|
new ActivityResultContracts.RequestPermission(),
|
|
isGranted -> {
|
|
// Prøv å laste kalender på nytt (nå potensielt med personlig kalender)
|
|
if (calendarRecycler != null) {
|
|
fetchCalendarEvents(calendarRecycler);
|
|
}
|
|
}
|
|
);
|
|
|
|
// Start bakgrunnsjobb for varsling (kjører hver 15. minutt)
|
|
startNotificationWorker();
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
return inflater.inflate(R.layout.fragment_home, container, false);
|
|
}
|
|
|
|
@Override
|
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
super.onViewCreated(view, savedInstanceState);
|
|
|
|
// 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));
|
|
}
|
|
|
|
// 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 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));
|
|
}
|
|
|
|
// Sjekk tillatelse for personlig kalender
|
|
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);
|
|
}
|
|
}
|
|
|
|
// 2. Nyheter oppsett
|
|
RecyclerView newsRecycler = view.findViewById(R.id.recycler_news);
|
|
newsRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
|
|
newsRecycler.setNestedScrollingEnabled(false);
|
|
// 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 (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
|
|
public void onResponse(Call<List<CalendarEvent>> call, Response<List<CalendarEvent>> response) {
|
|
if (!isAdded()) return;
|
|
|
|
List<CalendarEvent> apiEvents = new ArrayList<>();
|
|
if (response.isSuccessful() && response.body() != null) {
|
|
for (CalendarEvent e : response.body()) {
|
|
CalendarManager.formatEventForUI(e); // Formatér datoer
|
|
apiEvents.add(e);
|
|
}
|
|
}
|
|
|
|
// 3. Flett listene (API + Personlig) og sorter
|
|
List<CalendarEvent> merged = CalendarManager.mergeAndSort(apiEvents, deviceEvents);
|
|
|
|
// 4. Filtrer ut hendelser som har passert (vis kun fremtidige + i dag)
|
|
// (CalendarManager henter 1 år bakover, så vi må 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(upcomingEvents.size(), 5); i++) {
|
|
top5.add(upcomingEvents.get(i));
|
|
}
|
|
|
|
recyclerView.setAdapter(new CalendarAdapter(top5, event -> {
|
|
CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
|
|
sheet.show(getParentFragmentManager(), "CalendarDetails");
|
|
}));
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(Call<List<CalendarEvent>> call, Throwable t) {
|
|
if (!isAdded()) return;
|
|
// 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 -> {
|
|
CalendarDetailsBottomSheet sheet = new CalendarDetailsBottomSheet(event);
|
|
sheet.show(getParentFragmentManager(), "CalendarDetails");
|
|
}));
|
|
} else {
|
|
List<CalendarEvent> errorList = new ArrayList<>();
|
|
errorList.add(new CalendarEvent("Kunne ikke laste kalender", "Sjekk nettverk", "!", "OBS"));
|
|
recyclerView.setAdapter(new CalendarAdapter(errorList, null));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
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 nå 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 i HMS-kalenderen
|
|
PeriodicWorkRequest notifRequest =
|
|
new PeriodicWorkRequest.Builder(NotificationWorker.class, 15, TimeUnit.MINUTES)
|
|
.build();
|
|
WorkManager.getInstance(requireContext()).enqueue(notifRequest);
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\InputsAdapter.java
|
|
============================================================
|
|
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<>();
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\InternalLinkMovementMethod.java
|
|
============================================================
|
|
// FILSTI: app\src\main\java\com\kbs\kbsintranett\InternalLinkMovementMethod.java
|
|
package com.kbs.kbsintranett;
|
|
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.net.Uri;
|
|
import android.os.Bundle; // <-- Sjekk at denne er med!
|
|
import android.text.Spannable;
|
|
import android.text.method.LinkMovementMethod;
|
|
import android.text.style.URLSpan;
|
|
import android.util.Log;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
import androidx.navigation.Navigation;
|
|
|
|
import com.google.gson.JsonObject;
|
|
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
import retrofit2.Call;
|
|
import retrofit2.Callback;
|
|
import retrofit2.Response;
|
|
|
|
public class InternalLinkMovementMethod extends LinkMovementMethod {
|
|
private static InternalLinkMovementMethod instance;
|
|
private static final String TAG = "InternalLinkMethod";
|
|
|
|
public static InternalLinkMovementMethod getInstance() {
|
|
if (instance == null) instance = new InternalLinkMovementMethod();
|
|
return instance;
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
|
|
int action = event.getAction();
|
|
|
|
if (action == MotionEvent.ACTION_UP) {
|
|
int x = (int) event.getX();
|
|
int y = (int) event.getY();
|
|
|
|
x -= widget.getTotalPaddingLeft();
|
|
y -= widget.getTotalPaddingTop();
|
|
|
|
x += widget.getScrollX();
|
|
y += widget.getScrollY();
|
|
|
|
int line = widget.getLayout().getLineForVertical(y);
|
|
int off = widget.getLayout().getOffsetForHorizontal(line, x);
|
|
|
|
URLSpan[] link = buffer.getSpans(off, off, URLSpan.class);
|
|
if (link.length != 0) {
|
|
String url = link[0].getURL();
|
|
handleLink(widget.getContext(), url, widget);
|
|
return true;
|
|
}
|
|
}
|
|
return super.onTouchEvent(widget, buffer, event);
|
|
}
|
|
|
|
private void handleLink(Context context, String url, View view) {
|
|
Log.d(TAG, "Link clicked: " + url);
|
|
|
|
// 1. Sjekk om det er en intern lenke
|
|
if (url.contains("intranet.kbs.no") || url.startsWith("/")) {
|
|
|
|
// a) Prøv å finne ID hvis den finnes i URLen (?p=123)
|
|
int pageId = extractIdFromUrl(url);
|
|
|
|
if (pageId > 0) {
|
|
navigateToInternalPage(view, pageId, "Laster...");
|
|
} else {
|
|
// b) Det er en "pen" URL. Vi må spørre APIet hva IDen er.
|
|
// Vi bruker Toast for å gi feedback om at noe skjer
|
|
Toast.makeText(context, "Åpner side...", Toast.LENGTH_SHORT).show();
|
|
|
|
RetrofitClient.getApiService().lookupPageId(url).enqueue(new Callback<JsonObject>() {
|
|
@Override
|
|
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
|
|
if (response.isSuccessful() && response.body() != null) {
|
|
int id = response.body().get("id").getAsInt();
|
|
if (id > 0) {
|
|
// Suksess! Naviger internt
|
|
navigateToInternalPage(view, id, "Laster...");
|
|
} else {
|
|
// Fant ikke ID, åpne i WebView som fallback
|
|
openInWebView(context, url);
|
|
}
|
|
} else {
|
|
openInWebView(context, url);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(Call<JsonObject> call, Throwable t) {
|
|
openInWebView(context, url);
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
// Ekstern lenke - åpne i nettleser
|
|
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
|
context.startActivity(browserIntent);
|
|
}
|
|
}
|
|
|
|
private int extractIdFromUrl(String url) {
|
|
Pattern p = Pattern.compile("[?&](p|page_id|post)=([0-9]+)");
|
|
Matcher m = p.matcher(url);
|
|
if (m.find()) {
|
|
try {
|
|
return Integer.parseInt(m.group(2));
|
|
} catch (NumberFormatException e) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private void navigateToInternalPage(View view, int pageId, String title) {
|
|
try {
|
|
Bundle bundle = new Bundle();
|
|
bundle.putInt("page_id", pageId);
|
|
bundle.putString("page_title", title);
|
|
Navigation.findNavController(view).navigate(R.id.action_handbook_to_detail, bundle);
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Kunne ikke navigere", e);
|
|
}
|
|
}
|
|
|
|
private void openInWebView(Context context, String url) {
|
|
Intent intent = new Intent(context, WebViewActivity.class);
|
|
intent.putExtra(WebViewActivity.EXTRA_URL, url);
|
|
intent.putExtra(WebViewActivity.EXTRA_TITLE, "KBS Intranett");
|
|
context.startActivity(intent);
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\LoginFragment.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import android.content.Intent;
|
|
import android.os.Bundle;
|
|
import android.util.Log;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
import androidx.activity.result.ActivityResultLauncher;
|
|
import androidx.activity.result.contract.ActivityResultContracts;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.fragment.app.Fragment;
|
|
import androidx.navigation.NavController;
|
|
import androidx.navigation.Navigation;
|
|
|
|
import com.google.android.gms.auth.api.signin.GoogleSignIn;
|
|
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
|
|
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
|
|
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
|
|
import com.google.android.gms.common.SignInButton;
|
|
import com.google.android.gms.common.api.ApiException;
|
|
import com.google.android.gms.tasks.Task;
|
|
|
|
public class LoginFragment extends Fragment {
|
|
|
|
private static final String TAG = "LoginFragment";
|
|
private GoogleSignInClient mGoogleSignInClient;
|
|
private TextView statusText;
|
|
private SignInButton signInButton;
|
|
|
|
// Håndterer resultatet fra Google-vinduet
|
|
private final ActivityResultLauncher<Intent> signInLauncher = registerForActivityResult(
|
|
new ActivityResultContracts.StartActivityForResult(),
|
|
result -> {
|
|
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(result.getData());
|
|
handleGoogleResult(task);
|
|
}
|
|
);
|
|
|
|
@Nullable
|
|
@Override
|
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
View view = inflater.inflate(R.layout.fragment_login, container, false);
|
|
statusText = view.findViewById(R.id.status_text);
|
|
signInButton = view.findViewById(R.id.sign_in_button);
|
|
signInButton.setSize(SignInButton.SIZE_WIDE);
|
|
|
|
// Hent ID fra MainActivity
|
|
String clientId = MainActivity.GOOGLE_WEB_CLIENT_ID;
|
|
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
|
.requestIdToken(clientId)
|
|
.requestEmail()
|
|
.build();
|
|
mGoogleSignInClient = GoogleSignIn.getClient(requireActivity(), gso);
|
|
|
|
signInButton.setOnClickListener(v -> {
|
|
statusText.setText("Starter Google innlogging...");
|
|
Intent signInIntent = mGoogleSignInClient.getSignInIntent();
|
|
signInLauncher.launch(signInIntent);
|
|
});
|
|
return view;
|
|
}
|
|
|
|
private void handleGoogleResult(Task<GoogleSignInAccount> completedTask) {
|
|
try {
|
|
GoogleSignInAccount account = completedTask.getResult(ApiException.class);
|
|
// 1. Google er OK. Nå logger vi inn på WordPress.
|
|
statusText.setText("Google OK. Kobler til KBS Intranett...");
|
|
signInButton.setEnabled(false); // Hindre dobbeltklikk
|
|
|
|
String photoUrl = (account.getPhotoUrl() != null) ? account.getPhotoUrl().toString() : null;
|
|
|
|
AuthRepository.loginToWordPress(
|
|
account.getIdToken(),
|
|
account.getDisplayName(),
|
|
account.getEmail(),
|
|
photoUrl,
|
|
new AuthRepository.AuthCallback() {
|
|
@Override
|
|
public void onSuccess(String role) {
|
|
// 2. Alt er OK! Naviger til Hjem.
|
|
if (isAdded()) {
|
|
statusText.setText("Innlogging OK!");
|
|
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
|
navController.navigate(R.id.action_login_to_home);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onError(String message) {
|
|
if (isAdded()) {
|
|
statusText.setText(message);
|
|
signInButton.setEnabled(true);
|
|
}
|
|
}
|
|
}
|
|
);
|
|
} catch (ApiException e) {
|
|
// --- KORRIGERT FEILMELDING ---
|
|
Log.w(TAG, "signInResult:failed code=" + e.getStatusCode());
|
|
String message;
|
|
if (e.getStatusCode() == 12500) {
|
|
message = "Konto ikke funnet, eller konto uten rettigheter.";
|
|
} else {
|
|
message = "Google-feil: " + e.getStatusCode();
|
|
}
|
|
statusText.setText(message);
|
|
// --- SLUTT PÅ KORRIGERING ---
|
|
}
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\LoginRequest.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
public class LoginRequest {
|
|
public String token;
|
|
|
|
public LoginRequest(String token) {
|
|
this.token = token;
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\LoginResponse.java
|
|
============================================================
|
|
// FILSTI: app\src\main\java\com\kbs\kbsintranett\LoginResponse.java
|
|
package com.kbs.kbsintranett;
|
|
|
|
import com.google.gson.annotations.SerializedName;
|
|
|
|
public class LoginResponse {
|
|
public boolean success;
|
|
|
|
@SerializedName("full_cookie")
|
|
public String fullCookie;
|
|
|
|
public String role;
|
|
|
|
@SerializedName("user_id")
|
|
public int userId;
|
|
|
|
// --- NYE FELTER ---
|
|
@SerializedName("first_name")
|
|
public String firstName;
|
|
|
|
@SerializedName("last_name")
|
|
public String lastName;
|
|
|
|
@SerializedName("stilling") // Sjekk at JSON-nøkkelen fra WP matcher dette
|
|
public String stilling;
|
|
|
|
@SerializedName("mobiltelefon") // Sjekk at JSON-nøkkelen fra WP matcher dette
|
|
public String mobiltelefon;
|
|
// ------------------
|
|
|
|
public String message;
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\MainActivity.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import android.os.Bundle;
|
|
import android.util.Log;
|
|
import android.view.View;
|
|
|
|
import androidx.appcompat.app.AppCompatActivity;
|
|
import androidx.navigation.NavController;
|
|
import androidx.navigation.fragment.NavHostFragment;
|
|
import androidx.navigation.ui.NavigationUI;
|
|
|
|
import com.google.android.gms.auth.api.signin.GoogleSignIn;
|
|
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
|
|
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
|
|
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
|
|
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
|
|
|
public class MainActivity extends AppCompatActivity {
|
|
|
|
// VIKTIG: Erstatt denne med din Web Client ID
|
|
public static final String GOOGLE_WEB_CLIENT_ID = "SECRET.apps.googleusercontent.com";
|
|
private static final String TAG = "MainActivity";
|
|
private NavController navController;
|
|
private BottomNavigationView bottomNav;
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
setContentView(R.layout.activity_main);
|
|
|
|
// 1. Setup UI
|
|
bottomNav = findViewById(R.id.bottom_nav_view);
|
|
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
|
|
.findFragmentById(R.id.nav_host_fragment);
|
|
if (navHostFragment != null) {
|
|
navController = navHostFragment.getNavController();
|
|
NavigationUI.setupWithNavController(bottomNav, navController);
|
|
|
|
// Skjul meny på login-skjerm
|
|
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
|
|
// Sjekker mot R.id.navigation_login som er ID'en til fragmentet
|
|
if (destination.getId() == R.id.navigation_login) {
|
|
bottomNav.setVisibility(View.GONE);
|
|
} else {
|
|
bottomNav.setVisibility(View.VISIBLE);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 2. Start Silent Sign-In ved oppstart
|
|
checkLoginState();
|
|
}
|
|
|
|
private void checkLoginState() {
|
|
GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
|
|
if (account == null) {
|
|
navigateToLogin();
|
|
} else {
|
|
refreshGoogleToken();
|
|
}
|
|
}
|
|
|
|
private void refreshGoogleToken() {
|
|
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
|
.requestIdToken(GOOGLE_WEB_CLIENT_ID)
|
|
.requestEmail()
|
|
.build();
|
|
GoogleSignInClient client = GoogleSignIn.getClient(this, gso);
|
|
|
|
client.silentSignIn()
|
|
.addOnSuccessListener(account -> {
|
|
String photoUrl = (account.getPhotoUrl() != null) ? account.getPhotoUrl().toString() : null;
|
|
|
|
AuthRepository.loginToWordPress(
|
|
account.getIdToken(),
|
|
account.getDisplayName(),
|
|
account.getEmail(),
|
|
photoUrl,
|
|
new AuthRepository.AuthCallback() {
|
|
@Override
|
|
public void onSuccess(String role) {
|
|
Log.d(TAG, "Silent login fullført. Rolle: " + role);
|
|
// Gå videre til Home hvis vi står på Login
|
|
if (navController != null && navController.getCurrentDestination() != null &&
|
|
navController.getCurrentDestination().getId() == R.id.navigation_login) {
|
|
// Denne aksjonen finnes i mobile_navigation.xml
|
|
navController.navigate(R.id.action_login_to_home);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onError(String message) {
|
|
Log.e(TAG, "Silent login feilet mot WP: " + message);
|
|
navigateToLogin();
|
|
}
|
|
}
|
|
);
|
|
})
|
|
.addOnFailureListener(e -> {
|
|
Log.e(TAG, "Silent Sign-In feilet mot Google", e);
|
|
navigateToLogin();
|
|
});
|
|
}
|
|
|
|
private void navigateToLogin() {
|
|
if (navController != null) {
|
|
if (navController.getCurrentDestination() != null &&
|
|
// Sjekker mot R.id.navigation_login som er ID'en til fragmentet
|
|
navController.getCurrentDestination().getId() != R.id.navigation_login) {
|
|
// Denne ID'en finnes i mobile_navigation.xml
|
|
navController.navigate(R.id.navigation_login);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\NewsAdapter.java
|
|
============================================================
|
|
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<WpPost> posts;
|
|
private OnItemClickListener listener; // NYTT
|
|
|
|
// 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
|
|
@Override
|
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news, parent, false);
|
|
return new ViewHolder(view);
|
|
}
|
|
|
|
@Override
|
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
|
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 posts.size();
|
|
}
|
|
|
|
public static class ViewHolder extends RecyclerView.ViewHolder {
|
|
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);
|
|
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();
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\NewsDetailFragment.java
|
|
============================================================
|
|
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());
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\NewsFullFragment.java
|
|
============================================================
|
|
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 på 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 må 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) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\NewsItem.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
public class NewsItem {
|
|
private String title;
|
|
private String excerpt; // Kort tekst/ingress
|
|
private String author;
|
|
|
|
public NewsItem(String title, String excerpt, String author) {
|
|
this.title = title;
|
|
this.excerpt = excerpt;
|
|
this.author = author;
|
|
}
|
|
|
|
public String getTitle() { return title; }
|
|
public String getExcerpt() { return excerpt; }
|
|
public String getAuthor() { return author; }
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\NotificationWorker.java
|
|
============================================================
|
|
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());
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\ProfileFragment.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import android.os.Bundle;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.widget.Button;
|
|
import android.widget.ImageView;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.fragment.app.Fragment;
|
|
import androidx.navigation.NavController;
|
|
import androidx.navigation.Navigation;
|
|
|
|
import com.bumptech.glide.Glide;
|
|
import com.bumptech.glide.request.RequestOptions;
|
|
import com.google.android.gms.auth.api.signin.GoogleSignIn;
|
|
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
|
|
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
|
|
|
|
public class ProfileFragment extends Fragment {
|
|
|
|
@Nullable
|
|
@Override
|
|
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
|
|
|
|
// 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
|
|
if (user.getPhotoUrl() != null) {
|
|
Glide.with(this)
|
|
.load(user.getPhotoUrl())
|
|
.apply(RequestOptions.circleCropTransform())
|
|
.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
|
|
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();
|
|
});
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\RetrofitClient.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import com.google.gson.Gson;
|
|
import com.google.gson.GsonBuilder;
|
|
import com.google.gson.reflect.TypeToken;
|
|
|
|
import java.io.IOException;
|
|
import java.util.List;
|
|
|
|
import okhttp3.Interceptor;
|
|
import okhttp3.OkHttpClient;
|
|
import okhttp3.Request;
|
|
import okhttp3.Response;
|
|
import okhttp3.logging.HttpLoggingInterceptor; // NY IMPORT
|
|
import retrofit2.Retrofit;
|
|
import retrofit2.converter.gson.GsonConverterFactory;
|
|
|
|
public class RetrofitClient {
|
|
private static final String BASE_URL = "https://intranet.kbs.no/";
|
|
private static Retrofit retrofit = null;
|
|
|
|
public static WordPressApiService getApiService() {
|
|
if (retrofit == null) {
|
|
|
|
// NYTT: Logging Interceptor
|
|
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
|
|
logging.setLevel(HttpLoggingInterceptor.Level.BODY); // Logger ALT (Body, Headers)
|
|
|
|
OkHttpClient client = new OkHttpClient.Builder()
|
|
.addInterceptor(logging) // Legg til loggingen her
|
|
.addInterceptor(new Interceptor() {
|
|
@Override
|
|
public Response intercept(Chain chain) throws IOException {
|
|
Request originalRequest = chain.request();
|
|
Request.Builder builder = originalRequest.newBuilder();
|
|
|
|
String dynamicCookie = UserManager.getInstance().getCookie();
|
|
if (dynamicCookie != null && !dynamicCookie.isEmpty()) {
|
|
builder.header("Cookie", dynamicCookie);
|
|
}
|
|
|
|
return chain.proceed(builder.build());
|
|
}
|
|
})
|
|
.build();
|
|
|
|
Gson gson = new GsonBuilder()
|
|
.registerTypeAdapter(new TypeToken<List<GravityField.Choice>>(){}.getType(), new ChoicesAdapter())
|
|
.setLenient() // NYTT: Gjør parsing litt mer tilgivende
|
|
.create();
|
|
|
|
retrofit = new Retrofit.Builder()
|
|
.baseUrl(BASE_URL)
|
|
.client(client)
|
|
.addConverterFactory(GsonConverterFactory.create(gson))
|
|
.build();
|
|
}
|
|
return retrofit.create(WordPressApiService.class);
|
|
}
|
|
|
|
public static void clearClient() {
|
|
retrofit = null;
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\UserManager.java
|
|
============================================================
|
|
// FILSTI: app\src\main\java\com\kbs\kbsintranett\UserManager.java
|
|
package com.kbs.kbsintranett;
|
|
|
|
import androidx.annotation.Nullable;
|
|
|
|
/**
|
|
* UserManager fungerer som en global sesjon for appen.
|
|
* Den holder på informasjon om innlogget bruker, rettigheter og autentiserings-cookie.
|
|
*/
|
|
public class UserManager {
|
|
|
|
private static UserManager instance;
|
|
|
|
// Google Data
|
|
private String userDisplayName;
|
|
private String userEmail;
|
|
private String googleIdToken;
|
|
private String photoUrl;
|
|
|
|
// WordPress Data
|
|
private int userId;
|
|
private String userRole;
|
|
private String currentCookie;
|
|
|
|
// --- NYE FELTER ---
|
|
private String firstName;
|
|
private String lastName;
|
|
private String stilling;
|
|
private String mobiltelefon;
|
|
|
|
private UserManager() {
|
|
// Initielt er ingen logget inn
|
|
}
|
|
|
|
public static synchronized UserManager getInstance() {
|
|
if (instance == null) {
|
|
instance = new UserManager();
|
|
}
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
* Kalles når Google-innlogging er vellykket.
|
|
*/
|
|
public void setUserData(String name, String email, String token, @Nullable String photoUrl) {
|
|
this.userDisplayName = name;
|
|
this.userEmail = email;
|
|
this.googleIdToken = token;
|
|
this.photoUrl = photoUrl;
|
|
}
|
|
|
|
// --- NY METODE FOR UTVIDET INFO ---
|
|
public void setExtendedUserInfo(String firstName, String lastName, String stilling, String mobiltelefon) {
|
|
this.firstName = firstName;
|
|
this.lastName = lastName;
|
|
this.stilling = stilling;
|
|
this.mobiltelefon = mobiltelefon;
|
|
}
|
|
|
|
public void setCookie(String cookie) {
|
|
this.currentCookie = cookie;
|
|
}
|
|
|
|
public void setUserRole(String role) {
|
|
this.userRole = role;
|
|
}
|
|
|
|
public void setUserId(int id) {
|
|
this.userId = id;
|
|
}
|
|
|
|
// ---------------- GETTERS ----------------
|
|
|
|
public String getUserDisplayName() { return userDisplayName != null ? userDisplayName : ""; }
|
|
public String getUserEmail() { return userEmail != null ? userEmail : ""; }
|
|
public String getGoogleIdToken() { return googleIdToken; }
|
|
public String getPhotoUrl() { return photoUrl; }
|
|
public String getCookie() { return currentCookie; }
|
|
public String getUserRole() { return userRole != null ? userRole : "subscriber"; }
|
|
public int getUserId() { return userId; }
|
|
|
|
// --- NYE GETTERS ---
|
|
public String getFirstName() { return firstName != null ? firstName : ""; }
|
|
public String getLastName() { return lastName != null ? lastName : ""; }
|
|
public String getStilling() { return stilling != null ? stilling : ""; }
|
|
public String getMobiltelefon() { return mobiltelefon != null ? mobiltelefon : ""; }
|
|
|
|
// ---------------- HJELPEMETODER ----------------
|
|
|
|
public boolean isLoggedIn() {
|
|
return userEmail != null && !userEmail.isEmpty();
|
|
}
|
|
|
|
public boolean isAdmin() {
|
|
return "administrator".equalsIgnoreCase(userRole);
|
|
}
|
|
|
|
public boolean isEditorOrAbove() {
|
|
if (userRole == null) return false;
|
|
return userRole.equalsIgnoreCase("administrator") || userRole.equalsIgnoreCase("editor");
|
|
}
|
|
|
|
/**
|
|
* Nullstiller alt. Kalles ved utlogging.
|
|
*/
|
|
public void logout() {
|
|
userDisplayName = null;
|
|
userEmail = null;
|
|
googleIdToken = null;
|
|
photoUrl = null;
|
|
userRole = null;
|
|
currentCookie = null;
|
|
userId = 0;
|
|
|
|
// Nullstill nye felter
|
|
firstName = null;
|
|
lastName = null;
|
|
stilling = null;
|
|
mobiltelefon = null;
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\WebViewActivity.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.os.Bundle;
|
|
import android.webkit.CookieManager;
|
|
import android.webkit.WebSettings;
|
|
import android.webkit.WebView;
|
|
import android.webkit.WebViewClient;
|
|
import androidx.appcompat.app.AppCompatActivity;
|
|
import androidx.appcompat.widget.Toolbar;
|
|
|
|
public class WebViewActivity extends AppCompatActivity {
|
|
|
|
public static final String EXTRA_URL = "extra_url";
|
|
public static final String EXTRA_TITLE = "extra_title";
|
|
|
|
@SuppressLint("SetJavaScriptEnabled")
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
setContentView(R.layout.activity_webview);
|
|
|
|
String url = getIntent().getStringExtra(EXTRA_URL);
|
|
String title = getIntent().getStringExtra(EXTRA_TITLE);
|
|
|
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
|
toolbar.setTitle(title != null ? title : "Håndbok");
|
|
toolbar.setNavigationIcon(android.R.drawable.ic_menu_revert);
|
|
toolbar.setNavigationOnClickListener(v -> finish());
|
|
|
|
WebView webView = findViewById(R.id.webview);
|
|
WebSettings settings = webView.getSettings();
|
|
settings.setJavaScriptEnabled(true);
|
|
settings.setDomStorageEnabled(true);
|
|
settings.setBuiltInZoomControls(true);
|
|
settings.setDisplayZoomControls(false);
|
|
|
|
// --- MAGIEN SKJER HER: INJISER COOKIE ---
|
|
String cookie = UserManager.getInstance().getCookie();
|
|
if (cookie != null && !cookie.isEmpty()) {
|
|
CookieManager cookieManager = CookieManager.getInstance();
|
|
cookieManager.setAcceptCookie(true);
|
|
// Vi antar at domenet er intranet.kbs.no basert på APIet
|
|
cookieManager.setCookie("https://intranet.kbs.no", cookie);
|
|
}
|
|
// ----------------------------------------
|
|
|
|
webView.setWebViewClient(new WebViewClient()); // Åpne linker i samme WebView
|
|
|
|
if (url != null) {
|
|
webView.loadUrl(url);
|
|
}
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\WordPressApiService.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import com.google.gson.JsonElement;
|
|
import com.google.gson.JsonObject; // NY IMPORT
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import okhttp3.MultipartBody;
|
|
import okhttp3.RequestBody;
|
|
import retrofit2.Call;
|
|
import retrofit2.http.Body;
|
|
import retrofit2.http.GET;
|
|
import retrofit2.http.POST;
|
|
import retrofit2.http.Path;
|
|
import retrofit2.http.Multipart;
|
|
import retrofit2.http.Part;
|
|
import retrofit2.http.PartMap;
|
|
import retrofit2.http.Query;
|
|
|
|
public interface WordPressApiService {
|
|
@GET("wp-json/wp/v2/posts?per_page=10&_embed")
|
|
Call<List<WpPost>> getPosts();
|
|
|
|
@GET("wp-json/kbs/v1/forms/{id}")
|
|
Call<GravityForm> getForm(@Path("id") int formId);
|
|
|
|
@POST("wp-json/gf/v2/forms/{id}/submissions")
|
|
Call<JsonElement> submitForm(@Path("id") int formId, @Body FormSubmission submission);
|
|
|
|
@POST("wp-json/kbs/v1/login")
|
|
Call<LoginResponse> googleLogin(@Body LoginRequest request);
|
|
|
|
@GET("wp-json/kbs/v1/forms")
|
|
Call<List<GravityForm>> getFormsList();
|
|
|
|
@Multipart
|
|
@POST("wp-json/gf/v2/forms/{id}/submissions")
|
|
Call<JsonElement> submitMultipartForm(
|
|
@Path("id") int formId,
|
|
@PartMap Map<String, RequestBody> textFields,
|
|
@Part List<MultipartBody.Part> files
|
|
);
|
|
|
|
@GET("wp-json/kbs/v1/calendar/events")
|
|
Call<List<CalendarEvent>> getCalendarEvents();
|
|
|
|
@GET("wp-json/gf/v2/entries")
|
|
Call<GravityEntryResponse> getEntries(
|
|
@Query("form_ids") int formId,
|
|
@Query("search") String searchJson,
|
|
@Query("paging[page_size]") int pageSize
|
|
);
|
|
|
|
@GET("wp-json/gf/v2/entries/{entry_id}")
|
|
Call<JsonElement> getSingleEntry(@Path("entry_id") String entryId);
|
|
|
|
@GET("wp-json/wp/v2/posts?per_page=50&_embed")
|
|
Call<List<WpPost>> getAllPosts();
|
|
|
|
@GET("wp-json/kbs/v1/handbook")
|
|
Call<List<HandbookItem>> getHandbookItems();
|
|
|
|
@GET("wp-json/kbs/v1/handbook/{id}")
|
|
Call<HandbookPage> getHandbookPage(@Path("id") int id);
|
|
|
|
// NY: Slå opp ID fra URL
|
|
@GET("wp-json/kbs/v1/lookup-id")
|
|
Call<JsonObject> lookupPageId(@Query("url") String url);
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\java\com\kbs\kbsintranett\WpPost.java
|
|
============================================================
|
|
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 implements Serializable {
|
|
@SerializedName("title")
|
|
public Rendered title;
|
|
|
|
@SerializedName("excerpt")
|
|
public Rendered excerpt;
|
|
|
|
@SerializedName("content")
|
|
public Rendered content;
|
|
|
|
@SerializedName("date")
|
|
public String date;
|
|
|
|
@SerializedName("_embedded")
|
|
public Embedded embedded;
|
|
|
|
public static class Rendered implements Serializable {
|
|
@SerializedName("rendered")
|
|
public String renderedString;
|
|
}
|
|
|
|
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() {
|
|
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 "";
|
|
}
|
|
}
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\drawable\bg_category_selected.xml
|
|
============================================================
|
|
<?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>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\drawable\bg_category_unselected.xml
|
|
============================================================
|
|
<?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>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\drawable\ic_book.xml
|
|
============================================================
|
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
|
|
|
|
<path android:fillColor="@android:color/white" android:pathData="M240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L720,80Q753,80 776.5,103.5Q800,127 800,160L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM240,800L720,800Q720,800 720,800Q720,800 720,800L720,160Q720,160 720,160Q720,160 720,160L640,160L640,440L540,380L440,440L440,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800ZM240,800Q240,800 240,800Q240,800 240,800L240,160Q240,160 240,160Q240,160 240,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800ZM440,440L540,380L640,440L640,440L540,380L440,440Z"/>
|
|
|
|
</vector>
|
|
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\drawable\ic_form.xml
|
|
============================================================
|
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
|
|
|
<path android:fillColor="@android:color/white" android:pathData="M22,7h-9v2h9V7zM22,15h-9v2h9V15zM5.54,11L2,7.46l1.41,-1.41l2.12,2.12l4.24,-4.24l1.41,1.41L5.54,11zM5.54,19L2,15.46l1.41,-1.41l2.12,2.12l4.24,-4.24l1.41,1.41L5.54,19z"/>
|
|
|
|
</vector>
|
|
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\drawable\ic_handbook_car.xml
|
|
============================================================
|
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
android:width="24dp"
|
|
android:height="24dp"
|
|
android:viewportWidth="24"
|
|
android:viewportHeight="24"
|
|
android:tint="#0069B3">
|
|
<path android:fillColor="@android:color/white" android:pathData="M18.92,6.01C18.72,5.42 18.16,5 17.5,5h-11c-0.66,0 -1.21,0.42 -1.42,1.01L3,12v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8l-2.08,-5.99zM6.5,16c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,13 6.5,13s1.5,0.67 1.5,1.5S7.33,16 6.5,16zM17.5,16c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM5,11l1.5,-4.5h11L19,11H5z"/>
|
|
</vector>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\drawable\ic_handbook_doc.xml
|
|
============================================================
|
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
android:width="24dp"
|
|
android:height="24dp"
|
|
android:viewportWidth="24"
|
|
android:viewportHeight="24"
|
|
android:tint="#0069B3">
|
|
<path android:fillColor="@android:color/white" android:pathData="M14,2H6c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2H18c1.1,0 2,-0.9 2,-2V8l-6,-6zm2,16H8v-2h8v2zm0,-4H8v-2h8v2zm-3,-5V3.5L18.5,9H13z"/>
|
|
</vector>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\drawable\ic_handbook_general.xml
|
|
============================================================
|
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
android:width="24dp"
|
|
android:height="24dp"
|
|
android:viewportWidth="24"
|
|
android:viewportHeight="24"
|
|
android:tint="#0069B3">
|
|
<path android:fillColor="@android:color/white" android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2V7h-2v2z"/>
|
|
</vector>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\drawable\ic_handbook_health.xml
|
|
============================================================
|
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
android:width="24dp"
|
|
android:height="24dp"
|
|
android:viewportWidth="24"
|
|
android:viewportHeight="24"
|
|
android:tint="#0069B3">
|
|
<path android:fillColor="@android:color/white" android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM17,13h-4v4h-2v-4H7v-2h4V7h2v4h4V13z"/>
|
|
</vector>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\drawable\ic_handbook_people.xml
|
|
============================================================
|
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
android:width="24dp"
|
|
android:height="24dp"
|
|
android:viewportWidth="24"
|
|
android:viewportHeight="24"
|
|
android:tint="#0069B3">
|
|
<path android:fillColor="@android:color/white" android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zm-8,0c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zm0,2c-2.33,0 -7,1.17 -7,3.5V19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zm8,0c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45V19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z"/>
|
|
</vector>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\drawable\ic_handbook_warning.xml
|
|
============================================================
|
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
android:width="24dp"
|
|
android:height="24dp"
|
|
android:viewportWidth="24"
|
|
android:viewportHeight="24"
|
|
android:tint="#0069B3">
|
|
<path android:fillColor="@android:color/white" android:pathData="M1,21h22L12,2 1,21zm12,-3h-2v-2h2v2zm0,-4h-2v-4h2v4z"/>
|
|
</vector>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\drawable\ic_home.xml
|
|
============================================================
|
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
|
|
|
<path android:fillColor="@android:color/white" android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
|
|
|
|
</vector>
|
|
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\drawable\ic_launcher_background.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<vector
|
|
android:height="108dp"
|
|
android:width="108dp"
|
|
android:viewportHeight="108"
|
|
android:viewportWidth="108"
|
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
|
<path android:fillColor="#3DDC84"
|
|
android:pathData="M0,0h108v108h-108z"/>
|
|
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
</vector>
|
|
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\drawable\ic_launcher_foreground.xml
|
|
============================================================
|
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
xmlns:aapt="http://schemas.android.com/aapt"
|
|
android:width="108dp"
|
|
android:height="108dp"
|
|
android:viewportWidth="108"
|
|
android:viewportHeight="108">
|
|
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
|
<aapt:attr name="android:fillColor">
|
|
<gradient
|
|
android:endX="85.84757"
|
|
android:endY="92.4963"
|
|
android:startX="42.9492"
|
|
android:startY="49.59793"
|
|
android:type="linear">
|
|
<item
|
|
android:color="#44000000"
|
|
android:offset="0.0" />
|
|
<item
|
|
android:color="#00000000"
|
|
android:offset="1.0" />
|
|
</gradient>
|
|
</aapt:attr>
|
|
</path>
|
|
<path
|
|
android:fillColor="#FFFFFF"
|
|
android:fillType="nonZero"
|
|
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
|
android:strokeWidth="1"
|
|
android:strokeColor="#00000000" />
|
|
</vector>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\activity_main.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<androidx.constraintlayout.widget.ConstraintLayout
|
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
xmlns:tools="http://schemas.android.com/tools"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent"
|
|
android:fitsSystemWindows="true"
|
|
tools:context=".MainActivity">
|
|
|
|
<androidx.fragment.app.FragmentContainerView
|
|
android:id="@+id/nav_host_fragment"
|
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
|
android:layout_width="0dp"
|
|
android:layout_height="0dp"
|
|
app:defaultNavHost="true"
|
|
app:navGraph="@navigation/mobile_navigation"
|
|
app:layout_constraintBottom_toTopOf="@id/bottom_nav_view"
|
|
app:layout_constraintLeft_toLeftOf="parent"
|
|
app:layout_constraintRight_toRightOf="parent"
|
|
app:layout_constraintTop_toTopOf="parent" />
|
|
|
|
<com.google.android.material.bottomnavigation.BottomNavigationView
|
|
android:id="@+id/bottom_nav_view"
|
|
android:layout_width="0dp"
|
|
android:layout_height="wrap_content"
|
|
android:background="?android:attr/windowBackground"
|
|
app:menu="@menu/bottom_nav_menu"
|
|
app:layout_constraintBottom_toBottomOf="parent"
|
|
app:layout_constraintLeft_toLeftOf="parent"
|
|
app:layout_constraintRight_toRightOf="parent" />
|
|
|
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\activity_webview.xml
|
|
============================================================
|
|
<?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">
|
|
|
|
<androidx.appcompat.widget.Toolbar
|
|
android:id="@+id/toolbar"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="?attr/actionBarSize"
|
|
android:background="@color/white"
|
|
android:elevation="4dp"
|
|
android:theme="@style/ThemeOverlay.AppCompat.Light" />
|
|
|
|
<WebView
|
|
android:id="@+id/webview"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent" />
|
|
</LinearLayout>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\bottom_sheet_calendar_details.xml
|
|
============================================================
|
|
<?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>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\fragment_calendar_full.xml
|
|
============================================================
|
|
<?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>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\fragment_forms.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
xmlns:tools="http://schemas.android.com/tools"
|
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent"
|
|
android:orientation="vertical"
|
|
android:background="#F5F5F5"
|
|
tools:context=".FormsFragment">
|
|
|
|
<LinearLayout
|
|
android:id="@+id/history_wrapper"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="0dp"
|
|
android:layout_weight="3"
|
|
android:orientation="vertical"
|
|
android:background="#FFFFFF"
|
|
android:elevation="4dp"
|
|
android:layout_marginBottom="8dp"
|
|
android:padding="16dp">
|
|
|
|
<TextView
|
|
android:id="@+id/lbl_history"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:text="Tidligere innsendinger"
|
|
android:textSize="18sp"
|
|
android:textStyle="bold"
|
|
android:textColor="#333333"
|
|
android:layout_marginBottom="10dp"/>
|
|
|
|
<ScrollView
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent">
|
|
|
|
<LinearLayout
|
|
android:id="@+id/historyContainer"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:orientation="vertical" />
|
|
</ScrollView>
|
|
</LinearLayout>
|
|
|
|
<LinearLayout
|
|
android:layout_width="match_parent"
|
|
android:layout_height="0dp"
|
|
android:layout_weight="7"
|
|
android:orientation="vertical"
|
|
android:background="#FFFFFF"
|
|
android:elevation="4dp">
|
|
|
|
<LinearLayout
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:orientation="horizontal"
|
|
android:gravity="center_vertical"
|
|
android:padding="8dp"
|
|
android:background="#FAFAFA">
|
|
|
|
<ProgressBar
|
|
android:id="@+id/loading_spinner"
|
|
android:layout_width="24dp"
|
|
android:layout_height="24dp"
|
|
android:visibility="gone"
|
|
android:layout_marginEnd="8dp"/>
|
|
|
|
<TextView
|
|
android:id="@+id/txt_status"
|
|
android:layout_width="0dp"
|
|
android:layout_height="wrap_content"
|
|
android:layout_weight="1"
|
|
android:textColor="#666666"
|
|
android:textSize="12sp"
|
|
android:text="" />
|
|
|
|
<ImageView
|
|
android:id="@+id/btn_toggle_history"
|
|
android:layout_width="32dp"
|
|
android:layout_height="32dp"
|
|
android:src="@android:drawable/arrow_up_float"
|
|
android:background="?attr/selectableItemBackgroundBorderless"
|
|
android:padding="4dp"
|
|
android:contentDescription="Vis/Skjul historikk"
|
|
app:tint="#666666" />
|
|
</LinearLayout>
|
|
|
|
<ScrollView
|
|
android:id="@+id/form_scroll_view"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent"
|
|
android:padding="16dp"
|
|
android:fillViewport="true">
|
|
|
|
<LinearLayout
|
|
android:id="@+id/form_container"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:orientation="vertical"
|
|
android:paddingBottom="40dp">
|
|
</LinearLayout>
|
|
</ScrollView>
|
|
</LinearLayout>
|
|
|
|
</LinearLayout>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\fragment_forms_list.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
android:id="@+id/main_layout"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent"
|
|
android:orientation="vertical"
|
|
android:padding="16dp"
|
|
android:background="#F5F5F5">
|
|
|
|
<TextView
|
|
android:id="@+id/header_title"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:text="Skjemaer"
|
|
android:textSize="24sp"
|
|
android:textStyle="bold"
|
|
android:layout_marginBottom="20dp"
|
|
android:textColor="#333333"/>
|
|
|
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
android:id="@+id/swipe_refresh"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent">
|
|
|
|
<ScrollView
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent">
|
|
|
|
<LinearLayout
|
|
android:id="@+id/forms_container"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:orientation="vertical" />
|
|
</ScrollView>
|
|
|
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
|
|
|
</LinearLayout>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\fragment_handbook.xml
|
|
============================================================
|
|
<?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="#F5F5F5">
|
|
|
|
<LinearLayout
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:orientation="vertical"
|
|
android:padding="16dp"
|
|
android:background="@color/white"
|
|
android:elevation="4dp">
|
|
|
|
<TextView
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:text="Håndbok"
|
|
android:textSize="24sp"
|
|
android:textStyle="bold"
|
|
android:textColor="#333333"
|
|
android:layout_marginBottom="12dp"/>
|
|
|
|
<TextView
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:text="KBS Klima og Byggservice ønsker å ta vare på miljø og helse, og har derfor utarbeidet en håndbok som omhandler disse temaene. For enkelthets skyld er denne publisert i KBS-appen og på Intranettet.\n\nDet forventes at alle ansatte skal ha lest hele håndboken minst én gang, og at de har lest og forstått bedriftens HMS-målsetting senest 1. april hvert år."
|
|
android:textSize="14sp"
|
|
android:textColor="#666666"
|
|
android:lineSpacingExtra="4dp"
|
|
android:layout_marginBottom="16dp"/>
|
|
|
|
<EditText
|
|
android:id="@+id/search_field"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="48dp"
|
|
android:background="@drawable/bg_category_unselected"
|
|
android:hint="Søk i håndboken..."
|
|
android:paddingHorizontal="16dp"
|
|
android:drawableStart="@android:drawable/ic_menu_search"
|
|
android:drawablePadding="8dp"
|
|
android:textColor="#333"
|
|
android:textSize="14sp"
|
|
android:inputType="text"/>
|
|
</LinearLayout>
|
|
|
|
<ProgressBar
|
|
android:id="@+id/progressBar"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:layout_gravity="center"
|
|
android:layout_marginTop="32dp"/>
|
|
|
|
<androidx.recyclerview.widget.RecyclerView
|
|
android:id="@+id/recycler_handbook"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent"
|
|
android:padding="8dp"
|
|
android:clipToPadding="false"/>
|
|
|
|
</LinearLayout>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\fragment_handbook_detail.xml
|
|
============================================================
|
|
<?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/white">
|
|
|
|
<androidx.appcompat.widget.Toolbar
|
|
android:id="@+id/detail_toolbar"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="?attr/actionBarSize"
|
|
android:background="@color/kbs_very_light_blue"
|
|
android:elevation="4dp"
|
|
app:navigationIcon="@android:drawable/ic_menu_revert"
|
|
app:titleTextColor="@color/black" />
|
|
|
|
<RelativeLayout
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent">
|
|
|
|
<WebView
|
|
android:id="@+id/detail_webview"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent"
|
|
android:scrollbars="vertical" />
|
|
|
|
<ProgressBar
|
|
android:id="@+id/detail_loading"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:layout_centerInParent="true"
|
|
android:visibility="gone"/>
|
|
|
|
</RelativeLayout>
|
|
|
|
</LinearLayout>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\fragment_home.xml
|
|
============================================================
|
|
<?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:padding="8dp"
|
|
android:background="@color/kbs_very_light_blue">
|
|
|
|
<RelativeLayout
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:layout_marginBottom="16dp"
|
|
android:paddingHorizontal="8dp">
|
|
|
|
<TextView
|
|
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:orientation="horizontal"
|
|
android:gravity="center_vertical"
|
|
android:layout_marginBottom="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
|
|
android:id="@+id/recycler_calendar"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="0dp"
|
|
android:layout_weight="1"
|
|
android:scrollbars="vertical"
|
|
android:layout_marginBottom="16dp"/>
|
|
|
|
<LinearLayout
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:orientation="horizontal"
|
|
android:gravity="center_vertical"
|
|
android:layout_marginBottom="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"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="0dp"
|
|
android:layout_weight="2"
|
|
android:scrollbars="vertical"/>
|
|
|
|
</LinearLayout>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\fragment_login.xml
|
|
============================================================
|
|
<?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:gravity="center"
|
|
android:padding="32dp"
|
|
android:background="#FFFFFF">
|
|
|
|
<TextView
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:text="KBS Intranett"
|
|
android:textSize="28sp"
|
|
android:textStyle="bold"
|
|
android:textColor="#333333"
|
|
android:layout_marginBottom="8dp"/>
|
|
|
|
<TextView
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:text="Vennligst logg inn"
|
|
android:textSize="16sp"
|
|
android:textColor="#666666"
|
|
android:layout_marginBottom="48dp"/>
|
|
|
|
<com.google.android.gms.common.SignInButton
|
|
android:id="@+id/sign_in_button"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content" />
|
|
|
|
<TextView
|
|
android:id="@+id/status_text"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:layout_marginTop="24dp"
|
|
android:gravity="center"
|
|
android:text=""
|
|
android:textColor="#D32F2F"/>
|
|
|
|
</LinearLayout>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\fragment_news_detail.xml
|
|
============================================================
|
|
<?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>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\fragment_news_full.xml
|
|
============================================================
|
|
<?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>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\fragment_profile.xml
|
|
============================================================
|
|
<?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:padding="16dp"
|
|
android:background="@color/kbs_very_light_blue">
|
|
|
|
<RelativeLayout
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:layout_marginBottom="32dp"
|
|
android:paddingHorizontal="10dp"
|
|
android:background="@color/kbs_logo_blue" >
|
|
|
|
<ImageView
|
|
android:id="@+id/btn_close_profile"
|
|
android:layout_width="40dp"
|
|
android:layout_height="40dp"
|
|
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
|
android:background="?attr/selectableItemBackgroundBorderless"
|
|
android:padding="4dp"
|
|
android:layout_alignParentStart="true"
|
|
android:layout_centerVertical="true"
|
|
app:tint="@color/white"/>
|
|
|
|
<TextView
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:text="Min Profil"
|
|
android:textSize="20sp"
|
|
android:textStyle="bold"
|
|
android:textColor="@color/white"
|
|
android:layout_centerInParent="true"/>
|
|
</RelativeLayout>
|
|
|
|
<LinearLayout
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:orientation="vertical"
|
|
android:gravity="center_horizontal"
|
|
android:paddingTop="32dp">
|
|
|
|
<ImageView
|
|
android:id="@+id/profile_image"
|
|
android:layout_width="120dp"
|
|
android:layout_height="120dp"
|
|
android:src="@android:drawable/sym_def_app_icon"
|
|
android:background="@android:color/transparent"
|
|
android:layout_marginBottom="24dp"/>
|
|
|
|
<TextView
|
|
android:id="@+id/profile_name"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:text="Laster navn..."
|
|
android:textSize="24sp"
|
|
android:textStyle="bold"
|
|
android:textColor="@color/kbs_muted_blue_gray"/>
|
|
|
|
<TextView
|
|
android:id="@+id/profile_email"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:text="..."
|
|
android:textSize="16sp"
|
|
android:textColor="@color/kbs_muted_blue_gray"
|
|
android:layout_marginBottom="8dp"/>
|
|
|
|
<TextView
|
|
android:id="@+id/profile_role"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:text="Rolle: ..."
|
|
android:textSize="14sp"
|
|
android:textStyle="italic"
|
|
android:textColor="@color/kbs_logo_blue"
|
|
android:background="@color/kbs_very_light_blue"
|
|
android:paddingHorizontal="12dp"
|
|
android:paddingVertical="4dp"
|
|
android:layout_marginTop="8dp"
|
|
android:layout_marginBottom="32dp"/>
|
|
|
|
<Button
|
|
android:id="@+id/btn_update_info"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:text="Oppdater mine opplysninger"
|
|
android:backgroundTint="@color/white"
|
|
android:textColor="@color/kbs_logo_blue"
|
|
android:layout_marginBottom="16dp"/>
|
|
|
|
<Button
|
|
android:id="@+id/btn_logout"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:text="Logg ut"
|
|
android:backgroundTint="@color/kbs_logo_accent_red"
|
|
android:textColor="@color/white"/>
|
|
|
|
</LinearLayout>
|
|
|
|
</LinearLayout>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\item_calendar.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<androidx.cardview.widget.CardView 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:layout_marginBottom="8dp"
|
|
android:layout_marginHorizontal="8dp"
|
|
app:cardCornerRadius="8dp"
|
|
app:cardElevation="2dp">
|
|
|
|
<LinearLayout
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:orientation="horizontal"
|
|
android:padding="12dp"
|
|
android:gravity="center_vertical">
|
|
|
|
<LinearLayout
|
|
android:layout_width="50dp"
|
|
android:layout_height="50dp"
|
|
android:orientation="vertical"
|
|
android:gravity="center"
|
|
android:background="@color/kbs_logo_light_blue" android:layout_marginEnd="16dp">
|
|
|
|
<TextView
|
|
android:id="@+id/cal_day"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:text="09"
|
|
android:textSize="20sp"
|
|
android:textStyle="bold"
|
|
android:textColor="@color/white"/>
|
|
|
|
<TextView
|
|
android:id="@+id/cal_month"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:text="DES"
|
|
android:textSize="10sp"
|
|
android:textAllCaps="true"
|
|
android:textColor="@color/white"/>
|
|
</LinearLayout>
|
|
|
|
<LinearLayout
|
|
android:layout_width="0dp"
|
|
android:layout_height="wrap_content"
|
|
android:layout_weight="1"
|
|
android:orientation="vertical">
|
|
|
|
<TextView
|
|
android:id="@+id/cal_title"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:text="Møtetittel"
|
|
android:textSize="16sp"
|
|
android:textStyle="bold"
|
|
android:textColor="@color/kbs_muted_blue_gray"/>
|
|
|
|
<TextView
|
|
android:id="@+id/cal_time"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:text="Kl. 10:00 - 11:00"
|
|
android:textSize="12sp"
|
|
android:textColor="@android:color/darker_gray"/>
|
|
</LinearLayout>
|
|
|
|
</LinearLayout>
|
|
</androidx.cardview.widget.CardView>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\item_category.xml
|
|
============================================================
|
|
<?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" />
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\item_handbook.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<androidx.cardview.widget.CardView 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:layout_margin="6dp"
|
|
app:cardCornerRadius="8dp"
|
|
app:cardElevation="2dp"
|
|
app:cardBackgroundColor="@color/white">
|
|
|
|
<LinearLayout
|
|
android:layout_width="match_parent"
|
|
android:layout_height="wrap_content"
|
|
android:orientation="vertical"
|
|
android:padding="16dp"
|
|
android:gravity="center_horizontal">
|
|
|
|
<ImageView
|
|
android:id="@+id/icon"
|
|
android:layout_width="40dp"
|
|
android:layout_height="40dp"
|
|
android:src="@drawable/ic_handbook_general"
|
|
app:tint="@color/kbs_logo_blue"
|
|
android:layout_marginBottom="12dp"/>
|
|
|
|
<TextView
|
|
android:id="@+id/title"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:text="Tittel"
|
|
android:textStyle="bold"
|
|
android:textColor="#333333"
|
|
android:textSize="14sp"
|
|
android:gravity="center"
|
|
android:layout_marginBottom="4dp"/>
|
|
|
|
<TextView
|
|
android:id="@+id/desc"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content"
|
|
android:text="Beskrivelse..."
|
|
android:textColor="#666666"
|
|
android:textSize="12sp"
|
|
android:gravity="center"
|
|
android:maxLines="2"
|
|
android:ellipsize="end"/>
|
|
|
|
</LinearLayout>
|
|
</androidx.cardview.widget.CardView>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\layout\item_news.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<androidx.cardview.widget.CardView 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: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">
|
|
|
|
<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:orientation="vertical"
|
|
android:padding="16dp">
|
|
|
|
<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_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>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\menu\bottom_nav_menu.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
|
<item
|
|
android:id="@+id/navigation_home"
|
|
android:icon="@android:drawable/ic_menu_today"
|
|
android:title="Hjem" />
|
|
|
|
<item
|
|
android:id="@+id/navigation_forms"
|
|
android:icon="@android:drawable/ic_menu_edit"
|
|
android:title="Skjema" />
|
|
|
|
<item
|
|
android:id="@+id/navigation_handbook"
|
|
android:icon="@android:drawable/ic_menu_info_details"
|
|
android:title="Håndbok" />
|
|
</menu>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\mipmap-anydpi-v26\ic_launcher.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
<background android:drawable="@color/ic_launcher_background"/>
|
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
|
</adaptive-icon>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\mipmap-anydpi-v26\ic_launcher_round.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
<background android:drawable="@color/ic_launcher_background"/>
|
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
|
</adaptive-icon>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\navigation\mobile_navigation.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
xmlns:tools="http://schemas.android.com/tools"
|
|
android:id="@+id/mobile_navigation"
|
|
app:startDestination="@+id/navigation_home">
|
|
|
|
<fragment
|
|
android:id="@+id/navigation_login"
|
|
android:name="com.kbs.kbsintranett.LoginFragment"
|
|
android:label="Logg inn"
|
|
tools:layout="@layout/fragment_login">
|
|
<action
|
|
android:id="@+id/action_login_to_home"
|
|
app:destination="@id/navigation_home"
|
|
app:popUpTo="@id/navigation_home"
|
|
app:popUpToInclusive="true" />
|
|
</fragment>
|
|
|
|
<fragment
|
|
android:id="@+id/navigation_home"
|
|
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
|
|
android:id="@+id/navigation_calendar_full"
|
|
android:name="com.kbs.kbsintranett.CalendarFullFragment"
|
|
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_list">
|
|
<action
|
|
android:id="@+id/action_formsListFragment_to_formsDetailFragment"
|
|
app:destination="@id/navigation_forms_detail" />
|
|
</fragment>
|
|
|
|
<fragment
|
|
android:id="@+id/navigation_forms_detail"
|
|
android:name="com.kbs.kbsintranett.FormsFragment"
|
|
android:label="Fyll ut skjema"
|
|
tools:layout="@layout/fragment_forms">
|
|
<argument
|
|
android:name="formId"
|
|
app:argType="integer"
|
|
android:defaultValue="0" />
|
|
</fragment>
|
|
|
|
<fragment
|
|
android:id="@+id/navigation_handbook"
|
|
android:name="com.kbs.kbsintranett.HandbookFragment"
|
|
android:label="Håndbok"
|
|
tools:layout="@layout/fragment_handbook">
|
|
<action
|
|
android:id="@+id/action_handbook_to_detail"
|
|
app:destination="@id/navigation_handbook_detail" />
|
|
</fragment>
|
|
|
|
<fragment
|
|
android:id="@+id/navigation_handbook_detail"
|
|
android:name="com.kbs.kbsintranett.HandbookDetailFragment"
|
|
android:label="Håndbok Detaljer"
|
|
tools:layout="@layout/fragment_handbook_detail">
|
|
<argument
|
|
android:name="page_id"
|
|
app:argType="integer" />
|
|
<argument
|
|
android:name="page_title"
|
|
app:argType="string" />
|
|
|
|
<action
|
|
android:id="@+id/action_handbook_to_detail"
|
|
app:destination="@id/navigation_handbook_detail" />
|
|
|
|
<action
|
|
android:id="@+id/action_handbook_to_form"
|
|
app:destination="@id/navigation_forms_detail" />
|
|
</fragment>
|
|
|
|
<fragment
|
|
android:id="@+id/navigation_profile"
|
|
android:name="com.kbs.kbsintranett.ProfileFragment"
|
|
android:label="Min Profil"
|
|
tools:layout="@layout/fragment_profile">
|
|
<action
|
|
android:id="@+id/action_profile_to_login"
|
|
app:destination="@id/navigation_login"
|
|
app:popUpTo="@id/navigation_home"
|
|
app:popUpToInclusive="true" />
|
|
<action
|
|
android:id="@+id/action_profile_to_form"
|
|
app:destination="@id/navigation_forms_detail" />
|
|
</fragment>
|
|
|
|
</navigation>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\values\colors.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<resources>
|
|
<color name="black">#FF000000</color>
|
|
<color name="white">#FFFFFFFF</color>
|
|
|
|
<color name="kbs_logo_blue">#0069B3</color>
|
|
<color name="kbs_logo_light_blue">#53AFE9</color>
|
|
<color name="kbs_logo_accent_red">#C40426</color>
|
|
|
|
<color name="kbs_very_light_blue">#F5F7FA</color>
|
|
<color name="kbs_muted_blue_gray">#4F5B66</color>
|
|
<color name="kbs_soft_light_pink_beige">#F8E5E8</color>
|
|
</resources>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\values\ic_launcher_background.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<resources>
|
|
<color name="ic_launcher_background">#FFFFFF</color>
|
|
</resources>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\values\strings.xml
|
|
============================================================
|
|
<resources>
|
|
<string name="app_name">KBS Intranett</string>
|
|
</resources>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\values\themes.xml
|
|
============================================================
|
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
<style name="Base.Theme.KBSIntranett" parent="Theme.Material3.Light.NoActionBar">
|
|
<item name="colorPrimary">#0056b3</item> <item name="colorPrimaryVariant">#004494</item>
|
|
<item name="colorOnPrimary">#FFFFFF</item>
|
|
|
|
<item name="android:windowLightStatusBar">true</item>
|
|
</style>
|
|
|
|
<style name="Theme.KBSIntranett" parent="Base.Theme.KBSIntranett" />
|
|
</resources>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\values-night\themes.xml
|
|
============================================================
|
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
<style name="Base.Theme.KBSIntranett" parent="Theme.Material3.Light.NoActionBar">
|
|
<item name="colorPrimary">#0056b3</item> <item name="colorPrimaryVariant">#004494</item>
|
|
<item name="colorOnPrimary">#FFFFFF</item>
|
|
|
|
<item name="android:windowLightStatusBar">true</item>
|
|
</style>
|
|
|
|
<style name="Theme.KBSIntranett" parent="Base.Theme.KBSIntranett" />
|
|
</resources>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\xml\backup_rules.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?><!--
|
|
Sample backup rules file; uncomment and customize as necessary.
|
|
See https://developer.android.com/guide/topics/data/autobackup
|
|
for details.
|
|
Note: This file is ignored for devices older than API 31
|
|
See https://developer.android.com/about/versions/12/backup-restore
|
|
-->
|
|
<full-backup-content>
|
|
<!--
|
|
<include domain="sharedpref" path="."/>
|
|
<exclude domain="sharedpref" path="device.xml"/>
|
|
-->
|
|
</full-backup-content>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\xml\data_extraction_rules.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?><!--
|
|
Sample data extraction rules file; uncomment and customize as necessary.
|
|
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
|
for details.
|
|
-->
|
|
<data-extraction-rules>
|
|
<cloud-backup>
|
|
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
|
<include .../>
|
|
<exclude .../>
|
|
-->
|
|
</cloud-backup>
|
|
<!--
|
|
<device-transfer>
|
|
<include .../>
|
|
<exclude .../>
|
|
</device-transfer>
|
|
-->
|
|
</data-extraction-rules>
|
|
|
|
============================================================
|
|
FILSTI: app\src\main\res\xml\file_paths.xml
|
|
============================================================
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<paths>
|
|
<external-files-path name="my_images" path="Pictures" />
|
|
</paths>
|
|
|
|
============================================================
|
|
FILSTI: app\src\test\java\com\kbs\kbsintranett\ExampleUnitTest.java
|
|
============================================================
|
|
package com.kbs.kbsintranett;
|
|
|
|
import org.junit.Test;
|
|
|
|
import static org.junit.Assert.*;
|
|
|
|
/**
|
|
* Example local unit test, which will execute on the development machine (host).
|
|
*
|
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
*/
|
|
public class ExampleUnitTest {
|
|
@Test
|
|
public void addition_isCorrect() {
|
|
assertEquals(4, 2 + 2);
|
|
}
|
|
}
|