Compare commits

...

31 Commits

Author SHA1 Message Date
WallyHackenslacker c1bcfbebbd fix search URL encoding crash on Android 2026-05-07 01:10:19 -04:00
WallyHackenslacker 6b23b57415 update app config and sync behavior for release testing 2026-05-07 01:04:46 -04:00
Herbert Reiter 2fccc8b985 New release 2026-01-02 21:27:49 +01:00
Herbert Reiter e0dd33a1db Update copyright year 2026-01-02 21:22:56 +01:00
Herbert Reiter 0a65c17e4e New release 2026-01-02 21:20:50 +01:00
Herbert Reiter 6bb9b97e91 Upgrade library dependencies 2026-01-02 21:20:27 +01:00
Herbert Reiter 5ff42b83e8 Upgrade to moasdawiki-server 3.9.7 2026-01-02 21:20:02 +01:00
Herbert Reiter b944ce9e93 Upgrade to Java 17 2026-01-02 21:19:24 +01:00
Herbert Reiter 947317c6a7 Upgrade library dependencies 2026-01-02 21:18:29 +01:00
Herbert Reiter 588b132853 Use back gesture for browser back 2026-01-02 21:17:59 +01:00
Herbert Reiter 3a7b9cae32 Retry Google deployment 2025-08-30 21:01:24 +02:00
Herbert Reiter a4590e4b35 Neues Release 2025-08-30 16:28:39 +02:00
Herbert Reiter c6f942b6df Shortened app name 2025-08-30 15:46:47 +02:00
Herbert Reiter d11478d92c Update minimum supported Android API to 33 2025-08-30 15:36:20 +02:00
Herbert Reiter 016a20dca8 Upgrade library dependencies 2025-08-30 15:35:22 +02:00
Herbert Reiter afe3d5712d Bugfix: Searchbar not visible on newer Android versions 2025-08-30 15:30:51 +02:00
Herbert Reiter d48f50c15a Use Nullable annotations from Android package 2025-08-30 15:30:18 +02:00
Herbert Reiter 009c1e5131 New release 2025-07-06 20:31:20 +02:00
Herbert Reiter 3839ad430c Upgrade library dependencies 2025-07-06 20:28:44 +02:00
Herbert Reiter 5b60b46676 Update Gradle 2025-07-06 20:28:18 +02:00
Herbert Reiter cfe89ec716 Fix warning 2025-07-06 20:28:01 +02:00
Herbert Reiter 766326d170 Update to moasdawiki-server 3.9.5 2025-07-06 20:11:20 +02:00
Herbert Reiter 9d281ebde9 Upgrade to Android API 36 (Android 16) 2025-07-06 20:10:27 +02:00
Herbert Reiter 8959fdaa99 Show Gradle warnings 2025-07-06 20:09:02 +02:00
Herbert Reiter fbed0a15dc Upgrade to Gradle 8.14.3 2025-07-06 20:08:01 +02:00
Herbert Reiter 562d3662da New release 2025-01-05 22:02:02 +01:00
Herbert Reiter c97656a5d0 Update to moasdawiki-server 3.9.3 2025-01-05 21:59:04 +01:00
Herbert Reiter 1b5f7918ce Upgrade library dependencies 2025-01-05 21:58:19 +01:00
Herbert Reiter 49e575e462 Update copyright year 2025-01-05 21:57:45 +01:00
Herbert Reiter ddc5cea6bd Upgrade to Gradle 8.12 2025-01-05 21:57:23 +01:00
Herbert Reiter 52ecbeb7d7 Add Google Play link 2024-11-02 18:39:45 +01:00
36 changed files with 481 additions and 141 deletions
+173
View File
@@ -0,0 +1,173 @@
# AGENTS.md
Guidance for agentic coding tools working in `moasdawiki-app`.
## Project Snapshot
- Android app module: `:app`
- Language: Java 17 (no Kotlin sources currently)
- Build system: Gradle wrapper (`./gradlew`) + Android Gradle Plugin
- Main package: `net.moasdawiki.app`
- App depends on MoasdaWiki server module artifact: `net.moasdawiki:moasdawiki-server`
## Environment And Baseline
- Use the wrapper, not a globally installed Gradle: `./gradlew ...`
- Run commands from repository root.
- Keep changes scoped; do not refactor unrelated files.
- Preserve license headers in Java files.
- Prefer additive, minimal-risk edits.
## Build / Lint / Test Commands
### Common Lifecycle Commands
- Clean: `./gradlew clean`
- Build everything (assemble + checks): `./gradlew :app:build`
- Assemble debug APK: `./gradlew :app:assembleDebug`
- Assemble release APK: `./gradlew :app:assembleRelease`
- Install debug on connected device: `./gradlew :app:installDebug`
### Lint Commands
- Run lint (default variant): `./gradlew :app:lint`
- Run debug lint only: `./gradlew :app:lintDebug`
- Run release lint only: `./gradlew :app:lintRelease`
- Apply safe lint fixes: `./gradlew :app:lintFix`
### Unit Test Commands
- Run all unit tests: `./gradlew :app:test`
- Run debug unit tests: `./gradlew :app:testDebugUnitTest`
- Run release unit tests: `./gradlew :app:testReleaseUnitTest`
### Run A Single Unit Test (Important)
- Single test class:
`./gradlew :app:testDebugUnitTest --tests "net.moasdawiki.app.YourTestClass"`
- Single test method:
`./gradlew :app:testDebugUnitTest --tests "net.moasdawiki.app.YourTestClass.yourTestMethod"`
- Wildcard match:
`./gradlew :app:testDebugUnitTest --tests "*YourTestClass*"`
Notes:
- Use fully-qualified class names for reliable filtering.
- If the filter reports no tests found, verify package + method names.
- Keep `Debug` test task unless you specifically need release behavior.
### Instrumentation Test Commands
- Run all connected instrumentation tests:
`./gradlew :app:connectedDebugAndroidTest`
- Run one instrumentation class:
`./gradlew :app:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=net.moasdawiki.app.YourAndroidTestClass`
- Run one instrumentation method:
`./gradlew :app:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=net.moasdawiki.app.YourAndroidTestClass#yourTestMethod`
## High-Value Working Agreement
- Before editing, inspect nearby code and follow existing patterns.
- After editing, run the narrowest meaningful verification first.
- For behavior changes, prefer adding/updating tests when test infra exists.
- Do not introduce new build tools or formatting frameworks unless asked.
## Source Layout (Current)
- App code: `app/src/main/java/net/moasdawiki/app/`
- Resources: `app/src/main/res/`
- Manifest: `app/src/main/AndroidManifest.xml`
- Gradle module config: `app/build.gradle`
- Root build config: `build.gradle`, `settings.gradle`, `gradle.properties`
There are currently no `app/src/test` or `app/src/androidTest` files in this repository snapshot.
## Java Style Conventions To Follow
### Formatting
- 4-space indentation, no tabs.
- Opening brace on same line for classes/methods/control blocks.
- Keep methods reasonably focused; avoid large unrelated rewrites.
- Prefer one statement per line for readability.
- Preserve existing block comments and Javadoc style.
### Imports
- Use explicit imports; avoid wildcard imports.
- Group imports with blank lines by domain:
1) `android.*`
2) `androidx.*`
3) `net.moasdawiki.*`
4) `java.*`
- Keep import order stable and consistent within each group.
### Types And Nullability
- Use concrete types unless abstraction improves clarity.
- Follow existing nullability annotations (`@NonNull`, `@Nullable`).
- Annotate parameters/returns where null-safety is non-obvious.
- Prefer immutable locals/fields (`final`) where practical.
- Use boxed types only when null is a real state.
### Naming
- Classes: `PascalCase` (`MainActivity`, `SynchronizeWikiClient`).
- Methods/fields/local variables: `camelCase`.
- Constants: `UPPER_SNAKE_CASE`.
- Android log tags: short static constant named `TAG`.
- Preference keys/constants belong in `Constants`-style central locations.
### Control Flow And Readability
- Prefer early returns for invalid preconditions.
- Keep nesting shallow when possible.
- Extract helper methods for repeated logic.
- Use descriptive method names that reflect side effects.
### Error Handling And Logging
- Catch specific exceptions whenever practical.
- Log with Android `Log` at appropriate level (`d`, `i`, `w`, `e`).
- Include exception object when logging failures.
- Convert low-level failures into domain-friendly outcomes for callers.
- For user-visible failures, pair logs with UI feedback (`Toast`/status text).
- Avoid swallowing exceptions silently.
### Threading / Android Behavior
- Keep network and disk work off the UI thread.
- UI updates must run on UI thread (`runOnUiThread(...)` pattern).
- Continue using executor-based async style already present.
- Preserve lifecycle-safe behavior in `Activity`/`Fragment` methods.
## Testing Expectations For Agents
- If you change pure logic, add or update a unit test.
- If you change Android component behavior, prefer instrumentation tests when feasible.
- For low-risk refactors, run at least targeted lint + targeted tests.
- For broader edits, run `:app:lint` and `:app:testDebugUnitTest`.
## Dependency And Build File Edits
- Keep dependency changes minimal and justified.
- Match existing dependency declaration style in touched file.
- Do not upgrade AGP/Gradle/SDK versions unless explicitly requested.
- Call out deprecation warnings you encounter during Gradle runs.
## Commit And PR Hygiene (For Agents)
- Make small, reviewable commits with clear intent.
- In commit messages, explain why the change is needed.
- Include verification commands run and their outcome in PR notes.
- Do not include unrelated formatting churn.
## Cursor/Copilot Rule Ingestion
Checked in this repository:
- `.cursorrules`: not present
- `.cursor/rules/`: not present
- `.github/copilot-instructions.md`: not present
If these files are added later, agents should treat them as higher-priority repository instructions and merge them into this guidance.
+31
View File
@@ -1,5 +1,36 @@
# Changelog # Changelog
## 3.9.7.0 (versionCode 50, 2026-01-02)
- Bugfix: Use back gesture for browser back
- Upgrade to moasdawiki-server 3.9.7
- Upgrade to Java 17
- Upgrade library dependencies
- Update copyright year
## 3.9.5.1 (versionCode 48 + 49, 2025-08-30)
- Bugfix: Searchbar not visible on newer Android versions
- Shortened app name
- Update minimum supported Android API to 33 (Android 13)
- Use Nullable annotations from Android package
- Upgrade library dependencies
## 3.9.5.0 (versionCode 47, 2025-07-06)
- Update to moasdawiki-server 3.9.5
- Upgrade to Android API 36 (Android 16)
- Upgrade to Gradle 8.14.3
- Upgrade library dependencies
- Fix warning
## 3.9.3.0 (versionCode 46, 2025-01-05)
- Update to moasdawiki-server 3.9.3
- Upgrade to Gradle 8.12
- Upgrade library dependencies
- Update copyright year
## 3.9.1.1 (versionCode 45, 2024-11-02) ## 3.9.1.1 (versionCode 45, 2024-11-02)
- Update App sync description - Update App sync description
+1
View File
@@ -12,6 +12,7 @@ For MoasdaWiki documentation see https://moasdawiki.net/.
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" [<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid" alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/net.moasdawiki.app) height="80">](https://f-droid.org/packages/net.moasdawiki.app)
or [Google Play](https://play.google.com/store/apps/details?id=net.moasdawiki.app)
## Features ## Features
+20 -19
View File
@@ -1,25 +1,25 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
namespace "net.moasdawiki.app" namespace = "net.moasdawiki.app"
compileSdk 34 // 34 = Android 14 compileSdk = 36 // 36 = Android 16 Baklava
defaultConfig { defaultConfig {
applicationId "net.moasdawiki.app" applicationId "space.hackenslacker.moasdawiki.app"
minSdk 28 // 28 = Android 9 minSdk = 29 // 29 = Android 10 Q
targetSdk 34 // should be same as compileSdk targetSdk = 36 // should be same as compileSdk
versionCode 45 versionCode = 50
versionName "3.9.1.1" versionName = "3.9.7.0"
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_11 sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_17
} }
buildFeatures { buildFeatures {
buildConfig true buildConfig = true
} }
lintOptions { lintOptions {
// Workaround for NullPointerException in :app:lintVitalAnalyzeRelease in fdroid build // Workaround for NullPointerException in :app:lintVitalAnalyzeRelease in fdroid build
checkReleaseBuilds false checkReleaseBuilds = false
} }
base { base {
archivesName = "moasdawiki-" + defaultConfig.versionName + "-" + defaultConfig.versionCode archivesName = "moasdawiki-" + defaultConfig.versionName + "-" + defaultConfig.versionCode
@@ -27,12 +27,13 @@ android {
} }
dependencies { dependencies {
api 'net.moasdawiki:moasdawiki-server:3.9.1' api 'net.moasdawiki:moasdawiki-server:3.9.7'
api ('androidx.appcompat:appcompat:1.6.1') {
exclude group: 'org.jetbrains', module: 'annotations' compileOnly 'androidx.annotation:annotation:1.3.0'
} implementation 'androidx.appcompat:appcompat:1.7.1'
api ('androidx.preference:preference:1.2.1') { implementation 'androidx.preference:preference:1.2.1'
exclude group: 'org.jetbrains', module: 'annotations' // Fix duplicate class error
} implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.0"
compileOnly 'org.jetbrains:annotations:26.0.1'
testImplementation 'junit:junit:4.13.2'
} }
+3 -2
View File
@@ -17,7 +17,8 @@
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
android:allowBackup="false" android:allowBackup="false"
android:fullBackupContent="false" android:fullBackupContent="false"
android:dataExtractionRules="@xml/data_extraction_rules"> android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
@@ -39,7 +40,7 @@
<provider <provider
android:name="net.moasdawiki.app.CalendarContentProvider" android:name="net.moasdawiki.app.CalendarContentProvider"
android:authorities="net.moasdawiki.app.provider" android:authorities="space.hackenslacker.moasdawiki.app.provider"
android:exported="false" android:exported="false"
android:syncable="true"/> android:syncable="true"/>
@@ -1,6 +1,6 @@
/* /*
* MoasdaWiki App * MoasdaWiki App
* Copyright (C) 2008 - 2024 Herbert Reiter (herbert@moasdawiki.net) * Copyright (C) 2008 - 2026 Herbert Reiter (herbert@moasdawiki.net)
* *
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published * under the terms of the GNU General Public License version 3 as published
@@ -17,20 +17,20 @@
package net.moasdawiki.app; package net.moasdawiki.app;
import androidx.annotation.NonNull;
import net.moasdawiki.base.Logger; import net.moasdawiki.base.Logger;
import net.moasdawiki.base.Settings; import net.moasdawiki.base.Settings;
import net.moasdawiki.service.repository.RepositoryService; import net.moasdawiki.service.repository.RepositoryService;
import org.jetbrains.annotations.NotNull;
public class AndroidSettings extends Settings { public class AndroidSettings extends Settings {
public AndroidSettings(@NotNull Logger logger, @NotNull RepositoryService repositoryService, @NotNull String configFileName) { public AndroidSettings(@NonNull Logger logger, @NonNull RepositoryService repositoryService, @NonNull String configFileName) {
super(logger, repositoryService, configFileName); super(logger, repositoryService, configFileName);
} }
@Override @Override
@NotNull @NonNull
public String getVersion() { public String getVersion() {
return BuildConfig.VERSION_NAME; return BuildConfig.VERSION_NAME;
} }
@@ -1,6 +1,6 @@
/* /*
* MoasdaWiki App * MoasdaWiki App
* Copyright (C) 2008 - 2024 Herbert Reiter (herbert@moasdawiki.net) * Copyright (C) 2008 - 2026 Herbert Reiter (herbert@moasdawiki.net)
* *
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published * under the terms of the GNU General Public License version 3 as published
@@ -23,7 +23,7 @@ import android.accounts.AccountAuthenticatorResponse;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import org.jetbrains.annotations.Nullable; import androidx.annotation.Nullable;
/** /**
* Stub authenticator, required for calendar sync adapter. * Stub authenticator, required for calendar sync adapter.
@@ -1,6 +1,6 @@
/* /*
* MoasdaWiki App * MoasdaWiki App
* Copyright (C) 2008 - 2024 Herbert Reiter (herbert@moasdawiki.net) * Copyright (C) 2008 - 2026 Herbert Reiter (herbert@moasdawiki.net)
* *
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published * under the terms of the GNU General Public License version 3 as published
@@ -21,7 +21,7 @@ import android.app.Service;
import android.content.Intent; import android.content.Intent;
import android.os.IBinder; import android.os.IBinder;
import org.jetbrains.annotations.Nullable; import androidx.annotation.Nullable;
/** /**
* Service to bind the CalendarAccountAuthenticator. * Service to bind the CalendarAccountAuthenticator.
@@ -1,6 +1,6 @@
/* /*
* MoasdaWiki App * MoasdaWiki App
* Copyright (C) 2008 - 2024 Herbert Reiter (herbert@moasdawiki.net) * Copyright (C) 2008 - 2026 Herbert Reiter (herbert@moasdawiki.net)
* *
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published * under the terms of the GNU General Public License version 3 as published
@@ -21,8 +21,9 @@ import android.content.ContentProvider;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/** /**
* Stub content provider, necessary for sync adapter. * Stub content provider, necessary for sync adapter.
@@ -36,33 +37,33 @@ public class CalendarContentProvider extends ContentProvider {
@Nullable @Nullable
@Override @Override
public String getType(@NotNull Uri uri) { public String getType(@NonNull Uri uri) {
// Return no type for MIME type // Return no type for MIME type
return null; return null;
} }
@Nullable @Nullable
@Override @Override
public Cursor query(@NotNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
// query() always returns no results // query() always returns no results
return null; return null;
} }
@Nullable @Nullable
@Override @Override
public Uri insert(@NotNull Uri uri, @Nullable ContentValues contentValues) { public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
// Provider doesn't support changes from outside // Provider doesn't support changes from outside
return null; return null;
} }
@Override @Override
public int delete(@NotNull Uri uri, @Nullable String s, @Nullable String[] strings) { public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
// Provider doesn't support changes from outside // Provider doesn't support changes from outside
return 0; return 0;
} }
@Override @Override
public int update(@NotNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) { public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
// Provider doesn't support changes from outside // Provider doesn't support changes from outside
return 0; return 0;
} }
@@ -1,6 +1,6 @@
/* /*
* MoasdaWiki App * MoasdaWiki App
* Copyright (C) 2008 - 2024 Herbert Reiter (herbert@moasdawiki.net) * Copyright (C) 2008 - 2026 Herbert Reiter (herbert@moasdawiki.net)
* *
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published * under the terms of the GNU General Public License version 3 as published
@@ -36,6 +36,9 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.provider.CalendarContract; import android.provider.CalendarContract;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
@@ -45,9 +48,6 @@ import android.widget.Toast;
import net.moasdawiki.service.transform.TerminTransformer; import net.moasdawiki.service.transform.TerminTransformer;
import net.moasdawiki.util.PathUtils; import net.moasdawiki.util.PathUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
@@ -66,7 +66,7 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter {
static final String ACCOUNT_NAME = "MoasdaWiki"; static final String ACCOUNT_NAME = "MoasdaWiki";
static final String ACCOUNT_TYPE = "net.moasdawiki"; static final String ACCOUNT_TYPE = "net.moasdawiki";
static final String PROVIDER_NAME = "net.moasdawiki.app.provider"; static final String PROVIDER_NAME = "space.hackenslacker.moasdawiki.app.provider";
private static final String CALENDAR_NAME = "MoasdaWiki Events"; private static final String CALENDAR_NAME = "MoasdaWiki Events";
private static final Uri CALENDAR_URI = CalendarContract.Calendars.CONTENT_URI; private static final Uri CALENDAR_URI = CalendarContract.Calendars.CONTENT_URI;
@@ -123,7 +123,7 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter {
/** /**
* Imports all events on Wiki pages to the Android calendar. * Imports all events on Wiki pages to the Android calendar.
*/ */
@NotNull @NonNull
private List<TerminTransformer.Event> getWikiEvents() { private List<TerminTransformer.Event> getWikiEvents() {
Log.d(TAG, "Reading Wiki events"); Log.d(TAG, "Reading Wiki events");
WikiEngineApplication app = (WikiEngineApplication) getContext(); WikiEngineApplication app = (WikiEngineApplication) getContext();
@@ -140,7 +140,7 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter {
/** /**
* Fills empty day, month, and year fields in all events. * Fills empty day, month, and year fields in all events.
*/ */
private void fillEmptyDateFields(@NotNull List<TerminTransformer.Event> events) { private void fillEmptyDateFields(@NonNull List<TerminTransformer.Event> events) {
TimeZone utc = TimeZone.getTimeZone("UTC"); TimeZone utc = TimeZone.getTimeZone("UTC");
Calendar calendar = Calendar.getInstance(utc); Calendar calendar = Calendar.getInstance(utc);
for (TerminTransformer.Event event : events) { for (TerminTransformer.Event event : events) {
@@ -159,8 +159,8 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter {
/** /**
* Reduces the number of events if more than MAX_EVENT_COUNT, only keep events in the near future. * Reduces the number of events if more than MAX_EVENT_COUNT, only keep events in the near future.
*/ */
@NotNull @NonNull
private List<TerminTransformer.Event> filterEvents(@NotNull List<TerminTransformer.Event> rawEvents) { private List<TerminTransformer.Event> filterEvents(@NonNull List<TerminTransformer.Event> rawEvents) {
if (rawEvents.size() <= MAX_EVENT_COUNT) { if (rawEvents.size() <= MAX_EVENT_COUNT) {
return rawEvents; return rawEvents;
} }
@@ -196,8 +196,8 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter {
return result; return result;
} }
@NotNull @NonNull
private Uri buildUri(@NotNull Uri uri) { private Uri buildUri(@NonNull Uri uri) {
return uri.buildUpon() return uri.buildUpon()
.appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER, "true") .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER, "true")
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, ACCOUNT_NAME) .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, ACCOUNT_NAME)
@@ -251,7 +251,7 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter {
/** /**
* Clears the calendar. This is necessary if e.g. a birthday has been changed or removed. * Clears the calendar. This is necessary if e.g. a birthday has been changed or removed.
*/ */
private void deleteAllEvents(@NotNull String calendarId) { private void deleteAllEvents(@NonNull String calendarId) {
Log.d(TAG, "Delete all events from calendar"); Log.d(TAG, "Delete all events from calendar");
Uri eventUri = buildUri(EVENT_URI); Uri eventUri = buildUri(EVENT_URI);
try (Cursor cursor = contentResolver.query(eventUri, try (Cursor cursor = contentResolver.query(eventUri,
@@ -273,7 +273,7 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter {
* Adds up to 100 events from the event list. * Adds up to 100 events from the event list.
* The first occurrence is in the current year, the events are repeated every year. * The first occurrence is in the current year, the events are repeated every year.
*/ */
private void addEvents(@NotNull String calendarId, @NotNull List<TerminTransformer.Event> events) { private void addEvents(@NonNull String calendarId, @NonNull List<TerminTransformer.Event> events) {
Log.d(TAG, "Create calendar events"); Log.d(TAG, "Create calendar events");
for (TerminTransformer.Event event : events) { for (TerminTransformer.Event event : events) {
String title = event.description; String title = event.description;
@@ -293,7 +293,7 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter {
* Adds a single event to the calendar. * Adds a single event to the calendar.
*/ */
@Nullable @Nullable
private String addEvent(@NotNull String calendarId, int day, int month, int year, @NotNull String title, @NotNull String description) { private String addEvent(@NonNull String calendarId, int day, int month, int year, @NonNull String title, @NonNull String description) {
Log.d(TAG, "Create calendar event: day=" + day + ", month=" + month + ", year=" + year Log.d(TAG, "Create calendar event: day=" + day + ", month=" + month + ", year=" + year
+ ", title=" + title + ", description=" + description); + ", title=" + title + ", description=" + description);
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
@@ -324,7 +324,7 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter {
/** /**
* Add a reminder to the given event. * Add a reminder to the given event.
*/ */
private void addReminder(@NotNull String eventId) { private void addReminder(@NonNull String eventId) {
Log.d(TAG, "Add reminder to eventId=" + eventId); Log.d(TAG, "Add reminder to eventId=" + eventId);
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put(CalendarContract.Reminders.EVENT_ID, eventId); cv.put(CalendarContract.Reminders.EVENT_ID, eventId);
@@ -338,7 +338,7 @@ public class CalendarSyncAdapter extends AbstractThreadedSyncAdapter {
/** /**
* Initiates the calendar sync. * Initiates the calendar sync.
*/ */
public static void requestCalendarSync(@NotNull Activity activity) { public static void requestCalendarSync(@NonNull Activity activity) {
Log.d(TAG, "Requesting calendar synchronization"); Log.d(TAG, "Requesting calendar synchronization");
Context context = activity.getApplicationContext(); Context context = activity.getApplicationContext();
@@ -1,6 +1,6 @@
/* /*
* MoasdaWiki App * MoasdaWiki App
* Copyright (C) 2008 - 2024 Herbert Reiter (herbert@moasdawiki.net) * Copyright (C) 2008 - 2026 Herbert Reiter (herbert@moasdawiki.net)
* *
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published * under the terms of the GNU General Public License version 3 as published
@@ -21,7 +21,7 @@ import android.app.Service;
import android.content.Intent; import android.content.Intent;
import android.os.IBinder; import android.os.IBinder;
import org.jetbrains.annotations.Nullable; import androidx.annotation.Nullable;
public class CalendarSyncAdapterService extends Service { public class CalendarSyncAdapterService extends Service {
@@ -1,6 +1,6 @@
/* /*
* MoasdaWiki App * MoasdaWiki App
* Copyright (C) 2008 - 2024 Herbert Reiter (herbert@moasdawiki.net) * Copyright (C) 2008 - 2026 Herbert Reiter (herbert@moasdawiki.net)
* *
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published * under the terms of the GNU General Public License version 3 as published
@@ -1,6 +1,6 @@
/* /*
* MoasdaWiki App * MoasdaWiki App
* Copyright (C) 2008 - 2024 Herbert Reiter (herbert@moasdawiki.net) * Copyright (C) 2008 - 2026 Herbert Reiter (herbert@moasdawiki.net)
* *
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published * under the terms of the GNU General Public License version 3 as published
@@ -46,8 +46,10 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.menu.MenuBuilder; import androidx.appcompat.view.menu.MenuBuilder;
import androidx.activity.OnBackPressedCallback;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import net.moasdawiki.base.ServiceException; import net.moasdawiki.base.ServiceException;
@@ -57,13 +59,8 @@ import net.moasdawiki.http.HttpRequest;
import net.moasdawiki.server.RequestDispatcher; import net.moasdawiki.server.RequestDispatcher;
import net.moasdawiki.service.HttpResponse; import net.moasdawiki.service.HttpResponse;
import net.moasdawiki.service.repository.RepositoryService; import net.moasdawiki.service.repository.RepositoryService;
import net.moasdawiki.util.EscapeUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.util.Arrays; import java.util.Arrays;
@@ -116,6 +113,13 @@ public class MainActivity extends AppCompatActivity {
return false; return false;
} }
}); });
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
MainActivity.this.onBackInvoked();
}
});
} }
/** /**
@@ -134,7 +138,7 @@ public class MainActivity extends AppCompatActivity {
* External URLs will be shown in an external browser. * External URLs will be shown in an external browser.
*/ */
@Override @Override
public boolean shouldOverrideUrlLoading(@NotNull WebView view, @NotNull WebResourceRequest webResourceRequest) { public boolean shouldOverrideUrlLoading(@NonNull WebView view, @NonNull WebResourceRequest webResourceRequest) {
Uri uri = webResourceRequest.getUrl(); Uri uri = webResourceRequest.getUrl();
String host = uri.getHost(); String host = uri.getHost();
if ("localhost".equals(host)) { if ("localhost".equals(host)) {
@@ -154,7 +158,7 @@ public class MainActivity extends AppCompatActivity {
*/ */
@Override @Override
@Nullable @Nullable
public WebResourceResponse shouldInterceptRequest (@NotNull WebView view, @NotNull WebResourceRequest request) { public WebResourceResponse shouldInterceptRequest (@NonNull WebView view, @NonNull WebResourceRequest request) {
try { try {
// determine URL path // determine URL path
Uri uri = request.getUrl(); Uri uri = request.getUrl();
@@ -204,8 +208,8 @@ public class MainActivity extends AppCompatActivity {
webSettings.setJavaScriptEnabled(true); webSettings.setJavaScriptEnabled(true);
} }
@NotNull @NonNull
private Map<String, String> convertParameters(@NotNull Uri uri) { private Map<String, String> convertParameters(@NonNull Uri uri) {
Map<String, String> result = new HashMap<>(); Map<String, String> result = new HashMap<>();
for (String name : uri.getQueryParameterNames()) { for (String name : uri.getQueryParameterNames()) {
String value = uri.getQueryParameter(name); String value = uri.getQueryParameter(name);
@@ -216,7 +220,7 @@ public class MainActivity extends AppCompatActivity {
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
@Override @Override
public boolean onCreateOptionsMenu(@NotNull Menu menu) { public boolean onCreateOptionsMenu(@NonNull Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present. // Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_layout, menu); getMenuInflater().inflate(R.menu.menu_layout, menu);
if(menu instanceof MenuBuilder){ if(menu instanceof MenuBuilder){
@@ -232,7 +236,7 @@ public class MainActivity extends AppCompatActivity {
} }
@Override @Override
public boolean onOptionsItemSelected(@NotNull MenuItem item) { public boolean onOptionsItemSelected(@NonNull MenuItem item) {
// Handle action bar item clicks here. The action bar will // Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long // automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml. // as you specify a parent activity in AndroidManifest.xml.
@@ -265,9 +269,7 @@ public class MainActivity extends AppCompatActivity {
super.onRequestPermissionsResult(requestCode, permissions, grantResults); super.onRequestPermissionsResult(requestCode, permissions, grantResults);
} }
@Override public void onBackInvoked() {
public void onBackPressed() {
// webview.canGoBack() always returns false
WebBackForwardList backForwardList = webview.copyBackForwardList(); WebBackForwardList backForwardList = webview.copyBackForwardList();
Log.d(TAG, "Back button pressed, backForwardList index == " + backForwardList.getCurrentIndex()); Log.d(TAG, "Back button pressed, backForwardList index == " + backForwardList.getCurrentIndex());
if (backForwardList.getCurrentIndex() > 0) { if (backForwardList.getCurrentIndex() > 0) {
@@ -386,17 +388,21 @@ public class MainActivity extends AppCompatActivity {
/** /**
* Open a URL in the embedded browser. * Open a URL in the embedded browser.
*/ */
private void loadUrl(@NotNull String url) { private void loadUrl(@NonNull String url) {
Log.d(TAG, "Open URL " + url); Log.d(TAG, "Open URL " + url);
webview.loadUrl(url); webview.loadUrl(url);
} }
@NotNull @NonNull
private String getWikiserverSearchUrl(@NotNull String query) { private String getWikiserverSearchUrl(@NonNull String query) {
return SERVER_BASE_URL + "search/?text=" + EscapeUtils.encodeUrlParameter(query); return Uri.parse(SERVER_BASE_URL + "search/")
.buildUpon()
.appendQueryParameter("text", query)
.build()
.toString();
} }
@NotNull @NonNull
private String getWikiserverHelpUrl() { private String getWikiserverHelpUrl() {
return SERVER_BASE_URL + "view/wiki/"; return SERVER_BASE_URL + "view/wiki/";
} }
@@ -1,6 +1,6 @@
/* /*
* MoasdaWiki App * MoasdaWiki App
* Copyright (C) 2008 - 2024 Herbert Reiter (herbert@moasdawiki.net) * Copyright (C) 2008 - 2026 Herbert Reiter (herbert@moasdawiki.net)
* *
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published * under the terms of the GNU General Public License version 3 as published
@@ -20,10 +20,9 @@ package net.moasdawiki.app;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import org.jetbrains.annotations.Nullable;
/** /**
* Settings dialog * Settings dialog
*/ */
@@ -1,6 +1,6 @@
/* /*
* MoasdaWiki App * MoasdaWiki App
* Copyright (C) 2008 - 2024 Herbert Reiter (herbert@moasdawiki.net) * Copyright (C) 2008 - 2026 Herbert Reiter (herbert@moasdawiki.net)
* *
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published * under the terms of the GNU General Public License version 3 as published
@@ -1,6 +1,6 @@
/* /*
* MoasdaWiki App * MoasdaWiki App
* Copyright (C) 2008 - 2024 Herbert Reiter (herbert@moasdawiki.net) * Copyright (C) 2008 - 2026 Herbert Reiter (herbert@moasdawiki.net)
* *
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published * under the terms of the GNU General Public License version 3 as published
@@ -23,6 +23,8 @@ import android.os.Build;
import android.util.Base64; import android.util.Base64;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import net.moasdawiki.base.Logger; import net.moasdawiki.base.Logger;
@@ -35,9 +37,6 @@ import net.moasdawiki.util.DateUtils;
import net.moasdawiki.util.xml.XmlGenerator; import net.moasdawiki.util.xml.XmlGenerator;
import net.moasdawiki.util.xml.XmlParser; import net.moasdawiki.util.xml.XmlParser;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -51,8 +50,10 @@ import java.net.URI;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List;
/** /**
* Connects to the configured MoasdaWiki server and downloads the wiki files. * Connects to the configured MoasdaWiki server and downloads the wiki files.
@@ -67,19 +68,19 @@ public class SynchronizeWikiClient {
private static final int CONNECTION_READ_TIMEOUT = 120_000; // 2 minutes private static final int CONNECTION_READ_TIMEOUT = 120_000; // 2 minutes
private static final int CONNECTION_RETRIES = 3; private static final int CONNECTION_RETRIES = 3;
@NotNull @NonNull
private final Context mContext; private final Context mContext;
@NotNull @NonNull
private final Logger logger; private final Logger logger;
@NotNull @NonNull
private final Settings settings; private final Settings settings;
@NotNull @NonNull
private final RepositoryService repositoryService; private final RepositoryService repositoryService;
@NotNull @NonNull
private final SecureRandom random; private final SecureRandom random;
public SynchronizeWikiClient(@NotNull Context mContext, @NotNull Logger logger, @NotNull Settings settings, public SynchronizeWikiClient(@NonNull Context mContext, @NonNull Logger logger, @NonNull Settings settings,
@NotNull RepositoryService repositoryService) { @NonNull RepositoryService repositoryService) {
this.mContext = mContext; this.mContext = mContext;
this.logger = logger; this.logger = logger;
this.settings = settings; this.settings = settings;
@@ -133,7 +134,7 @@ public class SynchronizeWikiClient {
/** /**
* Connects with the MoasdaWiki server and creates a new session. * Connects with the MoasdaWiki server and creates a new session.
*/ */
private void createSession(@NotNull String serverHostPort) throws ServiceException { private void createSession(@NonNull String serverHostPort) throws ServiceException {
// send request // send request
CreateSessionXml createSessionXml = new CreateSessionXml(); CreateSessionXml createSessionXml = new CreateSessionXml();
createSessionXml.version = PROTOCOL_VERSION; createSessionXml.version = PROTOCOL_VERSION;
@@ -170,7 +171,7 @@ public class SynchronizeWikiClient {
editor.apply(); editor.apply();
} }
@NotNull @NonNull
private String generateSessionId() { private String generateSessionId() {
return new BigInteger(130, random).toString(32); return new BigInteger(130, random).toString(32);
} }
@@ -178,8 +179,8 @@ public class SynchronizeWikiClient {
/** /**
* Check if our MoasdaWiki server session is valid and authorized. * Check if our MoasdaWiki server session is valid and authorized.
*/ */
@NotNull @NonNull
private SessionStatus checkSession(@NotNull String serverHostPort) throws ServiceException { private SessionStatus checkSession(@NonNull String serverHostPort) throws ServiceException {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
String serverSessionId = preferences.getString(Constants.PREFERENCES_SYNC_SERVER_SESSION_ID, null); String serverSessionId = preferences.getString(Constants.PREFERENCES_SYNC_SERVER_SESSION_ID, null);
String clientSessionId = preferences.getString(Constants.PREFERENCES_SYNC_CLIENT_SESSION_ID, null); String clientSessionId = preferences.getString(Constants.PREFERENCES_SYNC_CLIENT_SESSION_ID, null);
@@ -288,7 +289,8 @@ public class SynchronizeWikiClient {
// Antwort auswerten // Antwort auswerten
ListModifiedFilesResponseXml response = parseXml(responseXml, ListModifiedFilesResponseXml.class); ListModifiedFilesResponseXml response = parseXml(responseXml, ListModifiedFilesResponseXml.class);
int fileCount = response.fileList.size(); List<String> filePathsToDownload = determineFilesToDownload(response.fileList);
int fileCount = filePathsToDownload.size();
Log.d(TAG, "Downloading " + fileCount + " files from server"); Log.d(TAG, "Downloading " + fileCount + " files from server");
if (fileCount == 0) { if (fileCount == 0) {
// no files to download, cancel process // no files to download, cancel process
@@ -297,9 +299,9 @@ public class SynchronizeWikiClient {
for (int i = 0; i < fileCount; i++) { for (int i = 0; i < fileCount; i++) {
feedback.progress(i, fileCount); feedback.progress(i, fileCount);
SingleFileXml serverFile = response.fileList.get(i); String filePath = filePathsToDownload.get(i);
try { try {
downloadFileFromServer(serverHostPort, serverSessionId, serverFile.filePath); downloadFileFromServer(serverHostPort, serverSessionId, filePath);
} }
catch (ServiceException e) { catch (ServiceException e) {
Log.w(TAG, "Error reading file from server, ignoring it", e); Log.w(TAG, "Error reading file from server, ignoring it", e);
@@ -322,6 +324,24 @@ public class SynchronizeWikiClient {
return new SyncResult(true, true, false, fileCount); return new SyncResult(true, true, false, fileCount);
} }
@NonNull
static List<String> determineFilesToDownload(@NonNull List<SingleFileXml> modifiedFiles) {
List<String> filePathsToDownload = new ArrayList<>();
for (SingleFileXml modifiedFile : modifiedFiles) {
String filePath = modifiedFile.filePath;
if (filePath != null && !filePathsToDownload.contains(filePath)) {
filePathsToDownload.add(filePath);
}
}
String appConfigFilePath = Settings.getConfigFileApp();
if (!filePathsToDownload.contains(appConfigFilePath)) {
filePathsToDownload.add(appConfigFilePath);
}
return filePathsToDownload;
}
public static class SyncResult { public static class SyncResult {
private final boolean sessionValid; private final boolean sessionValid;
private final boolean sessionAuthorized; private final boolean sessionAuthorized;
@@ -352,7 +372,7 @@ public class SynchronizeWikiClient {
} }
} }
private void downloadFileFromServer(@NotNull String serverHostPort, @NotNull String serverSessionId, @NotNull String filePath) throws ServiceException { private void downloadFileFromServer(@NonNull String serverHostPort, @NonNull String serverSessionId, @NonNull String filePath) throws ServiceException {
// Anfrage schicken // Anfrage schicken
ReadFileXml readFileXml = new ReadFileXml(); ReadFileXml readFileXml = new ReadFileXml();
readFileXml.version = PROTOCOL_VERSION; readFileXml.version = PROTOCOL_VERSION;
@@ -428,7 +448,7 @@ public class SynchronizeWikiClient {
return null; return null;
} }
@NotNull @NonNull
private String getDeviceName() { private String getDeviceName() {
String manufacturer = Build.MANUFACTURER; String manufacturer = Build.MANUFACTURER;
String model = Build.MODEL; String model = Build.MODEL;
@@ -449,8 +469,8 @@ public class SynchronizeWikiClient {
/** /**
* Sends an XML request and reads the XML response. * Sends an XML request and reads the XML response.
*/ */
@NotNull @NonNull
private String sendXmlRequest(@NotNull String serverHostPort, @NotNull String urlPath, @NotNull String requestXml) throws ServiceException { private String sendXmlRequest(@NonNull String serverHostPort, @NonNull String urlPath, @NonNull String requestXml) throws ServiceException {
try { try {
String url = "http://" + serverHostPort + urlPath; String url = "http://" + serverHostPort + urlPath;
Log.d(TAG, "Request to " + url + ": " + truncateLogText(requestXml, 200)); Log.d(TAG, "Request to " + url + ": " + truncateLogText(requestXml, 200));
@@ -468,7 +488,7 @@ public class SynchronizeWikiClient {
} }
} }
private byte[] sendBinaryRequestWithRetries(@NotNull URL url, byte[] requestBytes) throws ServiceException { private byte[] sendBinaryRequestWithRetries(@NonNull URL url, byte[] requestBytes) throws ServiceException {
for (int i = 1; i <= CONNECTION_RETRIES; i++) { for (int i = 1; i <= CONNECTION_RETRIES; i++) {
try { try {
return sendBinaryRequest(url, requestBytes); return sendBinaryRequest(url, requestBytes);
@@ -480,7 +500,7 @@ public class SynchronizeWikiClient {
throw new ServiceException("Error sending request to MoasdaWiki server for " + CONNECTION_RETRIES + " times, failed"); throw new ServiceException("Error sending request to MoasdaWiki server for " + CONNECTION_RETRIES + " times, failed");
} }
private byte[] sendBinaryRequest(@NotNull URL url, byte[] requestBytes) throws IOException { private byte[] sendBinaryRequest(@NonNull URL url, byte[] requestBytes) throws IOException {
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST"); conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "text/xml"); conn.setRequestProperty("Content-Type", "text/xml");
@@ -516,8 +536,8 @@ public class SynchronizeWikiClient {
/** /**
* Wandelt eine JAXB-Bean in einen XML-Strom um. * Wandelt eine JAXB-Bean in einen XML-Strom um.
*/ */
@NotNull @NonNull
private String generateXml(@NotNull AbstractSyncXml xmlBean) throws ServiceException { private String generateXml(@NonNull AbstractSyncXml xmlBean) throws ServiceException {
XmlGenerator xmlGenerator = new XmlGenerator(); XmlGenerator xmlGenerator = new XmlGenerator();
return xmlGenerator.generate(xmlBean); return xmlGenerator.generate(xmlBean);
} }
@@ -525,8 +545,8 @@ public class SynchronizeWikiClient {
/** /**
* Wandelt einen XML-Strom in eine JAXB-Bean um. * Wandelt einen XML-Strom in eine JAXB-Bean um.
*/ */
@NotNull @NonNull
private <T extends AbstractSyncXml> T parseXml(@NotNull String xml, @NotNull Class<T> xmlBeanType) throws ServiceException { private <T extends AbstractSyncXml> T parseXml(@NonNull String xml, @NonNull Class<T> xmlBeanType) throws ServiceException {
try { try {
XmlParser xmlParser = new XmlParser(logger); XmlParser xmlParser = new XmlParser(logger);
return xmlParser.parse(xml, xmlBeanType); return xmlParser.parse(xml, xmlBeanType);
@@ -1,6 +1,6 @@
/* /*
* MoasdaWiki App * MoasdaWiki App
* Copyright (C) 2008 - 2024 Herbert Reiter (herbert@moasdawiki.net) * Copyright (C) 2008 - 2026 Herbert Reiter (herbert@moasdawiki.net)
* *
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published * under the terms of the GNU General Public License version 3 as published
+1
View File
@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical" android:orientation="vertical"
tools:context=".MainActivity"> tools:context=".MainActivity">
@@ -2,6 +2,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_width="match_parent" android:layout_width="match_parent"
android:fitsSystemWindows="true"
android:id="@+id/settings_layout"> android:id="@+id/settings_layout">
</FrameLayout> </FrameLayout>
+1 -1
View File
@@ -1,5 +1,5 @@
<resources> <resources>
<string name="app_name" translatable="false">MoasdaWiki App</string> <string name="app_name" translatable="false">MoasdaWiki</string>
<string name="about_copyright">Copyright %1$d © Herbert Reiter</string> <string name="about_copyright">Copyright %1$d © Herbert Reiter</string>
<string name="about_homepage_url" translatable="false">https://www.moasdawiki.net/</string> <string name="about_homepage_url" translatable="false">https://www.moasdawiki.net/</string>
<string name="about_version">Version %1$s</string> <string name="about_version">Version %1$s</string>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="net.moasdawiki.app.provider" android:contentAuthority="space.hackenslacker.moasdawiki.app.provider"
android:accountType="net.moasdawiki" android:accountType="net.moasdawiki"
android:userVisible="false" android:userVisible="false"
android:supportsUploading="false" android:supportsUploading="false"
@@ -0,0 +1,81 @@
/*
* MoasdaWiki App
* Copyright (C) 2008 - 2026 Herbert Reiter (herbert@moasdawiki.net)
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation (GPL-3.0-only).
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
*/
package net.moasdawiki.app;
import net.moasdawiki.service.sync.SingleFileXml;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class SynchronizeWikiClientTest {
@Test
public void determineFilesToDownloadAddsConfigFileAppWhenMissing() {
List<SingleFileXml> modifiedFiles = new ArrayList<>();
modifiedFiles.add(file("/wiki/Home-App.txt"));
List<String> result = SynchronizeWikiClient.determineFilesToDownload(modifiedFiles);
Assert.assertTrue(result.contains("/wiki/Home-App.txt"));
Assert.assertTrue(result.contains("/config-app.txt"));
}
@Test
public void determineFilesToDownloadDoesNotDuplicateConfigFileApp() {
List<SingleFileXml> modifiedFiles = new ArrayList<>();
modifiedFiles.add(file("/config-app.txt"));
List<String> result = SynchronizeWikiClient.determineFilesToDownload(modifiedFiles);
int count = 0;
for (String filePath : result) {
if ("/config-app.txt".equals(filePath)) {
count++;
}
}
Assert.assertEquals(1, count);
}
@Test
public void determineFilesToDownloadRemovesDuplicatesAndNullValues() {
List<SingleFileXml> modifiedFiles = new ArrayList<>();
modifiedFiles.add(file("/wiki/PageA.txt"));
modifiedFiles.add(file("/wiki/PageA.txt"));
modifiedFiles.add(file(null));
List<String> result = SynchronizeWikiClient.determineFilesToDownload(modifiedFiles);
int pageACount = 0;
for (String filePath : result) {
if ("/wiki/PageA.txt".equals(filePath)) {
pageACount++;
}
Assert.assertNotNull(filePath);
}
Assert.assertEquals(1, pageACount);
}
private static SingleFileXml file(String filePath) {
SingleFileXml xml = new SingleFileXml();
xml.filePath = filePath;
return xml;
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.6.0' classpath 'com.android.tools.build:gradle:8.12.3'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
@@ -0,0 +1,4 @@
- Auf moasdawiki-server 3.9.3 aktualisieren
- Gradle-Upgrade auf 8.12
- Bibliotheks-Abhängigkeiten aktualisieren
- Copyright-Jahr aktualisieren
@@ -0,0 +1,5 @@
- Auf moasdawiki-server 3.9.5 aktualisieren
- Android API 36 (Android 16) unterstützen
- Gradle-Upgrade auf 8.14.3
- Bibliotheks-Abhängigkeiten aktualisieren
- Warnung beheben
@@ -0,0 +1,5 @@
- Bugfix: Suchleiste nicht sichtbar auf neueren Androidversionen
- Appname gekürzt
- Minimalanforderung auf Android API to 33 (Android 13) erhöht
- Nullable-Annotation auf Android-Paket umstellen
- Bibliotheks-Abhängigkeiten aktualisieren
@@ -0,0 +1,5 @@
- Bugfix: Zurück-Button hat App geschlossen
- Auf moasdawiki-server 3.9.7 aktualisieren
- Auf Java 17 aktualisieren
- Bibliotheks-Abhängigkeiten aktualisieren
- Copyright-Jahr aktualisieren
@@ -0,0 +1,4 @@
- Update to moasdawiki-server 3.9.3
- Upgrade to Gradle 8.12
- Upgrade library dependencies
- Update copyright year
@@ -0,0 +1,5 @@
- Update to moasdawiki-server 3.9.5
- Upgrade to Android API 36 (Android 16)
- Upgrade to Gradle 8.14.3
- Upgrade library dependencies
- Fix warning
@@ -0,0 +1,5 @@
- Bugfix: Searchbar not visible on newer Android versions
- Shortened app name
- Update minimum supported Android API to 33 (Android 13)
- Use Nullable annotations from Android package
- Upgrade library dependencies
@@ -0,0 +1,5 @@
- Bugfix: Use back gesture for browser back
- Upgrade to moasdawiki-server 3.9.7
- Upgrade to Java 17
- Upgrade library dependencies
- Update copyright year
+1 -19
View File
@@ -1,21 +1,3 @@
# 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.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useAndroidX=true android.useAndroidX=true
org.gradle.jvmargs=-Xmx3100M org.gradle.jvmargs=-Xmx3100M
org.gradle.warning.mode=all
Binary file not shown.
+2 -2
View File
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26 distributionSha256Sum=72f44c9f8ebcb1af43838f45ee5c4aa9c5444898b3468ab3f4af7b6076c5bc3f
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
Vendored
+7 -5
View File
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # 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. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 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. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@@ -112,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@@ -203,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command: # Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped. # 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 # * 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. # treated as '${Hostname}' itself on the command line.
@@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \ -classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@" "$@"
# Stop when "xargs" is not available. # Stop when "xargs" is not available.
Vendored
+4 -2
View File
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@@ -68,11 +70,11 @@ goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* "%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 :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell