commit 56f14e9b9272475be262c22bf75ccc8441d6b3e4 Author: ErolHaagenrud Date: Wed Dec 10 10:03:07 2025 +0100 Første commit – initial backup diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..f675041 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +KBS Intranett \ No newline at end of file diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml new file mode 100644 index 0000000..e762975 --- /dev/null +++ b/.idea/appInsightsSettings.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml new file mode 100644 index 0000000..91f9558 --- /dev/null +++ b/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..639c779 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b2c751a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..85220b6 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,57 @@ +plugins { + alias(libs.plugins.android.application) +} + +android { + namespace = "com.kbs.kbsintranett" + compileSdk { + version = release(36) + } + + defaultConfig { + applicationId = "com.kbs.kbsintranett" + minSdk = 28 + targetSdk = 36 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + 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 (KORRIGERT FOR KOTLIN DSL) + val navVersion = "2.8.5" // Oppdatert til en nyere, stabil versjon + 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") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# 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 \ No newline at end of file diff --git a/app/src/androidTest/java/com/kbs/kbsintranett/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/kbs/kbsintranett/ExampleInstrumentedTest.java new file mode 100644 index 0000000..b8d9f4e --- /dev/null +++ b/app/src/androidTest/java/com/kbs/kbsintranett/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +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 Testing documentation + */ +@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()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4ed031f --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..0809f19 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/kbs/kbsintranett/AuthRepository.java b/app/src/main/java/com/kbs/kbsintranett/AuthRepository.java new file mode 100644 index 0000000..d9e5512 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/AuthRepository.java @@ -0,0 +1,72 @@ +// 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() { + @Override + public void onResponse(Call call, Response 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 call, Throwable t) { + Log.e(TAG, "Nettverksfeil mot WP", t); + callback.onError("Nettverksfeil: " + t.getMessage()); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/CalendarAdapter.java b/app/src/main/java/com/kbs/kbsintranett/CalendarAdapter.java new file mode 100644 index 0000000..2cbf099 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/CalendarAdapter.java @@ -0,0 +1,51 @@ +// 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 { // [cite: 31] + + private List events; + public CalendarAdapter(List events) { // [cite: 32] + this.events = events; + } // [cite: 33] + + @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); // [cite: 34] + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + CalendarEvent event = events.get(position); + holder.day.setText(event.getDay()); // [cite: 35] + holder.month.setText(event.getMonth()); + // NYTT: Tidspunktet hentes nå fra getTime() som formateres i HomeFragment. + holder.time.setText(event.getTime()); + holder.title.setText(event.getTitle()); + } + + @Override + public int getItemCount() { + return events.size(); // [cite: 36] + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + TextView day, month, title, time; // NYTT: Lagt til time + public ViewHolder(View view) { // [cite: 37] + super(view); + day = view.findViewById(R.id.cal_day); + month = view.findViewById(R.id.cal_month); // [cite: 38] + title = view.findViewById(R.id.cal_title); + time = view.findViewById(R.id.cal_time); // NYTT + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/CalendarEvent.java b/app/src/main/java/com/kbs/kbsintranett/CalendarEvent.java new file mode 100644 index 0000000..983c4e3 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/CalendarEvent.java @@ -0,0 +1,30 @@ +// FILSTI: app\src\main\java\com\kbs\kbsintranett\CalendarEvent.java +package com.kbs.kbsintranett; + +public class CalendarEvent { + private String title; + private String rawDate; // NYTT: Holder den fulle, u-formaterte dato/tid-strengen fra API'et + private String day; // F.eks "12" + private String month; // F.eks "DES" + private String time; // NYTT: Brukes kun for visning av tid + + public CalendarEvent(String title, String time, String day, String month) { + this.title = title; + this.time = time; + this.day = day; + this.month = month; + } + + public CalendarEvent(String title, String rawDate) { + this.title = title; + this.rawDate = rawDate; + // La de andre feltene være null i starten, de fylles i HomeFragment + } + + public String getTitle() { return title; } + public String getTime() { return time; } + public String getDay() { return day; } + public String getMonth() { return month; } + + public String getRawDate() { return rawDate; } // NYTT +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/ChoicesAdapter.java b/app/src/main/java/com/kbs/kbsintranett/ChoicesAdapter.java new file mode 100644 index 0000000..0f0a7e1 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/ChoicesAdapter.java @@ -0,0 +1,31 @@ +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> { + @Override + public List 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 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<>(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/ConditionalLogicAdapter.java b/app/src/main/java/com/kbs/kbsintranett/ConditionalLogicAdapter.java new file mode 100644 index 0000000..ef6eef7 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/ConditionalLogicAdapter.java @@ -0,0 +1,26 @@ +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 { + @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; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/FormSubmission.java b/app/src/main/java/com/kbs/kbsintranett/FormSubmission.java new file mode 100644 index 0000000..dc2b16a --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/FormSubmission.java @@ -0,0 +1,14 @@ +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 inputValues; + + public FormSubmission(Map inputValues) { + this.inputValues = inputValues; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/FormsFragment.java b/app/src/main/java/com/kbs/kbsintranett/FormsFragment.java new file mode 100644 index 0000000..7808bf0 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/FormsFragment.java @@ -0,0 +1,541 @@ +package com.kbs.kbsintranett; + +import android.graphics.Color; +import android.graphics.Typeface; +import android.os.Bundle; +import android.text.Html; +import android.text.InputType; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +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"; + + // ID 1 = Ansatteopplysninger (Skal ha autofyll fra historikk) + private static final int ID_ANSATTEOPPLYSNINGER = 1; + + private int formId = 1; + + private LinearLayout formContainer; + private LinearLayout historyContainer; + private TextView txtStatus; + private TextView lblHistory; // Overskriften "Tidligere innsendinger" + private ProgressBar loadingSpinner; + + private Map dynamicFields = new HashMap<>(); + private Map requiredFieldsMap = new HashMap<>(); + + private final OkHttpClient client = new OkHttpClient(); + + @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); + txtStatus = view.findViewById(R.id.txt_status); + lblHistory = view.findViewById(R.id.lbl_history); + loadingSpinner = view.findViewById(R.id.loading_spinner); + + // Fallback hvis XML feiler + if (formContainer == null) { + // Hvis brukeren ikke har oppdatert XML ennå, unngå krasj + formContainer = new LinearLayout(getContext()); + } + + if (getArguments() != null) { + int argId = getArguments().getInt("formId", 0); + if (argId != 0) formId = argId; + } + + fetchFormStructure(); + + return view; + } + + private void fetchFormStructure() { + if (loadingSpinner != null) loadingSpinner.setVisibility(View.VISIBLE); + updateStatus("Laster skjema..."); + + WordPressApiService api = RetrofitClient.getApiService(); + api.getForm(formId).enqueue(new retrofit2.Callback() { + @Override + public void onResponse(retrofit2.Call call, retrofit2.Response response) { + if (response.isSuccessful() && response.body() != null) { + GravityForm form = response.body(); + if (getActivity() != null) { + getActivity().runOnUiThread(() -> { + if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE); + renderDynamicForm(form); + fetchFormEntries(); + }); + } + } else { + updateStatus("Feil ved lasting av skjema."); + if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE); + } + } + + @Override + public void onFailure(retrofit2.Call 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(); + dynamicFields.clear(); + requiredFieldsMap.clear(); + updateStatus(""); + + // Tittel + TextView title = new TextView(getContext()); + title.setText(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()); + formDesc.setText(form.description); + formDesc.setPadding(0, 0, 0, 40); + formContainer.addView(formDesc); + } + + if (form.fields == null) return; + + UserManager user = UserManager.getInstance(); + boolean isPersonaliaSection = true; + + boolean hasFilledName = false; + boolean hasFilledEmail = false; + boolean hasFilledPhone = false; + boolean hasFilledJob = false; + + for (GravityField field : form.fields) { + + if ("hidden".equals(field.type)) continue; + if (field.isHidden) continue; + if ("hidden".equals(field.visibility)) continue; + + if ("section".equals(field.type)) { + String labelLower = field.label.toLowerCase(); + isPersonaliaSection = labelLower.contains("personalia"); + addSectionHeader(field.label, field.description); + continue; + } + + if ("html".equals(field.type)) { + if (field.content != null && field.content.toLowerCase().contains("pårørende")) { + isPersonaliaSection = false; + } + if (field.content != null && !field.content.isEmpty()) { + TextView htmlView = new TextView(getContext()); + htmlView.setText(Html.fromHtml(field.content, Html.FROM_HTML_MODE_COMPACT)); + htmlView.setPadding(0, 40, 0, 10); + formContainer.addView(htmlView); + } + continue; + } + + if (field.inputs != null && !field.inputs.isEmpty()) { + renderCompositeField(field, isPersonaliaSection); + continue; + } + + // ENKELTFELT + TextView label = new TextView(getContext()); + String labelText = field.label; + if (field.isRequired) labelText += " *"; + label.setText(labelText); + label.setTextColor(Color.DKGRAY); + label.setPadding(0, 25, 0, 5); + formContainer.addView(label); + + EditText input = new EditText(getContext()); + input.setPadding(30, 30, 30, 30); + input.setBackgroundResource(android.R.drawable.edit_text); + + // Autofyll fra UserManager (Standardinfo som alltid er greit å fylle ut) + if (isPersonaliaSection) { + String lowerLabel = field.label.toLowerCase(); + + if (!hasFilledName && (lowerLabel.equals("navn") || lowerLabel.equals("navn *"))) { + input.setText(user.getUserDisplayName()); + hasFilledName = true; + } + else if (!hasFilledEmail && lowerLabel.contains("e-post") && lowerLabel.contains("arbeid")) { + input.setText(user.getUserEmail()); + hasFilledEmail = true; + } + else if (!hasFilledJob && lowerLabel.contains("stilling")) { + input.setText(user.getStilling()); + hasFilledJob = true; + } + else if (!hasFilledPhone && (lowerLabel.contains("mobil") || lowerLabel.equals("mobiltelefon"))) { + input.setText(user.getMobiltelefon()); + hasFilledPhone = true; + } + } + + 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); + } + + formContainer.addView(input); + + if (field.description != null && !field.description.isEmpty()) { + addDescription(field.description); + } + + dynamicFields.put(String.valueOf(field.id), input); + requiredFieldsMap.put(String.valueOf(field.id), field.isRequired); + } + + // SEND-KNAPP + 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); + } + + private void renderCompositeField(GravityField parentField, boolean isPersonaliaSection) { + TextView groupLabel = new TextView(getContext()); + String groupText = parentField.label; + if (parentField.isRequired) groupText += " *"; + groupLabel.setText(groupText); + groupLabel.setTextColor(Color.DKGRAY); + groupLabel.setTypeface(null, Typeface.BOLD); + groupLabel.setPadding(0, 30, 0, 5); + formContainer.addView(groupLabel); + + UserManager user = UserManager.getInstance(); + String lowerParentLabel = parentField.label.toLowerCase(); + + List inputs = new ArrayList<>(parentField.inputs); + + // Sortering for adresse + if (parentField.type.equals("address")) { + Collections.sort(inputs, new Comparator() { + @Override + public int compare(GravityField f1, GravityField f2) { + int s1 = getAddressScore(f1.label); + int s2 = getAddressScore(f2.label); + return Integer.compare(s1, s2); + } + }); + } + + for (GravityField subField : inputs) { + if (subField.isHidden) continue; + if ("hidden".equals(subField.visibility)) continue; + + TextView subLabel = new TextView(getContext()); + String subLabelText = subField.label; + + boolean isSubRequired = parentField.isRequired; + if (parentField.type.equals("address") && 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); + formContainer.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 (isPersonaliaSection && lowerParentLabel.contains("navn") && !lowerParentLabel.contains("pårørende")) { + String lowerSubLabel = subField.label.toLowerCase(); + if (lowerSubLabel.contains("fornavn")) { + subInput.setText(user.getFirstName()); + } + else if (lowerSubLabel.contains("etternavn")) { + subInput.setText(user.getLastName()); + } + } + + formContainer.addView(subInput); + + dynamicFields.put(subField.id, subInput); + requiredFieldsMap.put(subField.id, isSubRequired); + } + + if (parentField.description != null && !parentField.description.isEmpty()) { + addDescription(parentField.description); + } + } + + 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 addDescription(String text) { + TextView desc = new TextView(getContext()); + desc.setText(Html.fromHtml(text, Html.FROM_HTML_MODE_COMPACT)); + desc.setTextSize(12); + desc.setTextColor(Color.GRAY); + desc.setPadding(5, 5, 5, 0); + formContainer.addView(desc); + } + + private void addSectionHeader(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, 50, 0, 5); + formContainer.addView(sectionHeader); + + if (descText != null && !descText.isEmpty()) { + addDescription(descText); + } + + View line = new View(getContext()); + line.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 2)); + line.setBackgroundColor(Color.LTGRAY); + formContainer.addView(line); + } + + 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 Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + Log.e(TAG, "Kunne ikke hente historikk", e); + } + + @Override + public void onResponse(@NonNull 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); + + // VIKTIG: AUTO-FYLL SKJER NÅ KUN FOR SKJEMA ID 1 + if (entries.length() > 0 && formId == ID_ANSATTEOPPLYSNINGER) { + try { + prefillFormFromHistory(entries.getJSONObject(0)); + } catch (JSONException e) { e.printStackTrace(); } + } + }); + } + } + } catch (JSONException e) { e.printStackTrace(); } + } + } + }); + } + + private void prefillFormFromHistory(JSONObject latestEntry) { + for (Map.Entry mapEntry : dynamicFields.entrySet()) { + String fieldId = mapEntry.getKey(); + EditText input = mapEntry.getValue(); + + if (input.getText().length() > 0) continue; + + if (latestEntry.has(fieldId)) { + String prevValue = latestEntry.optString(fieldId); + if (!prevValue.isEmpty()) { + input.setText(prevValue); + } + } + } + updateStatus("Skjemaet er forhåndsutfylt fra din forrige innsending."); + } + + private void submitDynamicForm() { + JSONObject inputValues = new JSONObject(); + boolean hasValues = false; + + for (Map.Entry entry : dynamicFields.entrySet()) { + String fieldId = entry.getKey(); + EditText input = entry.getValue(); + String value = input.getText().toString().trim(); + + Boolean isRequired = requiredFieldsMap.get(fieldId); + if (isRequired != null && isRequired && value.isEmpty()) { + input.setError("Må fylles ut"); + input.requestFocus(); + return; + } + + if (!value.isEmpty()) { + try { + inputValues.put("input_" + fieldId, value); + hasValues = true; + } catch (JSONException e) { e.printStackTrace(); } + } + } + + if (!hasValues) { + Toast.makeText(getContext(), "Skjemaet er tomt", Toast.LENGTH_SHORT).show(); + return; + } + + updateStatus("Sender inn..."); + MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(JSON, inputValues.toString()); + String url = BASE_URL_GF + "/forms/" + formId + "/submissions"; + String cookie = UserManager.getInstance().getCookie(); + + if (cookie == null) { + updateStatus("Du er ikke logget inn."); + return; + } + + Request request = new Request.Builder().url(url).post(body).header("Cookie", cookie).build(); + + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + updateStatus("Nettverksfeil: " + e.getMessage()); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { + if (response.isSuccessful()) { + if (getActivity() != null) { + getActivity().runOnUiThread(() -> { + Toast.makeText(getContext(), "Skjema sendt!", Toast.LENGTH_LONG).show(); + fetchFormEntries(); + updateStatus("Innsending OK"); + }); + } + } else { + updateStatus("Feil fra server: " + response.code()); + } + } + }); + } + + private void showHistory(JSONArray entries) { + if (historyContainer == null) return; + historyContainer.removeAllViews(); + + if (entries.length() == 0) { + // Skjul overskriften hvis det ikke er historikk + if (lblHistory != null) lblHistory.setVisibility(View.GONE); + return; + } else { + if (lblHistory != null) lblHistory.setVisibility(View.VISIBLE); + } + + try { + for (int i = 0; i < entries.length(); i++) { + JSONObject entry = entries.getJSONObject(i); + String date = entry.optString("date_created"); + TextView item = new TextView(getContext()); + item.setText(date); + item.setPadding(10, 20, 10, 20); + + // Gjør historikken klikkbar (for fremtidig funksjonalitet) + item.setBackgroundResource(android.R.drawable.list_selector_background); + + 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(); } + } + + private void updateStatus(String msg) { + if (getActivity() != null && txtStatus != null) { + getActivity().runOnUiThread(() -> txtStatus.setText(msg)); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/FormsListFragment.java b/app/src/main/java/com/kbs/kbsintranett/FormsListFragment.java new file mode 100644 index 0000000..9521f21 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/FormsListFragment.java @@ -0,0 +1,140 @@ +package com.kbs.kbsintranett; + +import android.graphics.Color; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.navigation.Navigation; + +import java.util.Map; +import java.util.TreeMap; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class FormsListFragment extends Fragment { + + private LinearLayout container; + private ProgressBar loadingSpinner; + private TextView txtStatus; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + // Vi gjenbruker samme layout som detaljvisningen, siden den nå er ryddig + View view = inflater.inflate(R.layout.fragment_forms, container, false); + + this.container = view.findViewById(R.id.form_container); + this.loadingSpinner = view.findViewById(R.id.loading_spinner); + this.txtStatus = view.findViewById(R.id.txt_status); + + // Skjul "Tidligere innsendinger" tekst og container på denne siden, + // da vi kun skal vise en meny her. + View historyTitle = view.findViewById(R.id.historyContainer); // Egentlig containeren, men vi skjuler alt under + if (historyTitle != null) historyTitle.setVisibility(View.GONE); + + // Finn og skjul "Tidligere innsendinger" overskriften hvis mulig + // (Siden vi bruker en delt XML er dette litt "hacky", men funker) + LinearLayout mainLayout = view.findViewById(R.id.main_layout); + if (mainLayout != null && mainLayout.getChildCount() > 4) { + // Skjuler elementene nederst som hører til historikk + for(int i = 0; i < mainLayout.getChildCount(); i++) { + View child = mainLayout.getChildAt(i); + if (child instanceof TextView && ((TextView)child).getText().toString().contains("Tidligere")) { + child.setVisibility(View.GONE); + } + } + } + + return view; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + fetchFormsList(); + } + + private void fetchFormsList() { + if (container != null) container.removeAllViews(); + if (loadingSpinner != null) loadingSpinner.setVisibility(View.VISIBLE); + if (txtStatus != null) txtStatus.setText(""); + + // Hent listen over skjemaer (Map ID -> Skjema) + RetrofitClient.getApiService().getFormsListMap().enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (getContext() == null) return; + + // SKJUL SPINNER NÅR VI FÅR SVAR + if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE); + + if (response.isSuccessful() && response.body() != null) { + Map formMap = response.body(); + + if (formMap.isEmpty()) { + if (txtStatus != null) txtStatus.setText("Ingen skjemaer funnet."); + return; + } + + // Vi sorterer listen basert på ID (eller tittel) for ryddighet + Map sortedMap = new TreeMap<>(formMap); + + for (Map.Entry entry : sortedMap.entrySet()) { + GravityForm form = entry.getValue(); + // Sjekk at skjemaet er aktivt og har en tittel + if (form != null && form.title != null) { + addFormButton(form); + } + } + } else { + if (txtStatus != null) txtStatus.setText("Kunne ikke laste listen (Kode " + response.code() + ")"); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + if (getContext() == null) return; + // SKJUL SPINNER VED FEIL + if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE); + if (txtStatus != null) txtStatus.setText("Nettverksfeil: " + t.getMessage()); + } + }); + } + + private void addFormButton(GravityForm form) { + Button button = new Button(getContext()); + button.setText(form.title.toUpperCase()); // Store bokstaver ser ofte ryddigere ut i menyer + button.setTextColor(Color.WHITE); + button.setBackgroundColor(Color.parseColor("#0069B3")); // KBS Blå + button.setTextSize(14); + button.setPadding(30, 30, 30, 30); + + // Klikk-lytter + button.setOnClickListener(v -> { + Bundle bundle = new Bundle(); + bundle.putInt("formId", form.id); + Navigation.findNavController(v).navigate(R.id.action_formsListFragment_to_formsDetailFragment, bundle); + }); + + // Layout parametere (Marginer) + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + params.setMargins(0, 0, 0, 20); // Avstand mellom knappene + button.setLayoutParams(params); + + container.addView(button); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/GravityEntryResponse.java b/app/src/main/java/com/kbs/kbsintranett/GravityEntryResponse.java new file mode 100644 index 0000000..bfe2980 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/GravityEntryResponse.java @@ -0,0 +1,15 @@ +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> entries; + // Vi bruker Map 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" } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/GravityField.java b/app/src/main/java/com/kbs/kbsintranett/GravityField.java new file mode 100644 index 0000000..a9f70d9 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/GravityField.java @@ -0,0 +1,82 @@ +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("label") + public String label; + + @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 choices; + + @SerializedName("content") + public String content; + + @SerializedName("inputs") + public List inputs; + + @SerializedName("isHidden") + public boolean isHidden; + + @JsonAdapter(ConditionalLogicAdapter.class) + @SerializedName("conditionalLogic") + public ConditionalLogic conditionalLogic; + + // NYTT: For å lese Populate Anything-regler + @SerializedName("gppa-values-templates") + public java.util.Map gppaTemplates; + + public static class Choice { + @SerializedName("text") + public String text; + + @SerializedName("value") + public String value; + } + + public static class ConditionalLogic { + @SerializedName("actionType") + public String actionType; // "show" eller "hide" + + @SerializedName("logicType") + public String logicType; // "all" eller "any" + + @SerializedName("rules") + public List rules; + } + + public static class Rule { + @SerializedName("fieldId") + public String fieldId; + + @SerializedName("operator") + public String operator; // "is", "isnot", etc. + + @SerializedName("value") + public String value; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/GravityForm.java b/app/src/main/java/com/kbs/kbsintranett/GravityForm.java new file mode 100644 index 0000000..cd54690 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/GravityForm.java @@ -0,0 +1,18 @@ +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; + + @SerializedName("fields") + public List fields; // En liste med alle feltene i skjemaet +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/HandbookFragment.java b/app/src/main/java/com/kbs/kbsintranett/HandbookFragment.java new file mode 100644 index 0000000..6cd7db3 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/HandbookFragment.java @@ -0,0 +1,12 @@ +package com.kbs.kbsintranett; + +import androidx.fragment.app.Fragment; // Viktig import! + +public class HandbookFragment extends Fragment { + // Tomt innhold er OK, men klassen må hete det samme som filnavnet + // og den må arve fra (extends) Fragment. + + public HandbookFragment() { + super(R.layout.fragment_home); // Kobler til layouten automatisk + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java new file mode 100644 index 0000000..6f4eda9 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/HomeFragment.java @@ -0,0 +1,202 @@ +// FILSTI: app\src\main\java\com\kbs\kbsintranett\HomeFragment.java +package com.kbs.kbsintranett; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +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.ParseException; +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 retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class HomeFragment extends Fragment { + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + // Laster inn layouten fra XML (fragment_home.xml) + return inflater.inflate(R.layout.fragment_home, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + // --------------------------------------------------------- + // 0. SETT OPP PROFIL-KNAPP (Ny!) + // --------------------------------------------------------- + // Vi finner ikonet vi la til i XML og sier at det skal gå til Profil-siden + View profileBtn = view.findViewById(R.id.btn_profile); + if (profileBtn != null) { + profileBtn.setOnClickListener(v -> { + Navigation.findNavController(view).navigate(R.id.navigation_profile); + }); + } + + // --------------------------------------------------------- + // 1. SETT OPP KALENDER (Henter fra WordPress) + // --------------------------------------------------------- + RecyclerView calendarRecycler = view.findViewById(R.id.recycler_calendar); + calendarRecycler.setLayoutManager(new LinearLayoutManager(getContext())); + + // Starter henting av kalenderdata + fetchCalendarEvents(calendarRecycler); + + // --------------------------------------------------------- + // 2. SETT OPP NYHETER (Hentes fra WordPress) + // --------------------------------------------------------- + RecyclerView newsRecycler = view.findViewById(R.id.recycler_news); + newsRecycler.setLayoutManager(new LinearLayoutManager(getContext())); + + // Gjør at scrollen flyter bedre inni NestedScrollView (hvis du bruker det i XML) + newsRecycler.setNestedScrollingEnabled(false); + // Start henting av ekte data + fetchNewsFromWordpress(newsRecycler); + } + + /** + * Henter kalenderhendelser fra WordPress via RetrofitClient + */ + private void fetchCalendarEvents(RecyclerView recyclerView) { + // 1. Hent API-tjenesten vår + WordPressApiService apiService = RetrofitClient.getApiService(); + // 2. Send forespørsel til nettet (Asynkront) + apiService.getCalendarEvents().enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (getContext() == null || response.body() == null) return; + + List rawEvents = response.body(); + List formattedEvents = new ArrayList<>(); + + // Formater for parsing og visning + SimpleDateFormat apiFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); + apiFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); // Fikser deprecation/tidssone + + SimpleDateFormat dayFormat = new SimpleDateFormat("dd", Locale.getDefault()); + dayFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); // Fikser deprecation/tidssone + + // Bruker norsk locale for måned (Jan, Feb, Mar, etc.) + SimpleDateFormat monthFormat = new SimpleDateFormat("MMM", new Locale("no", "NO")); + monthFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); // Fikser deprecation/tidssone + + SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm", Locale.getDefault()); + timeFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); // Fikser deprecation/tidssone + + for (CalendarEvent event : rawEvents) { + try { + // Bruker getRawDate() fra CalendarEvent.java (oppdatert) + Date date = apiFormat.parse(event.getRawDate()); + String day = dayFormat.format(date); + String month = monthFormat.format(date).toUpperCase(Locale.getDefault()); + String startTime = timeFormat.format(date); + + // Bruker den gamle konstruktøren for å sette formaterte data i adapteren + formattedEvents.add(new CalendarEvent( + event.getTitle(), + startTime, + day, + month + )); + } catch (ParseException e) { + e.printStackTrace(); + // Håndterer feil i parsing av dato/tid ved å vise rå data + formattedEvents.add(new CalendarEvent(event.getTitle(), "Ukjent", event.getDay(), event.getMonth())); + } + } + recyclerView.setAdapter(new CalendarAdapter(formattedEvents)); + } + + @Override + public void onFailure(Call> call, Throwable t) { + if (getContext() == null) return; + System.err.println("Kalender Nettverksfeil: " + t.getMessage()); + // Vis feilmelding i RecyclerView + List errorList = new ArrayList<>(); + errorList.add(new CalendarEvent("Kunne ikke laste kalender", "Sjekk nettverket ditt.", "00", "FEIL")); + recyclerView.setAdapter(new CalendarAdapter(errorList)); + } + }); + } + + /** + * Henter nyheter fra WordPress via RetrofitClient + */ + private void fetchNewsFromWordpress(RecyclerView recyclerView) { + // 1. Hent API-tjenesten vår + WordPressApiService apiService = RetrofitClient.getApiService(); + // 2. Send forespørsel til nettet (Asynkront) + apiService.getPosts().enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + // Sjekk om appen fortsatt lever (viktig for å unngå krasj) + if (getContext() == null) return; + + if (response.isSuccessful() && response.body() != null) { + // 3. Suksess! Vi fikk data fra WordPress. + List wpPosts = response.body(); + List newsList = new ArrayList<>(); + + // Datoformatering for nyhetene + SimpleDateFormat rawFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); + rawFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); // Fikser deprecation/tidssone + + SimpleDateFormat targetFormat = new SimpleDateFormat("dd. MMM yyyy", Locale.getDefault()); + targetFormat.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); // Fikser deprecation/tidssone + + // Konverter fra "WpPost" (API-format) til "NewsItem" (App-format) + for (WpPost post : wpPosts) { + String formattedDate = post.date; + + try { + // API-datoen (post.date) er i formatet "yyyy-MM-dd'T'HH:mm:ss" + Date date = rawFormat.parse(post.date); + formattedDate = targetFormat.format(date); + } catch (ParseException e) { + System.err.println("Feil ved parsing av nyhetsdato: " + e.getMessage()); + } + + newsList.add(new NewsItem( + post.getTitleStr(), + post.getExcerptStr(), + "Publisert: " + formattedDate + )); + } + + // 4. Send listen til Adapteren slik at den vises på skjermen + NewsAdapter adapter = new NewsAdapter(newsList); + recyclerView.setAdapter(adapter); + } else { + System.err.println("Feil: Fikk svar, men noe var galt med dataene: " + response.code()); + // Her kunne vi vist en "Ingen nyheter"-tekst + // (Løsningen har allerede lagt inn fallback i onFailure) + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + // Nettverksfeil (Ingen nett, feil URL, etc) + if (getContext() == null) return; + System.err.println("Nettverksfeil: " + t.getMessage()); + // Legg til en "Feilmelding" i listen så brukeren ser det + List errorList = new ArrayList<>(); + errorList.add(new NewsItem("Kunne ikke laste nyheter", "Sjekk nettverket ditt.", "System")); + recyclerView.setAdapter(new NewsAdapter(errorList)); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/LoginFragment.java b/app/src/main/java/com/kbs/kbsintranett/LoginFragment.java new file mode 100644 index 0000000..df665c5 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/LoginFragment.java @@ -0,0 +1,114 @@ +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 signInLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + Task 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 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 --- + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/LoginRequest.java b/app/src/main/java/com/kbs/kbsintranett/LoginRequest.java new file mode 100644 index 0000000..dfccb8b --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/LoginRequest.java @@ -0,0 +1,9 @@ +package com.kbs.kbsintranett; + +public class LoginRequest { + public String token; + + public LoginRequest(String token) { + this.token = token; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/LoginResponse.java b/app/src/main/java/com/kbs/kbsintranett/LoginResponse.java new file mode 100644 index 0000000..cfbda46 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/LoginResponse.java @@ -0,0 +1,32 @@ +// 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; +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/MainActivity.java b/app/src/main/java/com/kbs/kbsintranett/MainActivity.java new file mode 100644 index 0000000..cfd03b1 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/MainActivity.java @@ -0,0 +1,115 @@ +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 = "738325360287-cidl3plnqv9ei74vm9vm5muustj6eenb.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); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/NewsAdapter.java b/app/src/main/java/com/kbs/kbsintranett/NewsAdapter.java new file mode 100644 index 0000000..767ad75 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/NewsAdapter.java @@ -0,0 +1,48 @@ +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 NewsAdapter extends RecyclerView.Adapter { + + private List newsList; + + public NewsAdapter(List newsList) { + this.newsList = newsList; + } + + @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) { + NewsItem item = newsList.get(position); + holder.title.setText(item.getTitle()); + holder.excerpt.setText(item.getExcerpt()); + holder.author.setText("Av: " + item.getAuthor()); + } + + @Override + public int getItemCount() { + return newsList.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + TextView title, excerpt, author; + public ViewHolder(View view) { + super(view); + title = view.findViewById(R.id.news_title); + excerpt = view.findViewById(R.id.news_excerpt); + author = view.findViewById(R.id.news_author); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/NewsItem.java b/app/src/main/java/com/kbs/kbsintranett/NewsItem.java new file mode 100644 index 0000000..f34d7bd --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/NewsItem.java @@ -0,0 +1,17 @@ +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; } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/ProfileFragment.java b/app/src/main/java/com/kbs/kbsintranett/ProfileFragment.java new file mode 100644 index 0000000..a43ca41 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/ProfileFragment.java @@ -0,0 +1,85 @@ +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); + + // 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 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(); + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/RetrofitClient.java b/app/src/main/java/com/kbs/kbsintranett/RetrofitClient.java new file mode 100644 index 0000000..c75de16 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/RetrofitClient.java @@ -0,0 +1,66 @@ +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 retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class RetrofitClient { + private static final String BASE_URL = "https://intranet.kbs.no/"; + + // VI FJERNER FAKE_COOKIE HERFRA! Den trengs ikke lenger. + + private static Retrofit retrofit = null; + + public static WordPressApiService getApiService() { + // Vi må bygge klienten på nytt hvis vi logger ut/inn, men for enkelhets skyld + // sjekker vi bare null her. + if (retrofit == null) { + + OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(new Interceptor() { + @Override + public Response intercept(Chain chain) throws IOException { + Request originalRequest = chain.request(); + Request.Builder builder = originalRequest.newBuilder(); + + // 1. Hent cookie fra UserManager + String dynamicCookie = UserManager.getInstance().getCookie(); + + // 2. Hvis vi har en cookie, legg den til i headeren + if (dynamicCookie != null && !dynamicCookie.isEmpty()) { + builder.header("Cookie", dynamicCookie); + } + + return chain.proceed(builder.build()); + } + }) + .build(); + + Gson gson = new GsonBuilder() + .registerTypeAdapter(new TypeToken>(){}.getType(), new ChoicesAdapter()) + .create(); + + retrofit = new Retrofit.Builder() + .baseUrl(BASE_URL) + .client(client) + .addConverterFactory(GsonConverterFactory.create(gson)) + .build(); + } + return retrofit.create(WordPressApiService.class); + } + + // Hjelpemetode for å nullstille Retrofit ved utlogging + public static void clearClient() { + retrofit = null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/UserManager.java b/app/src/main/java/com/kbs/kbsintranett/UserManager.java new file mode 100644 index 0000000..903e0d3 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/UserManager.java @@ -0,0 +1,121 @@ +// 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; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java b/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java new file mode 100644 index 0000000..ca274b5 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/WordPressApiService.java @@ -0,0 +1,60 @@ +// FILSTI: app\src\main\java\com\kbs\kbsintranett\WordPressApiService.java +package com.kbs.kbsintranett; + +import com.google.gson.JsonElement; +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; // NYTT + +public interface WordPressApiService { + // 1. Hent nyheter + @GET("wp-json/wp/v2/posts?per_page=5") + Call> getPosts(); + + // 2. Hent et spesifikt skjema med ID + @GET("wp-json/gf/v2/forms/{id}") + Call getForm(@Path("id") int formId); + + // 3. SEND INN SKJEMA (JSON-data uten filer) + @POST("wp-json/gf/v2/forms/{id}/submissions") + Call submitForm(@Path("id") int formId, @Body FormSubmission submission); + + // 4. LOGIN MED GOOGLE + @POST("wp-json/kbs/v1/login") + Call googleLogin(@Body LoginRequest request); + + // 5. HENT LISTE AV SKJEMAER + @GET("wp-json/gf/v2/forms") + Call> getFormsListMap(); + + // 6. SEND INN SKJEMA (MULTIPART - for filopplasting) + @Multipart + @POST("wp-json/gf/v2/forms/{id}/submissions") + Call submitMultipartForm( + @Path("id") int formId, + @PartMap Map textFields, + @Part List files + ); + + // 7. HENT KALENDERHENDELSER + @GET("wp-json/kbs/v1/calendar/events") + Call> getCalendarEvents(); + + // 8. HENT INNSENDINGER (Entries) - NYTT + @GET("wp-json/gf/v2/entries") + Call getEntries( + @Query("form_ids") int formId, + @Query("search") String searchJson, + @Query("paging[page_size]") int pageSize + ); +} \ No newline at end of file diff --git a/app/src/main/java/com/kbs/kbsintranett/WpPost.java b/app/src/main/java/com/kbs/kbsintranett/WpPost.java new file mode 100644 index 0000000..d7504e6 --- /dev/null +++ b/app/src/main/java/com/kbs/kbsintranett/WpPost.java @@ -0,0 +1,31 @@ +package com.kbs.kbsintranett; + +import com.google.gson.annotations.SerializedName; + +public class WpPost { + // WordPress sender tittelen som et objekt: "title": { "rendered": "Overskrift" } + @SerializedName("title") + public Rendered title; + + @SerializedName("excerpt") + public Rendered excerpt; + + @SerializedName("date") + public String date; + + // Hjelpeklasse for å hente ut teksten inni "rendered" + public static class Rendered { + @SerializedName("rendered") + public String renderedString; + } + + // En hjelpemetode for å få ren tekst ut (fjerner HTML-koder hvis nødvendig) + public String getTitleStr() { + return title != null ? title.renderedString : "Uten tittel"; + } + + public String getExcerptStr() { + // En enkel rensing av HTML-tags (f.eks

) + return excerpt != null ? android.text.Html.fromHtml(excerpt.renderedString, android.text.Html.FROM_HTML_MODE_COMPACT).toString() : ""; + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_book.xml b/app/src/main/res/drawable/ic_book.xml new file mode 100644 index 0000000..2f548a2 --- /dev/null +++ b/app/src/main/res/drawable/ic_book.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_form.xml b/app/src/main/res/drawable/ic_form.xml new file mode 100644 index 0000000..4014ac5 --- /dev/null +++ b/app/src/main/res/drawable/ic_form.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_home.xml b/app/src/main/res/drawable/ic_home.xml new file mode 100644 index 0000000..20cb4d6 --- /dev/null +++ b/app/src/main/res/drawable/ic_home.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..ca3826a --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..42acff3 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,33 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_forms.xml b/app/src/main/res/layout/fragment_forms.xml new file mode 100644 index 0000000..cf5a7b2 --- /dev/null +++ b/app/src/main/res/layout/fragment_forms.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_handbook.xml b/app/src/main/res/layout/fragment_handbook.xml new file mode 100644 index 0000000..e6fda5a --- /dev/null +++ b/app/src/main/res/layout/fragment_handbook.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml new file mode 100644 index 0000000..76853e9 --- /dev/null +++ b/app/src/main/res/layout/fragment_home.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml new file mode 100644 index 0000000..ed770d6 --- /dev/null +++ b/app/src/main/res/layout/fragment_login.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml new file mode 100644 index 0000000..2787143 --- /dev/null +++ b/app/src/main/res/layout/fragment_profile.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + +