Første commit – initial backup
15
.gitignore
vendored
Normal file
|
|
@ -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
|
||||
3
.idea/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
1
.idea/.name
Normal file
|
|
@ -0,0 +1 @@
|
|||
KBS Intranett
|
||||
6
.idea/AndroidProjectSystem.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AndroidProjectSystem">
|
||||
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||
</component>
|
||||
</project>
|
||||
18
.idea/appInsightsSettings.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AppInsightsSettings">
|
||||
<option name="tabSettings">
|
||||
<map>
|
||||
<entry key="Firebase Crashlytics">
|
||||
<value>
|
||||
<InsightsFilterSettings>
|
||||
<option name="signal" value="SIGNAL_UNSPECIFIED" />
|
||||
<option name="timeIntervalDays" value="THIRTY_DAYS" />
|
||||
<option name="visibilityType" value="ALL" />
|
||||
</InsightsFilterSettings>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/compiler.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="21" />
|
||||
</component>
|
||||
</project>
|
||||
10
.idea/deploymentTargetSelector.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetSelector">
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
||||
13
.idea/deviceManager.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DeviceTable">
|
||||
<option name="columnSorters">
|
||||
<list>
|
||||
<ColumnSorterState>
|
||||
<option name="column" value="Name" />
|
||||
<option name="order" value="ASCENDING" />
|
||||
</ColumnSorterState>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
19
.idea/gradle.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
10
.idea/migrations.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectMigrations">
|
||||
<option name="MigrateToGradleLocalJavaHome">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
9
.idea/misc.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
17
.idea/runConfigurations.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
1
app/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
57
app/build.gradle.kts
Normal file
|
|
@ -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")
|
||||
}
|
||||
21
app/proguard-rules.pro
vendored
Normal file
|
|
@ -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
|
||||
|
|
@ -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 <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());
|
||||
}
|
||||
}
|
||||
44
app/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.kbs.kbsintranett">
|
||||
|
||||
<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" />
|
||||
|
||||
<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>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
BIN
app/src/main/ic_launcher-playstore.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
72
app/src/main/java/com/kbs/kbsintranett/AuthRepository.java
Normal file
|
|
@ -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<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());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
51
app/src/main/java/com/kbs/kbsintranett/CalendarAdapter.java
Normal file
|
|
@ -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<CalendarAdapter.ViewHolder> { // [cite: 31]
|
||||
|
||||
private List<CalendarEvent> events;
|
||||
public CalendarAdapter(List<CalendarEvent> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
30
app/src/main/java/com/kbs/kbsintranett/CalendarEvent.java
Normal file
|
|
@ -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
|
||||
}
|
||||
31
app/src/main/java/com/kbs/kbsintranett/ChoicesAdapter.java
Normal file
|
|
@ -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<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<>();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<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;
|
||||
}
|
||||
}
|
||||
14
app/src/main/java/com/kbs/kbsintranett/FormSubmission.java
Normal file
|
|
@ -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<String, String> inputValues;
|
||||
|
||||
public FormSubmission(Map<String, String> inputValues) {
|
||||
this.inputValues = inputValues;
|
||||
}
|
||||
}
|
||||
541
app/src/main/java/com/kbs/kbsintranett/FormsFragment.java
Normal file
|
|
@ -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<String, EditText> dynamicFields = new HashMap<>();
|
||||
private Map<String, Boolean> 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<GravityForm>() {
|
||||
@Override
|
||||
public void onResponse(retrofit2.Call<GravityForm> call, retrofit2.Response<GravityForm> 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<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();
|
||||
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<GravityField> inputs = new ArrayList<>(parentField.inputs);
|
||||
|
||||
// Sortering for adresse
|
||||
if (parentField.type.equals("address")) {
|
||||
Collections.sort(inputs, new Comparator<GravityField>() {
|
||||
@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<String, EditText> 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<String, EditText> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
140
app/src/main/java/com/kbs/kbsintranett/FormsListFragment.java
Normal file
|
|
@ -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<Map<String, GravityForm>>() {
|
||||
@Override
|
||||
public void onResponse(Call<Map<String, GravityForm>> call, Response<Map<String, GravityForm>> 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<String, GravityForm> 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<String, GravityForm> sortedMap = new TreeMap<>(formMap);
|
||||
|
||||
for (Map.Entry<String, GravityForm> 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<Map<String, GravityForm>> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<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" }
|
||||
}
|
||||
82
app/src/main/java/com/kbs/kbsintranett/GravityField.java
Normal file
|
|
@ -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<Choice> choices;
|
||||
|
||||
@SerializedName("content")
|
||||
public String content;
|
||||
|
||||
@SerializedName("inputs")
|
||||
public List<GravityField> 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<String, String> 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<Rule> rules;
|
||||
}
|
||||
|
||||
public static class Rule {
|
||||
@SerializedName("fieldId")
|
||||
public String fieldId;
|
||||
|
||||
@SerializedName("operator")
|
||||
public String operator; // "is", "isnot", etc.
|
||||
|
||||
@SerializedName("value")
|
||||
public String value;
|
||||
}
|
||||
}
|
||||
18
app/src/main/java/com/kbs/kbsintranett/GravityForm.java
Normal file
|
|
@ -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<GravityField> fields; // En liste med alle feltene i skjemaet
|
||||
}
|
||||
12
app/src/main/java/com/kbs/kbsintranett/HandbookFragment.java
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
202
app/src/main/java/com/kbs/kbsintranett/HomeFragment.java
Normal file
|
|
@ -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<List<CalendarEvent>>() {
|
||||
@Override
|
||||
public void onResponse(Call<List<CalendarEvent>> call, Response<List<CalendarEvent>> response) {
|
||||
if (getContext() == null || response.body() == null) return;
|
||||
|
||||
List<CalendarEvent> rawEvents = response.body();
|
||||
List<CalendarEvent> 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<List<CalendarEvent>> call, Throwable t) {
|
||||
if (getContext() == null) return;
|
||||
System.err.println("Kalender Nettverksfeil: " + t.getMessage());
|
||||
// Vis feilmelding i RecyclerView
|
||||
List<CalendarEvent> 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<List<WpPost>>() {
|
||||
@Override
|
||||
public void onResponse(Call<List<WpPost>> call, Response<List<WpPost>> response) {
|
||||
// Sjekk om appen fortsatt lever (viktig for å unngå krasj)
|
||||
if (getContext() == null) return;
|
||||
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
// 3. Suksess! Vi fikk data fra WordPress.
|
||||
List<WpPost> wpPosts = response.body();
|
||||
List<NewsItem> 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<List<WpPost>> 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<NewsItem> errorList = new ArrayList<>();
|
||||
errorList.add(new NewsItem("Kunne ikke laste nyheter", "Sjekk nettverket ditt.", "System"));
|
||||
recyclerView.setAdapter(new NewsAdapter(errorList));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
114
app/src/main/java/com/kbs/kbsintranett/LoginFragment.java
Normal file
|
|
@ -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<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 ---
|
||||
}
|
||||
}
|
||||
}
|
||||
9
app/src/main/java/com/kbs/kbsintranett/LoginRequest.java
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package com.kbs.kbsintranett;
|
||||
|
||||
public class LoginRequest {
|
||||
public String token;
|
||||
|
||||
public LoginRequest(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
}
|
||||
32
app/src/main/java/com/kbs/kbsintranett/LoginResponse.java
Normal file
|
|
@ -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;
|
||||
}
|
||||
115
app/src/main/java/com/kbs/kbsintranett/MainActivity.java
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
48
app/src/main/java/com/kbs/kbsintranett/NewsAdapter.java
Normal file
|
|
@ -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<NewsAdapter.ViewHolder> {
|
||||
|
||||
private List<NewsItem> newsList;
|
||||
|
||||
public NewsAdapter(List<NewsItem> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
app/src/main/java/com/kbs/kbsintranett/NewsItem.java
Normal file
|
|
@ -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; }
|
||||
}
|
||||
85
app/src/main/java/com/kbs/kbsintranett/ProfileFragment.java
Normal file
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
66
app/src/main/java/com/kbs/kbsintranett/RetrofitClient.java
Normal file
|
|
@ -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<List<GravityField.Choice>>(){}.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;
|
||||
}
|
||||
}
|
||||
121
app/src/main/java/com/kbs/kbsintranett/UserManager.java
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<List<WpPost>> getPosts();
|
||||
|
||||
// 2. Hent et spesifikt skjema med ID
|
||||
@GET("wp-json/gf/v2/forms/{id}")
|
||||
Call<GravityForm> getForm(@Path("id") int formId);
|
||||
|
||||
// 3. SEND INN SKJEMA (JSON-data uten filer)
|
||||
@POST("wp-json/gf/v2/forms/{id}/submissions")
|
||||
Call<JsonElement> submitForm(@Path("id") int formId, @Body FormSubmission submission);
|
||||
|
||||
// 4. LOGIN MED GOOGLE
|
||||
@POST("wp-json/kbs/v1/login")
|
||||
Call<LoginResponse> googleLogin(@Body LoginRequest request);
|
||||
|
||||
// 5. HENT LISTE AV SKJEMAER
|
||||
@GET("wp-json/gf/v2/forms")
|
||||
Call<Map<String, GravityForm>> getFormsListMap();
|
||||
|
||||
// 6. SEND INN SKJEMA (MULTIPART - for filopplasting)
|
||||
@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
|
||||
);
|
||||
|
||||
// 7. HENT KALENDERHENDELSER
|
||||
@GET("wp-json/kbs/v1/calendar/events")
|
||||
Call<List<CalendarEvent>> getCalendarEvents();
|
||||
|
||||
// 8. HENT INNSENDINGER (Entries) - NYTT
|
||||
@GET("wp-json/gf/v2/entries")
|
||||
Call<GravityEntryResponse> getEntries(
|
||||
@Query("form_ids") int formId,
|
||||
@Query("search") String searchJson,
|
||||
@Query("paging[page_size]") int pageSize
|
||||
);
|
||||
}
|
||||
31
app/src/main/java/com/kbs/kbsintranett/WpPost.java
Normal file
|
|
@ -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 <p>)
|
||||
return excerpt != null ? android.text.Html.fromHtml(excerpt.renderedString, android.text.Html.FROM_HTML_MODE_COMPACT).toString() : "";
|
||||
}
|
||||
}
|
||||
5
app/src/main/res/drawable/ic_book.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<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>
|
||||
5
app/src/main/res/drawable/ic_form.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<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>
|
||||
5
app/src/main/res/drawable/ic_home.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<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>
|
||||
74
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?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>
|
||||
30
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<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>
|
||||
33
app/src/main/res/layout/activity_main.xml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?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>
|
||||
74
app/src/main/res/layout/fragment_forms.xml
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp"
|
||||
android:background="@android:color/white">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/main_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Skjema"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loading_spinner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/lbl_history"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Tidligere innsendinger:"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="5dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/historyContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="20dp"/>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:background="#0069B3"
|
||||
android:layout_marginBottom="20dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ny innsending:"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="10dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/form_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txt_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:textColor="#D32F2F"
|
||||
android:gravity="center" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
11
app/src/main/res/layout/fragment_handbook.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Håndbok"
|
||||
android:layout_gravity="center"
|
||||
android:textSize="24sp"/>
|
||||
</FrameLayout>
|
||||
66
app/src/main/res/layout/fragment_home.xml
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?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>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Kommende hendelser"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/black"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginStart="8dp"/>
|
||||
|
||||
<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"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Siste nytt"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/black"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginStart="8dp"/>
|
||||
|
||||
<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>
|
||||
41
app/src/main/res/layout/fragment_login.xml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?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>
|
||||
87
app/src/main/res/layout/fragment_profile.xml
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<?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="48dp"/>
|
||||
|
||||
<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>
|
||||
69
app/src/main/res/layout/item_calendar.xml
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?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>
|
||||
44
app/src/main/res/layout/item_news.xml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?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="16dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/news_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Nyhets overskrift"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@android:color/black"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/news_excerpt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Her kommer ingressen..."
|
||||
android:textColor="@android:color/darker_gray"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/news_author"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="Skrevet av: Ole"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="italic"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
17
app/src/main/res/menu/bottom_nav_menu.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?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>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?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>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?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>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
65
app/src/main/res/navigation/mobile_navigation.xml
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<?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" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_forms"
|
||||
android:name="com.kbs.kbsintranett.FormsListFragment"
|
||||
android:label="Skjemaer"
|
||||
tools:layout="@layout/fragment_forms">
|
||||
<action
|
||||
android:id="@+id/action_formsListFragment_to_formsDetailFragment"
|
||||
app:destination="@id/navigation_forms_detail" />
|
||||
</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" />
|
||||
|
||||
<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" />
|
||||
</fragment>
|
||||
|
||||
</navigation>
|
||||
10
app/src/main/res/values-night/themes.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<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>
|
||||
13
app/src/main/res/values/colors.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?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>
|
||||
4
app/src/main/res/values/ic_launcher_background.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
||||
3
app/src/main/res/values/strings.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">KBS Intranett</string>
|
||||
</resources>
|
||||
10
app/src/main/res/values/themes.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<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>
|
||||
13
app/src/main/res/xml/backup_rules.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?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>
|
||||
19
app/src/main/res/xml/data_extraction_rules.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?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>
|
||||
17
app/src/test/java/com/kbs/kbsintranett/ExampleUnitTest.java
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
4
build.gradle.kts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
}
|
||||
21
gradle.properties
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. For more details, visit
|
||||
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
22
gradle/libs.versions.toml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
[versions]
|
||||
agp = "8.13.1"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.1.5"
|
||||
espressoCore = "3.5.1"
|
||||
appcompat = "1.6.1"
|
||||
material = "1.10.0"
|
||||
activity = "1.8.0"
|
||||
constraintlayout = "2.1.4"
|
||||
|
||||
[libraries]
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||
activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
|
||||
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
8
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#Fri Nov 28 13:26:23 CET 2025
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
251
gradlew
vendored
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
94
gradlew.bat
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
3060
hele_prosjektet.txt
Normal file
62
samle_kode.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import os
|
||||
|
||||
# --- INNSTILLINGER ---
|
||||
# Filtyper vi vil ta med (legg til flere hvis du trenger)
|
||||
FILTYPER = ['.kt', '.java', '.xml', '.gradle', '.kts', '.pro']
|
||||
|
||||
# Mapper vi skal ignorere (viktig for å unngå tusenvis av autogenererte filer)
|
||||
IGNORER_MAPPER = {
|
||||
'build', '.gradle', '.idea', '.git', 'captures', 'cxx',
|
||||
'generated', 'intermediates', 'outputs', 'tmp'
|
||||
}
|
||||
|
||||
OUTPUT_FILNAVN = 'hele_prosjektet.txt'
|
||||
|
||||
def samle_filer():
|
||||
# Finner mappen scriptet kjøres fra
|
||||
start_mappe = os.getcwd()
|
||||
|
||||
print(f"Starter skanning i: {start_mappe}")
|
||||
print(f"Lagrer til: {OUTPUT_FILNAVN} ...")
|
||||
|
||||
teller = 0
|
||||
|
||||
with open(OUTPUT_FILNAVN, 'w', encoding='utf-8') as ut_fil:
|
||||
# Skriv en liten header til Gemini
|
||||
ut_fil.write("Dette er kildekoden til et Android Studio-prosjekt.\n")
|
||||
ut_fil.write("Hver fil er separert med overskrifter.\n\n")
|
||||
|
||||
# Gå gjennom alle mapper og filer
|
||||
for root, dirs, files in os.walk(start_mappe):
|
||||
# Fjern mapper vi vil ignorere slik at skriptet ikke går inn i dem
|
||||
dirs[:] = [d for d in dirs if d not in IGNORER_MAPPER]
|
||||
|
||||
for fil in files:
|
||||
if any(fil.endswith(ext) for ext in FILTYPER):
|
||||
# Hopp over selve output-filen og script-filen
|
||||
if fil == OUTPUT_FILNAVN or fil == os.path.basename(__file__):
|
||||
continue
|
||||
|
||||
full_sti = os.path.join(root, fil)
|
||||
relativ_sti = os.path.relpath(full_sti, start_mappe)
|
||||
|
||||
try:
|
||||
with open(full_sti, 'r', encoding='utf-8') as inn_fil:
|
||||
innhold = inn_fil.read()
|
||||
|
||||
# Lag en tydelig separator som Gemini forstår
|
||||
ut_fil.write(f"\n{'='*60}\n")
|
||||
ut_fil.write(f"FILSTI: {relativ_sti}\n")
|
||||
ut_fil.write(f"{'='*60}\n")
|
||||
ut_fil.write(innhold + "\n")
|
||||
|
||||
print(f"La til: {relativ_sti}")
|
||||
teller += 1
|
||||
except Exception as e:
|
||||
print(f"Kunne ikke lese {relativ_sti}: {e}")
|
||||
|
||||
print(f"\nFerdig! Samlet {teller} filer i '{OUTPUT_FILNAVN}'.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
samle_filer()
|
||||
input("\nTrykk Enter for å avslutte...")
|
||||
24
settings.gradle.kts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
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")
|
||||
|
||||