12 Commits

Author SHA1 Message Date
inventory69
9b6bf04954 🐛 Release v1.1.1 - Critical Bugfixes
 Server-Erreichbarkeits-Check vor jedem Sync
- Socket-Check mit 2s Timeout (DHCP/Routing-Init abwarten)
- Verhindert Fehler-Notifications in fremden WiFi-Netzen
- Verhindert Fehler bei Netzwerk-Initialisierung (WiFi-Connect)
- Stiller Abbruch wenn Server nicht erreichbar
- 80% schnellerer Abbruch: 2s statt 10+ Sekunden

🔧 Notification-Verbesserungen
- Alte Notifications werden beim App-Start gelöscht
- Fehler-Notifications verschwinden automatisch nach 30s
- Bessere Batterie-Effizienz

📱 UI-Bugfixes
- Sync-Icon nur anzeigen wenn Sync konfiguriert ist
- Swipe-to-Delete: Kein Flackern mehr bei schnellem Löschen
- Scroll-to-Top nach Note Save (ListAdapter async fix)

📡 Sync-Architektur Dokumentation
- SYNC_ARCHITECTURE.md mit allen 4 Sync-Triggern
- DOCS.md + DOCS.en.md aktualisiert
- GitHub Actions: F-Droid Changelogs statt Commit-Messages

🎯 Testing: BUGFIX_SPURIOUS_SYNC_ERROR_NOTIFICATIONS.md
📦 Version: 1.1.1 (versionCode=3)
2025-12-26 12:18:51 +01:00
inventory69
7644f5bf76 📝 Add CONTRIBUTING.md with PR workflow documentation
- Comprehensive contributor guide (bilingual DE/EN)
- Explains automated PR build checks
- Local build & test instructions
- Code style guidelines
- PR checklist
- What contributions are welcome
- Linked in README.md and README.en.md

[skip ci]
2025-12-24 00:12:11 +01:00
inventory69
300dc67a7c 🔧 Add PR build check workflow
- Builds debug APKs for pull requests (no signing required)
- Runs unit tests
- Uploads APKs as artifacts (30 days retention)
- Posts build status comment to PR
- No production releases for PRs (only on main merge)
- Gradle cache for faster builds

[skip ci]
2025-12-24 00:06:27 +01:00
inventory69
c42a9c84d7 Improve issue templates with structured forms
- Add proper GitHub Form templates with dropdowns and checkboxes
- Bug report: Android version, app version, sync/battery optimization status
- Feature request: Platform selection, priority, willingness to contribute
- Question: Documentation checklist, topic selection, context fields
- All forms bilingual (DE/EN) with app-specific questions

[skip ci]
2025-12-24 00:00:43 +01:00
inventory69
7942b73af3 fix: Enable blank issues in issue templates [skip ci] 2025-12-23 23:57:01 +01:00
inventory69
b4d868434f 📝 Add GitHub Issue Templates (Bug/Feature/Question)
- Bug report template with system info and reproduction steps
- Feature request template with priority selection
- Question template with documentation checklist
- Config with links to docs and troubleshooting
- All templates bilingual (DE/EN)

[skip ci]
2025-12-23 23:45:54 +01:00
inventory69
ad5fd0a313 fix: Update documentation and add English versions for all guides [skip ci] 2025-12-23 22:47:34 +01:00
inventory69
80a46e0e49 Update documentation for Simple Notes Sync
- Revamped QUICKSTART.md for clearer installation and setup instructions, including detailed steps for server setup and app configuration.
- Revised README.md to reflect new features and streamlined installation process, emphasizing offline capabilities and auto-sync functionality.
- Removed outdated README.old.md to maintain a clean repository. [skip ci]
2025-12-23 22:36:41 +01:00
inventory69
1338da9dde 🔐 Add keystore management scripts and documentation [skip ci]
- Add create-keystore.fish: Generate new release keystore with auto-generated passwords
- Add verify-secrets.fish: Verify GitHub Secrets and local keystore setup
- Add build-release-local.fish: Build signed release APKs locally
- Add LOCAL_BUILDS.md: Documentation for local release builds
- Add key.properties.example: Template for signing configuration
- Update android/.gitignore: Protect sensitive keystore files
- Integrate GitHub CLI for automatic secret management
- All scripts support both manual and automated workflows
2025-12-23 18:13:12 +01:00
inventory69
0c2d069443 fix: Update repository URL in setup instructions [skip ci] 2025-12-23 17:43:53 +01:00
inventory69
70efc13ea4 fix: Workflow für F-Droid APKs + Emoji-Fixes + Korrekturen [skip ci]
- 📦 F-Droid Flavor APKs werden jetzt mit gebaut (6 statt 3 APKs)
- 🎉 README Emoji-Darstellungsfehler behoben
- 🇩🇪 Workflow-Kommentare auf Deutsch
-  Korrekte Beschreibung: HTTP/HTTPS wählbar (nicht nur HTTPS)
- 💡 Klarstellung: Standard + F-Droid sind identisch (100% FOSS)
2025-12-22 14:54:49 +01:00
inventory69
55401977e3 fix: Korrigiere GitHub Actions Workflow für Standard-Flavor und semantische Versionierung
- Verwende assembleStandardRelease statt assembleRelease
- Korrigiere APK-Pfade: app-standard-*-release.apk
- Verwende versionName/versionCode aus build.gradle.kts (1.1.0/2)
- Keine Überschreibung mit Datums-Versionierung mehr
- F-Droid kompatible semantische Versionierung (v1.1.0)
2025-12-22 01:03:04 +01:00
36 changed files with 3262 additions and 3297 deletions

152
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,152 @@
name: 🐛 Bug Report / Fehlerbericht
description: Melde einen Fehler in der App / Report a bug in the app
title: "[BUG] "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Danke für deinen Bug Report! / Thanks for reporting a bug!
Bitte fülle alle relevanten Felder aus. / Please fill out all relevant fields.
- type: textarea
id: description
attributes:
label: 🐛 Beschreibung / Description
description: Beschreibe den Fehler kurz und präzise / Describe the bug briefly and precisely
placeholder: "z.B. Auto-Sync funktioniert nicht mehr nach App-Update / e.g. Auto-sync stopped working after app update"
validations:
required: true
- type: dropdown
id: android-version
attributes:
label: 📱 Android Version
description: Welche Android Version verwendest du? / Which Android version are you using?
options:
- Android 14
- Android 13
- Android 12
- Android 11
- Android 10
- Android 9
- Android 8.1
- Android 8.0
- Andere / Other
validations:
required: true
- type: input
id: app-version
attributes:
label: 📲 App Version
description: Welche Version der App verwendest du? (Einstellungen → Über) / Which app version? (Settings → About)
placeholder: "z.B. / e.g. v1.1.0"
validations:
required: true
- type: input
id: device
attributes:
label: 📱 Gerät / Device
description: Welches Gerät verwendest du? / Which device are you using?
placeholder: "z.B. Samsung Galaxy S21, Google Pixel 7, etc."
validations:
required: false
- type: textarea
id: steps
attributes:
label: 🔄 Schritte zum Reproduzieren / Steps to Reproduce
description: Wie kann der Fehler reproduziert werden? / How can the bug be reproduced?
placeholder: |
1. Öffne die App / Open the app
2. Gehe zu Einstellungen / Go to settings
3. Klicke auf ... / Click on ...
4. Fehler tritt auf / Bug occurs
validations:
required: true
- type: textarea
id: expected
attributes:
label: ✅ Erwartetes Verhalten / Expected Behavior
description: Was sollte passieren? / What should happen?
placeholder: "z.B. Notizen sollten alle 30 Min synchronisiert werden / e.g. Notes should sync every 30 min"
validations:
required: true
- type: textarea
id: actual
attributes:
label: ❌ Tatsächliches Verhalten / Actual Behavior
description: Was passiert stattdessen? / What happens instead?
placeholder: "z.B. Sync funktioniert nicht, keine Notification / e.g. Sync doesn't work, no notification"
validations:
required: true
- type: dropdown
id: sync-enabled
attributes:
label: <EFBFBD> Auto-Sync aktiviert? / Auto-Sync enabled?
options:
- "Ja / Yes"
- "Nein / No"
validations:
required: false
- type: dropdown
id: battery-optimization
attributes:
label: 🔋 Akku-Optimierung deaktiviert? / Battery optimization disabled?
description: Einstellungen → Apps → Simple Notes → Akku → Nicht optimieren / Settings → Apps → Simple Notes → Battery → Don't optimize
options:
- "Ja, deaktiviert / Yes, disabled"
- "Nein, noch optimiert / No, still optimized"
- "Weiß nicht / Don't know"
validations:
required: false
- type: textarea
id: server-config
attributes:
label: 🌐 Server-Konfiguration / Server Configuration
description: Falls relevant / If relevant (KEINE Passwörter! / NO passwords!)
placeholder: |
- Server läuft lokal / Server runs locally
- Docker auf Raspberry Pi / Docker on Raspberry Pi
- Gleiche WiFi / Same WiFi
- Server-IP: 192.168.x.x (erste 3 Zahlen reichen / first 3 numbers sufficient)
validations:
required: false
- type: textarea
id: logs
attributes:
label: 📋 Logs / Screenshots
description: |
Falls vorhanden: Screenshots oder LogCat Output / If available: Screenshots or LogCat output
LogCat Filter: `adb logcat -s SyncWorker NetworkMonitor WebDavSyncService`
placeholder: "Füge hier Logs oder Screenshots ein / Paste logs or screenshots here"
validations:
required: false
- type: textarea
id: additional
attributes:
label: 🔧 Zusätzliche Informationen / Additional Context
description: Gibt es noch etwas, das wir wissen sollten? / Is there anything else we should know?
validations:
required: false
- type: checkboxes
id: checklist
attributes:
label: ✅ Checklist
options:
- label: Ich habe die [Troubleshooting-Sektion](https://github.com/inventory69/simple-notes-sync/blob/main/QUICKSTART.md#troubleshooting) gelesen / I have read the troubleshooting section
required: false
- label: Ich habe "Verbindung testen" in den Einstellungen probiert / I have tried "Test connection" in settings
required: false

11
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
blank_issues_enabled: true
contact_links:
- name: 📖 Dokumentation / Documentation
url: https://github.com/inventory69/simple-notes-sync/blob/main/README.md
about: Schau zuerst in die Dokumentation / Check documentation first
- name: 🚀 Quick Start Guide
url: https://github.com/inventory69/simple-notes-sync/blob/main/QUICKSTART.md
about: Schritt-für-Schritt Anleitung / Step-by-step guide
- name: 🐛 Troubleshooting
url: https://github.com/inventory69/simple-notes-sync/blob/main/QUICKSTART.md#troubleshooting
about: Häufige Probleme und Lösungen / Common issues and solutions

View File

@@ -0,0 +1,84 @@
name: 💡 Feature Request / Feature-Wunsch
description: Schlage eine neue Funktion vor / Suggest a new feature
title: "[FEATURE] "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Danke für deinen Feature-Vorschlag! / Thanks for your feature suggestion!
- type: textarea
id: feature-description
attributes:
label: 💡 Feature-Beschreibung / Feature Description
description: Was möchtest du hinzugefügt haben? / What would you like to be added?
placeholder: "z.B. Notizen sollten Markdown-Formatierung unterstützen / e.g. Notes should support markdown formatting"
validations:
required: true
- type: textarea
id: problem
attributes:
label: 🎯 Problem / Motivation
description: Welches Problem würde dieses Feature lösen? / What problem would this feature solve?
placeholder: "z.B. Ich möchte Code-Snippets und Listen in meinen Notizen formatieren / e.g. I want to format code snippets and lists in my notes"
validations:
required: true
- type: textarea
id: solution
attributes:
label: 📝 Vorgeschlagene Lösung / Proposed Solution
description: Wie könnte das Feature funktionieren? / How could the feature work?
placeholder: "z.B. Markdown-Editor mit Live-Preview / e.g. Markdown editor with live preview"
validations:
required: false
- type: textarea
id: alternatives
attributes:
label: 🔄 Alternativen / Alternatives
description: Hast du andere Lösungsansätze in Betracht gezogen? / Have you considered other solutions?
validations:
required: false
- type: dropdown
id: platform
attributes:
label: 📱 Plattform / Platform
description: Für welche Komponente ist das Feature? / For which component is the feature?
options:
- Android App
- WebDAV Server
- Dokumentation / Documentation
- Andere / Other
validations:
required: true
- type: dropdown
id: priority
attributes:
label: 🌟 Priorität (aus deiner Sicht) / Priority (from your perspective)
options:
- Nice to have
- Wichtig / Important
- Sehr wichtig / Very important
validations:
required: false
- type: checkboxes
id: willing-to-contribute
attributes:
label: 🤝 Beitragen / Contribute
options:
- label: Ich würde gerne bei der Implementierung helfen / I would like to help with implementation
required: false
- type: textarea
id: additional
attributes:
label: 🔧 Zusätzliche Informationen / Additional Context
description: Screenshots, Mockups, Links, ähnliche Apps, etc.
validations:
required: false

76
.github/ISSUE_TEMPLATE/question.yml vendored Normal file
View File

@@ -0,0 +1,76 @@
name: ❓ Question / Frage
description: Stelle eine Frage zur Nutzung / Ask a question about usage
title: "[QUESTION] "
labels: ["question"]
body:
- type: markdown
attributes:
value: |
Hast du eine Frage? Wir helfen gerne! / Have a question? We're happy to help!
- type: textarea
id: question
attributes:
label: ❓ Frage / Question
description: Was möchtest du wissen? / What would you like to know?
placeholder: "z.B. Wie kann ich die Sync-URL für einen externen Server konfigurieren? / e.g. How can I configure the sync URL for an external server?"
validations:
required: true
- type: checkboxes
id: documentation-checked
attributes:
label: 📚 Dokumentation gelesen / Documentation checked
description: Hast du bereits in der Dokumentation nachgeschaut? / Have you already checked the documentation?
options:
- label: Ich habe die [README](https://github.com/inventory69/simple-notes-sync/blob/main/README.md) gelesen / I have read the README
required: false
- label: Ich habe den [Quick Start Guide](https://github.com/inventory69/simple-notes-sync/blob/main/QUICKSTART.md) gelesen / I have read the Quick Start Guide
required: false
- label: Ich habe das [Troubleshooting](https://github.com/inventory69/simple-notes-sync/blob/main/QUICKSTART.md#troubleshooting) durchgearbeitet / I have checked the troubleshooting section
required: false
- type: textarea
id: tried
attributes:
label: 🔍 Was hast du bereits versucht? / What have you already tried?
description: Hilf uns, dir besser zu helfen / Help us help you better
placeholder: "z.B. Ich habe versucht die Server-URL anzupassen, aber... / e.g. I tried adjusting the server URL, but..."
validations:
required: false
- type: dropdown
id: topic
attributes:
label: <EFBFBD> Thema / Topic
description: Um was geht es? / What is this about?
options:
- Server Setup / Server-Einrichtung
- App-Konfiguration / App configuration
- Sync-Probleme / Sync issues
- Netzwerk / Network
- Android-Einstellungen / Android settings
- Andere / Other
validations:
required: false
- type: textarea
id: context
attributes:
label: <EFBFBD> Kontext / Context
description: Zusätzliche Informationen die hilfreich sein könnten / Additional information that might be helpful
placeholder: |
- Android Version: Android 13
- App Version: v1.1.0
- Server: Raspberry Pi mit Docker / Raspberry Pi with Docker
- Netzwerk: Lokales WiFi / Local WiFi
validations:
required: false
- type: textarea
id: additional
attributes:
label: 🔧 Screenshots / Config
description: Falls hilfreich (KEINE Passwörter!) / If helpful (NO passwords!)
validations:
required: false

View File

@@ -2,11 +2,11 @@ name: Build Android Production APK
on: on:
push: push:
branches: [ main ] # Trigger on push to main branch branches: [ main ] # Nur bei Push/Merge auf main triggern
workflow_dispatch: # Allow manual trigger workflow_dispatch: # Ermöglicht manuellen Trigger
permissions: permissions:
contents: write # Required for creating releases contents: write # Fuer Release-Erstellung erforderlich
jobs: jobs:
build: build:
@@ -14,185 +14,164 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Code auschecken
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Java - name: Java einrichten
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '17' java-version: '17'
- name: Generate Production version number - name: Semantic Versionsnummer aus build.gradle.kts extrahieren
run: | run: |
# Generate semantic version: YYYY.MM.DD # Version aus build.gradle.kts fuer F-Droid Kompatibilität
VERSION_NAME="$(date +'%Y.%m.%d')" VERSION_NAME=$(grep "versionName = " android/app/build.gradle.kts | sed 's/.*versionName = "\(.*\)".*/\1/')
VERSION_CODE=$(grep "versionCode = " android/app/build.gradle.kts | sed 's/.*versionCode = \([0-9]*\).*/\1/')
# Use GitHub run number as build number for production # Semantische Versionierung (nicht datums-basiert)
BUILD_NUMBER="${{ github.run_number }}" BUILD_NUMBER="$VERSION_CODE"
echo "VERSION_NAME=$VERSION_NAME" >> $GITHUB_ENV echo "VERSION_NAME=$VERSION_NAME" >> $GITHUB_ENV
echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_ENV echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_ENV
echo "VERSION_TAG=v$VERSION_NAME-prod.$BUILD_NUMBER" >> $GITHUB_ENV echo "VERSION_TAG=v$VERSION_NAME" >> $GITHUB_ENV
echo "🚀 Generated PRODUCTION version: $VERSION_NAME+$BUILD_NUMBER" echo "🚀 Baue Version: $VERSION_NAME (Code: $BUILD_NUMBER)"
- name: Update build.gradle.kts with Production version - name: Version aus build.gradle.kts verifizieren
run: | run: |
# Update versionCode and versionName in build.gradle.kts echo "✅ Verwende Version aus build.gradle.kts:"
sed -i "s/versionCode = [0-9]*/versionCode = ${{ env.BUILD_NUMBER }}/" android/app/build.gradle.kts
sed -i "s/versionName = \".*\"/versionName = \"${{ env.VERSION_NAME }}\"/" android/app/build.gradle.kts
echo "✅ Updated build.gradle.kts:"
grep -E "versionCode|versionName" android/app/build.gradle.kts grep -E "versionCode|versionName" android/app/build.gradle.kts
- name: Setup Android signing - name: Android Signing konfigurieren
run: | run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/simple-notes-release.jks echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/simple-notes-release.jks
echo "storePassword=${{ secrets.KEYSTORE_PASSWORD }}" > android/key.properties echo "storePassword=${{ secrets.KEYSTORE_PASSWORD }}" > android/key.properties
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/key.properties echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/key.properties
echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/key.properties echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/key.properties
echo "storeFile=simple-notes-release.jks" >> android/key.properties echo "storeFile=simple-notes-release.jks" >> android/key.properties
echo "✅ Signing configuration created" echo "✅ Signing-Konfiguration erstellt"
- name: Build Production APK (Release) - name: Produktions-APK bauen (Standard + F-Droid Flavors)
run: | run: |
cd android cd android
./gradlew assembleRelease --no-daemon --stacktrace ./gradlew assembleStandardRelease assembleFdroidRelease --no-daemon --stacktrace
- name: Copy APK variants to root with version names - name: APK-Varianten mit Versionsnamen kopieren
run: | run: |
mkdir -p apk-output mkdir -p apk-output
# === Standard Flavor (mit Google Services) ===
# Universal APK (funktioniert auf allen Geraeten)
cp android/app/build/outputs/apk/standard/release/app-standard-universal-release.apk \
apk-output/simple-notes-sync-v${{ env.VERSION_NAME }}-standard-universal.apk
# ARM64 APK (moderne Geräte 2018+)
cp android/app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release.apk \
apk-output/simple-notes-sync-v${{ env.VERSION_NAME }}-standard-arm64-v8a.apk
# ARMv7 APK (ältere Geräte)
cp android/app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release.apk \
apk-output/simple-notes-sync-v${{ env.VERSION_NAME }}-standard-armeabi-v7a.apk
# === F-Droid Flavor (ohne Google Services) ===
# Universal APK # Universal APK
cp android/app/build/outputs/apk/release/app-universal-release.apk \ cp android/app/build/outputs/apk/fdroid/release/app-fdroid-universal-release.apk \
apk-output/simple-notes-sync-v${{ env.VERSION_NAME }}-universal.apk apk-output/simple-notes-sync-v${{ env.VERSION_NAME }}-fdroid-universal.apk
# ARM64 APK # ARM64 APK
cp android/app/build/outputs/apk/release/app-arm64-v8a-release.apk \ cp android/app/build/outputs/apk/fdroid/release/app-fdroid-arm64-v8a-release.apk \
apk-output/simple-notes-sync-v${{ env.VERSION_NAME }}-arm64-v8a.apk apk-output/simple-notes-sync-v${{ env.VERSION_NAME }}-fdroid-arm64-v8a.apk
# ARMv7 APK # ARMv7 APK
cp android/app/build/outputs/apk/release/app-armeabi-v7a-release.apk \ cp android/app/build/outputs/apk/fdroid/release/app-fdroid-armeabi-v7a-release.apk \
apk-output/simple-notes-sync-v${{ env.VERSION_NAME }}-armeabi-v7a.apk apk-output/simple-notes-sync-v${{ env.VERSION_NAME }}-fdroid-armeabi-v7a.apk
echo "✅ APK files prepared:" echo "✅ APK-Dateien vorbereitet (Standard + F-Droid):"
ls -lh apk-output/ ls -lh apk-output/
- name: Upload APK artifacts - name: APK-Artefakte hochladen
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: simple-notes-sync-apks-v${{ env.VERSION_NAME }} name: simple-notes-sync-apks-v${{ env.VERSION_NAME }}
path: apk-output/*.apk path: apk-output/*.apk
retention-days: 90 # Keep production builds longer retention-days: 90 # Produktions-Builds länger aufbewahren
- name: Get commit info - name: Commit-Informationen auslesen
run: | run: |
echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "COMMIT_DATE=$(git log -1 --format=%cd --date=iso-strict)" >> $GITHUB_ENV echo "COMMIT_DATE=$(git log -1 --format=%cd --date=iso-strict)" >> $GITHUB_ENV
- name: F-Droid Changelogs lesen
run: |
# Lese deutsche Changelog (Hauptsprache)
if [ -f "android/fastlane/metadata/android/de-DE/changelogs/${{ env.BUILD_NUMBER }}.txt" ]; then
{
echo 'CHANGELOG_DE<<EOF'
cat "android/fastlane/metadata/android/de-DE/changelogs/${{ env.BUILD_NUMBER }}.txt"
echo 'EOF'
} >> $GITHUB_ENV
else
echo "CHANGELOG_DE=Keine deutschen Release Notes verfügbar." >> $GITHUB_ENV
fi
# Get full commit message preserving newlines and emojis (UTF-8) # Lese englische Changelog (optional)
{ if [ -f "android/fastlane/metadata/android/en-US/changelogs/${{ env.BUILD_NUMBER }}.txt" ]; then
echo 'COMMIT_MSG<<EOF' {
git -c core.quotepath=false log -1 --pretty=%B echo 'CHANGELOG_EN<<EOF'
echo 'EOF' cat "android/fastlane/metadata/android/en-US/changelogs/${{ env.BUILD_NUMBER }}.txt"
} >> $GITHUB_ENV echo 'EOF'
} >> $GITHUB_ENV
else
echo "CHANGELOG_EN=" >> $GITHUB_ENV
fi
- name: Create Production Release - name: Create Production Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
tag_name: ${{ env.VERSION_TAG }} tag_name: ${{ env.VERSION_TAG }}
name: "📝 Simple Notes Sync v${{ env.VERSION_NAME }} (Produktions-Release)" name: "📝 Simple Notes Sync v${{ env.VERSION_NAME }}"
files: apk-output/*.apk files: apk-output/*.apk
draft: false draft: false
prerelease: false prerelease: false
generate_release_notes: false generate_release_notes: false
body: | body: |
# 📝 Produktions-Release: Simple Notes Sync v${{ env.VERSION_NAME }} ## 📦 Downloads
## Build-Informationen | Variante | Datei | Info |
|----------|-------|------|
| **🏆 Empfohlen** | `simple-notes-sync-v${{ env.VERSION_NAME }}-standard-universal.apk` | Funktioniert auf allen Android-Geraeten |
| Modern (2018+) | `simple-notes-sync-v${{ env.VERSION_NAME }}-standard-arm64-v8a.apk` | Kleinere Dateigröße fuer 64-bit Geräte |
| Aelter (<2018) | `simple-notes-sync-v${{ env.VERSION_NAME }}-standard-armeabi-v7a.apk` | Fuer 32-bit ARM Geräte |
| F-Droid Universal | `simple-notes-sync-v${{ env.VERSION_NAME }}-fdroid-universal.apk` | Fuer F-Droid Store |
| F-Droid ARM64 | `simple-notes-sync-v${{ env.VERSION_NAME }}-fdroid-arm64-v8a.apk` | F-Droid 64-bit |
| F-Droid ARMv7 | `simple-notes-sync-v${{ env.VERSION_NAME }}-fdroid-armeabi-v7a.apk` | F-Droid 32-bit |
- **Version:** ${{ env.VERSION_NAME }}+${{ env.BUILD_NUMBER }} 💡 **Nicht sicher?** → Nimm die **Universal** APK!
- **Build-Datum:** ${{ env.COMMIT_DATE }}
---
## 📋 Changelog / Release Notes
${{ env.CHANGELOG_EN }}
<details>
<summary><3E>🇪 Deutsche Version (zum Aufklappen)</summary>
${{ env.CHANGELOG_DE }}
</details>
---
## 📊 Build-Info
- **Version:** ${{ env.VERSION_NAME }} (Code: ${{ env.BUILD_NUMBER }})
- **Datum:** ${{ env.COMMIT_DATE }}
- **Commit:** ${{ env.SHORT_SHA }} - **Commit:** ${{ env.SHORT_SHA }}
- **Umgebung:** 🟢 **PRODUKTION**
--- ---
## 📋 Änderungen **[📖 Dokumentation](https://github.com/inventory69/simple-notes-sync/blob/main/QUICKSTART.md)** · **[🐛 Issue melden](https://github.com/inventory69/simple-notes-sync/issues)**
${{ env.COMMIT_MSG }}
---
## 📦 Download & Installation
### Welche APK soll ich herunterladen?
| Dein Gerät | Lade diese APK herunter | Größe | Kompatibilität |
|------------|------------------------|-------|----------------|
| 🤷 Nicht sicher? | `simple-notes-sync-v${{ env.VERSION_NAME }}-universal.apk` | ~5 MB | Funktioniert auf allen Geräten |
| Modern (2018+) | `simple-notes-sync-v${{ env.VERSION_NAME }}-arm64-v8a.apk` | ~3 MB | Schneller, kleiner |
| Ältere Geräte | `simple-notes-sync-v${{ env.VERSION_NAME }}-armeabi-v7a.apk` | ~3 MB | Ältere ARM-Chips |
### Installationsschritte
1. Lade die passende APK aus den Assets unten herunter
2. Aktiviere "Installation aus unbekannten Quellen" in den Android-Einstellungen
3. Öffne die heruntergeladene APK-Datei
4. Folge den Installationsanweisungen
5. Konfiguriere die WebDAV-Einstellungen in der App
---
## ⚙️ Funktionen
- ✅ Automatische WebDAV-Synchronisation alle 30 Minuten (~0,4% Akku/Tag)
- ✅ Intelligente Gateway-Erkennung (automatische Heimnetzwerk-Erkennung)
- ✅ Material Design 3 Oberfläche
- ✅ Datenschutzorientiert (kein Tracking, keine Analysen)
- ✅ Offline-First Architektur
---
## 🔄 Update von vorheriger Version
Installiere diese APK einfach über die bestehende Installation - alle Daten und Einstellungen bleiben erhalten.
---
## 📱 Obtanium - Auto-Update App
Erhalte automatische Updates mit [Obtanium](https://github.com/ImranR98/Obtanium/releases/latest).
**Einrichtung:**
1. Installiere Obtanium über den Link oben
2. Füge die App mit dieser URL hinzu: `https://github.com/dettmersLiq/simple-notes-sync`
3. Aktiviere Auto-Updates
---
## 🆘 Support
Bei Problemen oder Fragen öffne bitte ein Issue auf GitHub.
---
## 🔒 Datenschutz & Sicherheit
- Alle Daten werden über deinen eigenen WebDAV-Server synchronisiert
- Keine Drittanbieter-Analysen oder Tracking
- Keine Internet-Berechtigungen außer für WebDAV-Sync
- Alle Synchronisationsvorgänge verschlüsselt (HTTPS)
- Open Source - prüfe den Code selbst
---
## 🛠️ Erstellt mit
- **Sprache:** Kotlin
- **UI:** Material Design 3
- **Sync:** WorkManager + WebDAV
- **Target SDK:** Android 16 (API 36)
- **Min SDK:** Android 8.0 (API 26)
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

113
.github/workflows/pr-build-check.yml vendored Normal file
View File

@@ -0,0 +1,113 @@
name: PR Build Check
on:
pull_request:
branches: [ main ]
paths:
- 'android/**'
- '.github/workflows/pr-build-check.yml'
jobs:
build:
name: Build & Test APK
runs-on: ubuntu-latest
steps:
- name: Code auschecken
uses: actions/checkout@v4
- name: Java einrichten
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Gradle Cache
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Version auslesen
run: |
VERSION_NAME=$(grep "versionName = " android/app/build.gradle.kts | sed 's/.*versionName = "\(.*\)".*/\1/')
VERSION_CODE=$(grep "versionCode = " android/app/build.gradle.kts | sed 's/.*versionCode = \([0-9]*\).*/\1/')
echo "VERSION_NAME=$VERSION_NAME" >> $GITHUB_ENV
echo "VERSION_CODE=$VERSION_CODE" >> $GITHUB_ENV
echo "📱 Version: $VERSION_NAME (Code: $VERSION_CODE)"
- name: Debug Build erstellen (ohne Signing)
run: |
cd android
./gradlew assembleStandardDebug assembleFdroidDebug --no-daemon --stacktrace
- name: Unit Tests ausfuehren
run: |
cd android
./gradlew test --no-daemon --stacktrace
continue-on-error: true
- name: Build-Ergebnis pruefen
run: |
if [ -f "android/app/build/outputs/apk/standard/debug/app-standard-universal-debug.apk" ]; then
echo "✅ Standard Debug APK erfolgreich gebaut"
ls -lh android/app/build/outputs/apk/standard/debug/*.apk
else
echo "❌ Standard Debug APK Build fehlgeschlagen"
exit 1
fi
if [ -f "android/app/build/outputs/apk/fdroid/debug/app-fdroid-universal-debug.apk" ]; then
echo "✅ F-Droid Debug APK erfolgreich gebaut"
ls -lh android/app/build/outputs/apk/fdroid/debug/*.apk
else
echo "❌ F-Droid Debug APK Build fehlgeschlagen"
exit 1
fi
- name: Debug APKs hochladen (Artefakte)
uses: actions/upload-artifact@v4
with:
name: debug-apks-pr-${{ github.event.pull_request.number }}
path: |
android/app/build/outputs/apk/standard/debug/*.apk
android/app/build/outputs/apk/fdroid/debug/*.apk
retention-days: 30
- name: Kommentar zu PR hinzufuegen
uses: actions/github-script@v7
if: success()
with:
script: |
const fs = require('fs');
const standardApk = fs.readdirSync('android/app/build/outputs/apk/standard/debug/')
.filter(f => f.endsWith('.apk'));
const fdroidApk = fs.readdirSync('android/app/build/outputs/apk/fdroid/debug/')
.filter(f => f.endsWith('.apk'));
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## ✅ Build erfolgreich!
**Version:** ${{ env.VERSION_NAME }} (Code: ${{ env.VERSION_CODE }})
### 📦 Debug APKs (Test-Builds)
Die Debug-APKs wurden erfolgreich gebaut und sind als Artefakte verfuegbar:
**Standard Flavor:**
${standardApk.map(f => '- `' + f + '`').join('\n')}
**F-Droid Flavor:**
${fdroidApk.map(f => '- `' + f + '`').join('\n')}
> ⚠️ **Hinweis:** Dies sind unsigned Debug-Builds zum Testen. Production Releases werden nur bei Merge auf \`main\` erstellt.
[📥 Download Artefakte](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})`
})

263
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,263 @@
# Contributing to Simple Notes Sync 🤝
> Beiträge sind willkommen! / Contributions are welcome!
**🌍 Languages:** [Deutsch](#deutsch) · [English](#english)
---
## Deutsch
Danke, dass du zu Simple Notes Sync beitragen möchtest!
### 🚀 Schnellstart
1. **Fork & Clone**
```bash
git clone https://github.com/DEIN-USERNAME/simple-notes-sync.git
cd simple-notes-sync
```
2. **Branch erstellen**
```bash
git checkout -b feature/mein-feature
# oder
git checkout -b fix/mein-bugfix
```
3. **Änderungen machen**
- Code schreiben
- Testen
- Committen mit aussagekräftiger Message
4. **Pull Request erstellen**
- Push deinen Branch: `git push origin feature/mein-feature`
- Gehe zu GitHub und erstelle einen Pull Request
- Beschreibe deine Änderungen
### 🧪 Automatische Tests
Wenn du einen Pull Request erstellst, läuft automatisch ein **Build Check**:
- ✅ Debug APKs werden gebaut (Standard + F-Droid)
- ✅ Unit Tests werden ausgeführt
- ✅ APKs werden als Artefakte hochgeladen (zum Testen)
- ✅ Build-Status wird als Kommentar im PR gepostet
**Wichtig:** Der Build muss erfolgreich sein (grüner Haken ✅) bevor der PR gemerged werden kann.
### 📱 Android App Development
**Build lokal testen:**
```bash
cd android
# Debug Build
./gradlew assembleStandardDebug
# Tests ausführen
./gradlew test
# Lint Check
./gradlew lint
```
**Anforderungen:**
- Android SDK 36 (Target)
- Android SDK 24 (Minimum)
- JDK 17
- Kotlin 1.9+
### 📝 Code Style
- **Kotlin:** Folge den [Kotlin Coding Conventions](https://kotlinlang.org/docs/coding-conventions.html)
- **Formatierung:** Android Studio Default Formatter
- **Kommentare:** Deutsch oder Englisch (bevorzugt Englisch für Code)
### 🐛 Bug Reports
Nutze die [Bug Report Template](https://github.com/inventory69/simple-notes-sync/issues/new/choose) mit:
- Android Version
- App Version
- Schritte zum Reproduzieren
- Erwartetes vs. tatsächliches Verhalten
### 💡 Feature Requests
Nutze die [Feature Request Template](https://github.com/inventory69/simple-notes-sync/issues/new/choose) und beschreibe:
- Was soll hinzugefügt werden
- Warum ist es nützlich
- Wie könnte es funktionieren
### 📚 Dokumentation
Dokumentations-Verbesserungen sind auch Contributions!
**Dateien:**
- `README.md` / `README.en.md` - Übersicht
- `QUICKSTART.md` / `QUICKSTART.en.md` - Schritt-für-Schritt Anleitung
- `DOCS.md` / `DOCS.en.md` - Technische Details
- `server/README.md` / `server/README.en.md` - Server Setup
**Bitte:** Halte beide Sprachen (DE/EN) synchron!
### ✅ Pull Request Checklist
- [ ] Code kompiliert lokal (`./gradlew assembleStandardDebug`)
- [ ] Tests laufen durch (`./gradlew test`)
- [ ] Keine neuen Lint-Warnungen
- [ ] Commit-Messages sind aussagekräftig
- [ ] Dokumentation aktualisiert (falls nötig)
- [ ] Beide Sprachen aktualisiert (bei Doku-Änderungen)
### 🎯 Was wird akzeptiert?
**✅ Gerne:**
- Bug Fixes
- Performance-Verbesserungen
- Neue Features (nach Diskussion in einem Issue)
- Dokumentations-Verbesserungen
- Tests
- UI/UX Verbesserungen
**❌ Schwierig:**
- Breaking Changes (bitte erst als Issue diskutieren)
- Komplett neue Architektur
- Dependencies mit fragwürdigen Lizenzen
### 📄 Lizenz
Indem du contributest, stimmst du zu dass dein Code unter der [MIT License](LICENSE) veröffentlicht wird.
---
## English
Thanks for wanting to contribute to Simple Notes Sync!
### 🚀 Quick Start
1. **Fork & Clone**
```bash
git clone https://github.com/YOUR-USERNAME/simple-notes-sync.git
cd simple-notes-sync
```
2. **Create Branch**
```bash
git checkout -b feature/my-feature
# or
git checkout -b fix/my-bugfix
```
3. **Make Changes**
- Write code
- Test
- Commit with meaningful message
4. **Create Pull Request**
- Push your branch: `git push origin feature/my-feature`
- Go to GitHub and create a Pull Request
- Describe your changes
### 🧪 Automated Tests
When you create a Pull Request, an automatic **Build Check** runs:
- ✅ Debug APKs are built (Standard + F-Droid)
- ✅ Unit tests are executed
- ✅ APKs are uploaded as artifacts (for testing)
- ✅ Build status is posted as comment in PR
**Important:** The build must succeed (green checkmark ✅) before the PR can be merged.
### 📱 Android App Development
**Test build locally:**
```bash
cd android
# Debug Build
./gradlew assembleStandardDebug
# Run tests
./gradlew test
# Lint Check
./gradlew lint
```
**Requirements:**
- Android SDK 36 (Target)
- Android SDK 24 (Minimum)
- JDK 17
- Kotlin 1.9+
### 📝 Code Style
- **Kotlin:** Follow [Kotlin Coding Conventions](https://kotlinlang.org/docs/coding-conventions.html)
- **Formatting:** Android Studio Default Formatter
- **Comments:** German or English (preferably English for code)
### 🐛 Bug Reports
Use the [Bug Report Template](https://github.com/inventory69/simple-notes-sync/issues/new/choose) with:
- Android version
- App version
- Steps to reproduce
- Expected vs. actual behavior
### 💡 Feature Requests
Use the [Feature Request Template](https://github.com/inventory69/simple-notes-sync/issues/new/choose) and describe:
- What should be added
- Why is it useful
- How could it work
### 📚 Documentation
Documentation improvements are also contributions!
**Files:**
- `README.md` / `README.en.md` - Overview
- `QUICKSTART.md` / `QUICKSTART.en.md` - Step-by-step guide
- `DOCS.md` / `DOCS.en.md` - Technical details
- `server/README.md` / `server/README.en.md` - Server setup
**Please:** Keep both languages (DE/EN) in sync!
### ✅ Pull Request Checklist
- [ ] Code compiles locally (`./gradlew assembleStandardDebug`)
- [ ] Tests pass (`./gradlew test`)
- [ ] No new lint warnings
- [ ] Commit messages are meaningful
- [ ] Documentation updated (if needed)
- [ ] Both languages updated (for doc changes)
### 🎯 What Gets Accepted?
**✅ Welcome:**
- Bug fixes
- Performance improvements
- New features (after discussion in an issue)
- Documentation improvements
- Tests
- UI/UX improvements
**❌ Difficult:**
- Breaking changes (please discuss in issue first)
- Completely new architecture
- Dependencies with questionable licenses
### 📄 License
By contributing, you agree that your code will be published under the [MIT License](LICENSE).
---
## 🆘 Fragen? / Questions?
Öffne ein [Issue](https://github.com/inventory69/simple-notes-sync/issues) oder nutze die [Question Template](https://github.com/inventory69/simple-notes-sync/issues/new/choose).
**Frohe Weihnachten & Happy Coding! 🎄**

529
DOCS.en.md Normal file
View File

@@ -0,0 +1,529 @@
# Simple Notes Sync - Technical Documentation
This file contains detailed technical information about implementation, architecture, and advanced features.
**🌍 Languages:** [Deutsch](DOCS.md) · **English**
---
## 📐 Architecture
### Overall Overview
```
┌─────────────────┐
│ Android App │
│ (Kotlin) │
└────────┬────────┘
│ WebDAV/HTTP
┌────────▼────────┐
│ WebDAV Server │
│ (Docker) │
└─────────────────┘
```
### Android App Architecture
```
app/
├── models/
│ ├── Note.kt # Data class for notes
│ └── SyncStatus.kt # Sync status enum
├── storage/
│ └── NotesStorage.kt # Local JSON file storage
├── sync/
│ ├── WebDavSyncService.kt # WebDAV sync logic
│ ├── NetworkMonitor.kt # WiFi detection
│ ├── SyncWorker.kt # WorkManager background worker
│ └── BootReceiver.kt # Device reboot handler
├── adapters/
│ └── NotesAdapter.kt # RecyclerView adapter
├── utils/
│ ├── Constants.kt # App constants
│ ├── NotificationHelper.kt# Notification management
│ └── Logger.kt # Debug/release logging
└── activities/
├── MainActivity.kt # Main view with list
├── NoteEditorActivity.kt# Note editor
└── SettingsActivity.kt # Server configuration
```
---
## 🔄 Auto-Sync Implementation
### WorkManager Periodic Task
Auto-sync is based on **WorkManager** with the following configuration:
```kotlin
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) // WiFi only
.build()
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(
30, TimeUnit.MINUTES, // Every 30 minutes
10, TimeUnit.MINUTES // Flex interval
)
.setConstraints(constraints)
.build()
```
**Why WorkManager?**
- ✅ Runs even when app is closed
- ✅ Automatic restart after device reboot
- ✅ Battery-efficient (Android managed)
- ✅ Guaranteed execution when constraints are met
### Network Detection
Instead of SSID-based detection (Android 13+ privacy issues), we use **Gateway IP Comparison**:
```kotlin
fun isInHomeNetwork(): Boolean {
val gatewayIP = getGatewayIP() // e.g. 192.168.0.1
val serverIP = extractIPFromUrl(serverUrl) // e.g. 192.168.0.188
return isSameNetwork(gatewayIP, serverIP) // Checks /24 network
}
```
**Advantages:**
- ✅ No location permissions needed
- ✅ Works with all Android versions
- ✅ Reliable and fast
### Sync Flow
```
1. WorkManager wakes up (every 30 min)
2. Check: WiFi connected?
3. Check: Same network as server?
4. Load local notes
5. Upload new/changed notes → Server
6. Download remote notes ← Server
7. Merge & resolve conflicts
8. Update local storage
9. Show notification (if changes)
```
---
## <20> Sync Trigger Overview
The app uses **4 different sync triggers** with different use cases:
| Trigger | File | Function | When? | Pre-Check? |
|---------|------|----------|-------|------------|
| **1. Manual Sync** | `MainActivity.kt` | `triggerManualSync()` | User clicks sync button in menu | ✅ Yes |
| **2. Auto-Sync (onResume)** | `MainActivity.kt` | `triggerAutoSync()` | App opened/resumed | ✅ Yes |
| **3. Background Sync (Periodic)** | `SyncWorker.kt` | `doWork()` | Every 15/30/60 minutes (configurable) | ✅ Yes |
| **4. WiFi-Connect Sync** | `NetworkMonitor.kt``SyncWorker.kt` | `triggerWifiConnectSync()` | WiFi enabled/SSID changed | ✅ Yes |
### Server Reachability Check (Pre-Check)
**All 4 sync triggers** use a **pre-check** before the actual sync:
```kotlin
// WebDavSyncService.kt - isServerReachable()
suspend fun isServerReachable(): Boolean = withContext(Dispatchers.IO) {
return@withContext try {
Socket().use { socket ->
socket.connect(InetSocketAddress(host, port), 2000) // 2s Timeout
}
true
} catch (e: Exception) {
Logger.d(TAG, "Server not reachable: ${e.message}")
false
}
}
```
**Why Socket Check instead of HTTP Request?**
-**Faster:** Socket connect is instant, HTTP request takes longer
- 🔋 **Battery Efficient:** No HTTP overhead (headers, TLS handshake, etc.)
- 🎯 **More Precise:** Only checks network reachability, not server logic
- 🛡️ **Prevents Errors:** Detects foreign WiFi networks before sync error occurs
**When does the check fail?**
- ❌ Server offline/unreachable
- ❌ Wrong WiFi network (e.g. public café WiFi)
- ❌ Network not ready yet (DHCP/routing delay after WiFi connect)
- ❌ VPN blocks server access
- ❌ No WebDAV server URL configured
### Sync Behavior by Trigger Type
| Trigger | When server not reachable | On successful sync | Throttling |
|---------|--------------------------|-------------------|------------|
| Manual Sync | Toast: "Server not reachable" | Toast: "✅ Synced: X notes" | None |
| Auto-Sync (onResume) | Silent abort (no toast) | Toast: "✅ Synced: X notes" | Max. 1x/min |
| Background Sync | Silent abort (no toast) | Silent (LocalBroadcast only) | 15/30/60 min |
| WiFi-Connect Sync | Silent abort (no toast) | Silent (LocalBroadcast only) | SSID-based |
---
## <20>🔋 Battery Optimization
### Usage Analysis
| Component | Frequency | Usage | Details |
|------------|----------|-----------|---------|
| WorkManager Wakeup | Every 30 min | ~0.15 mAh | System wakes up |
| Network Check | 48x/day | ~0.03 mAh | Gateway IP check |
| WebDAV Sync | 2-3x/day | ~1.5 mAh | Only when changes |
| **Total** | - | **~12 mAh/day** | **~0.4%** at 3000mAh |
### Optimizations
1. **IP Caching**
```kotlin
private var cachedServerIP: String? = null
// DNS lookup only once at start, not every check
```
2. **Throttling**
```kotlin
private var lastSyncTime = 0L
private const val MIN_SYNC_INTERVAL_MS = 60_000L // Max 1 sync/min
```
3. **Conditional Logging**
```kotlin
object Logger {
fun d(tag: String, msg: String) {
if (BuildConfig.DEBUG) Log.d(tag, msg)
}
}
```
4. **Network Constraints**
- WiFi only (not mobile data)
- Only when server is reachable
- No permanent listeners
---
## 📦 WebDAV Sync Details
### Upload Flow
```kotlin
suspend fun uploadNotes(): Int {
val localNotes = storage.loadAllNotes()
var uploadedCount = 0
for (note in localNotes) {
if (note.syncStatus == SyncStatus.PENDING) {
val jsonContent = note.toJson()
val remotePath = "$serverUrl/${note.id}.json"
sardine.put(remotePath, jsonContent.toByteArray())
note.syncStatus = SyncStatus.SYNCED
storage.saveNote(note)
uploadedCount++
}
}
return uploadedCount
}
```
### Download Flow
```kotlin
suspend fun downloadNotes(): DownloadResult {
val remoteFiles = sardine.list(serverUrl)
var downloadedCount = 0
var conflictCount = 0
for (file in remoteFiles) {
if (!file.name.endsWith(".json")) continue
val content = sardine.get(file.href)
val remoteNote = Note.fromJson(content)
val localNote = storage.loadNote(remoteNote.id)
if (localNote == null) {
// New note from server
storage.saveNote(remoteNote)
downloadedCount++
} else if (localNote.modifiedAt < remoteNote.modifiedAt) {
// Server has newer version
storage.saveNote(remoteNote)
downloadedCount++
} else if (localNote.modifiedAt > remoteNote.modifiedAt) {
// Local version is newer → Conflict
resolveConflict(localNote, remoteNote)
conflictCount++
}
}
return DownloadResult(downloadedCount, conflictCount)
}
```
### Conflict Resolution
Strategy: **Last-Write-Wins** with **Conflict Copy**
```kotlin
fun resolveConflict(local: Note, remote: Note) {
// Rename remote note (conflict copy)
val conflictNote = remote.copy(
id = "${remote.id}_conflict_${System.currentTimeMillis()}",
title = "${remote.title} (Conflict)"
)
storage.saveNote(conflictNote)
// Local note remains
local.syncStatus = SyncStatus.SYNCED
storage.saveNote(local)
}
```
---
## 🔔 Notifications
### Notification Channels
```kotlin
val channel = NotificationChannel(
"notes_sync_channel",
"Notes Synchronization",
NotificationManager.IMPORTANCE_DEFAULT
)
```
### Success Notification
```kotlin
fun showSyncSuccess(context: Context, count: Int) {
val intent = Intent(context, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, FLAGS)
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle("Sync successful")
.setContentText("$count notes synchronized")
.setContentIntent(pendingIntent) // Click opens app
.setAutoCancel(true) // Dismiss on click
.build()
notificationManager.notify(NOTIFICATION_ID, notification)
}
```
---
## 🛡️ Permissions
The app requires **minimal permissions**:
```xml
<!-- Network -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<!-- Notifications -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Boot Receiver -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- Battery Optimization (optional) -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
```
**No Location Permissions!**
Earlier versions required `ACCESS_FINE_LOCATION` for SSID detection. Now we use Gateway IP Comparison.
---
## 🧪 Testing
### Test Server
```bash
# WebDAV server reachable?
curl -u noteuser:password http://192.168.0.188:8080/
# Upload file
echo '{"test":"data"}' > test.json
curl -u noteuser:password -T test.json http://192.168.0.188:8080/test.json
# Download file
curl -u noteuser:password http://192.168.0.188:8080/test.json
```
### Test Android App
**Unit Tests:**
```bash
cd android
./gradlew test
```
**Instrumented Tests:**
```bash
./gradlew connectedAndroidTest
```
**Manual Testing Checklist:**
- [ ] Create note → visible in list
- [ ] Edit note → changes saved
- [ ] Delete note → removed from list
- [ ] Manual sync → server status "Reachable"
- [ ] Auto-sync → notification after ~30 min
- [ ] Close app → auto-sync continues
- [ ] Device reboot → auto-sync starts automatically
- [ ] Server offline → error notification
- [ ] Notification click → app opens
---
## 🚀 Build & Deployment
### Debug Build
```bash
cd android
./gradlew assembleDebug
# APK: app/build/outputs/apk/debug/app-debug.apk
```
### Release Build
```bash
./gradlew assembleRelease
# APK: app/build/outputs/apk/release/app-release-unsigned.apk
```
### Sign (for Distribution)
```bash
# Create keystore
keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-alias
# Sign APK
jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 \
-keystore my-release-key.jks \
app-release-unsigned.apk my-alias
# Optimize
zipalign -v 4 app-release-unsigned.apk app-release.apk
```
---
## 🐛 Debugging
### LogCat Filter
```bash
# Only app logs
adb logcat -s SimpleNotesApp NetworkMonitor SyncWorker WebDavSyncService
# With timestamps
adb logcat -v time -s SyncWorker
# Save to file
adb logcat -s SyncWorker > sync_debug.log
```
### Common Issues
**Problem: Auto-sync not working**
```
Solution: Disable battery optimization
Settings → Apps → Simple Notes → Battery → Don't optimize
```
**Problem: Server not reachable**
```
Check:
1. Server running? → docker-compose ps
2. IP correct? → ip addr show
3. Port open? → telnet 192.168.0.188 8080
4. Firewall? → sudo ufw allow 8080
```
**Problem: Notifications not appearing**
```
Check:
1. Notification permission granted?
2. Do Not Disturb active?
3. App in background? → Force stop & restart
```
---
## 📚 Dependencies
```gradle
// Core
androidx.core:core-ktx:1.12.0
androidx.appcompat:appcompat:1.6.1
com.google.android.material:material:1.11.0
// Lifecycle
androidx.lifecycle:lifecycle-runtime-ktx:2.7.0
// RecyclerView
androidx.recyclerview:recyclerview:1.3.2
// Coroutines
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3
// WorkManager
androidx.work:work-runtime-ktx:2.9.0
// WebDAV Client
com.github.thegrizzlylabs:sardine-android:0.8
// Broadcast (deprecated but working)
androidx.localbroadcastmanager:localbroadcastmanager:1.1.0
```
---
## 🔮 Roadmap
### v1.1
- [ ] Search & Filter
- [ ] Dark Mode
- [ ] Tags/Categories
- [ ] Markdown Preview
### v2.0
- [ ] Desktop Client (Flutter)
- [ ] End-to-End Encryption
- [ ] Shared Notes (Collaboration)
- [ ] Attachment Support
---
## 📖 Further Documentation
- [Project Docs](https://github.com/inventory69/project-docs/tree/main/simple-notes-sync)
- [Sync Architecture](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/SYNC_ARCHITECTURE.md) - **Detailed Sync Trigger Documentation**
- [Android Guide](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/ANDROID_GUIDE.md)
- [Bugfix Documentation](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/BUGFIX_SYNC_SPAM_AND_NOTIFICATIONS.md)
---
**Last updated:** December 25, 2025

61
DOCS.md
View File

@@ -2,6 +2,8 @@
Diese Datei enthält detaillierte technische Informationen über die Implementierung, Architektur und erweiterte Funktionen. Diese Datei enthält detaillierte technische Informationen über die Implementierung, Architektur und erweiterte Funktionen.
**🌍 Sprachen:** **Deutsch** · [English](DOCS.en.md)
--- ---
## 📐 Architektur ## 📐 Architektur
@@ -116,7 +118,61 @@ fun isInHomeNetwork(): Boolean {
--- ---
## 🔋 Akku-Optimierung ## <EFBFBD> Sync-Trigger Übersicht
Die App verwendet **4 verschiedene Sync-Trigger** mit unterschiedlichen Anwendungsfällen:
| Trigger | Datei | Funktion | Wann? | Pre-Check? |
|---------|-------|----------|-------|------------|
| **1. Manueller Sync** | `MainActivity.kt` | `triggerManualSync()` | User klickt auf Sync-Button im Menü | ✅ Ja |
| **2. Auto-Sync (onResume)** | `MainActivity.kt` | `triggerAutoSync()` | App wird geöffnet/fortgesetzt | ✅ Ja |
| **3. Hintergrund-Sync (Periodic)** | `SyncWorker.kt` | `doWork()` | Alle 15/30/60 Minuten (konfigurierbar) | ✅ Ja |
| **4. WiFi-Connect Sync** | `NetworkMonitor.kt``SyncWorker.kt` | `triggerWifiConnectSync()` | WiFi an/SSID-Wechsel | ✅ Ja |
### Server-Erreichbarkeits-Check (Pre-Check)
**Alle 4 Sync-Trigger** verwenden vor dem eigentlichen Sync einen **Pre-Check**:
```kotlin
// WebDavSyncService.kt - isServerReachable()
suspend fun isServerReachable(): Boolean = withContext(Dispatchers.IO) {
return@withContext try {
Socket().use { socket ->
socket.connect(InetSocketAddress(host, port), 2000) // 2s Timeout
}
true
} catch (e: Exception) {
Logger.d(TAG, "Server not reachable: ${e.message}")
false
}
}
```
**Warum Socket-Check statt HTTP-Request?**
-**Schneller:** Socket-Connect ist instant, HTTP-Request dauert länger
- 🔋 **Akkuschonender:** Kein HTTP-Overhead (Headers, TLS Handshake, etc.)
- 🎯 **Präziser:** Prüft nur Netzwerk-Erreichbarkeit, nicht Server-Logik
- 🛡️ **Verhindert Fehler:** Erkennt fremde WiFi-Netze bevor Sync-Fehler entsteht
**Wann schlägt der Check fehl?**
- ❌ Server offline/nicht erreichbar
- ❌ Falsches WiFi-Netzwerk (z.B. öffentliches Café-WiFi)
- ❌ Netzwerk noch nicht bereit (DHCP/Routing-Delay nach WiFi-Connect)
- ❌ VPN blockiert Server-Zugriff
- ❌ Keine WebDAV-Server-URL konfiguriert
### Sync-Verhalten nach Trigger-Typ
| Trigger | Bei Server nicht erreichbar | Bei erfolgreichem Sync | Throttling |
|---------|----------------------------|----------------------|------------|
| Manueller Sync | Toast: "Server nicht erreichbar" | Toast: "✅ Gesynct: X Notizen" | Keins |
| Auto-Sync (onResume) | Silent abort (kein Toast) | Toast: "✅ Gesynct: X Notizen" | Max. 1x/Min |
| Hintergrund-Sync | Silent abort (kein Toast) | Silent (LocalBroadcast only) | 15/30/60 Min |
| WiFi-Connect Sync | Silent abort (kein Toast) | Silent (LocalBroadcast only) | SSID-basiert |
---
## <20>🔋 Akku-Optimierung
### Verbrauchsanalyse ### Verbrauchsanalyse
@@ -464,9 +520,10 @@ androidx.localbroadcastmanager:localbroadcastmanager:1.1.0
## 📖 Weitere Dokumentation ## 📖 Weitere Dokumentation
- [Project Docs](https://github.com/inventory69/project-docs/tree/main/simple-notes-sync) - [Project Docs](https://github.com/inventory69/project-docs/tree/main/simple-notes-sync)
- [Sync Architecture](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/SYNC_ARCHITECTURE.md) - **Detaillierte Sync-Trigger Dokumentation**
- [Android Guide](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/ANDROID_GUIDE.md) - [Android Guide](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/ANDROID_GUIDE.md)
- [Bugfix Documentation](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/BUGFIX_SYNC_SPAM_AND_NOTIFICATIONS.md) - [Bugfix Documentation](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/BUGFIX_SYNC_SPAM_AND_NOTIFICATIONS.md)
--- ---
**Letzte Aktualisierung:** 21. Dezember 2025 **Letzte Aktualisierung:** 25. Dezember 2025

View File

@@ -1,188 +0,0 @@
# GitHub Actions Setup Guide
This guide explains how to set up the GitHub Actions workflow for automated APK builds with proper signing.
## Overview
The workflow in `.github/workflows/build-production-apk.yml` automatically:
- Builds signed APKs on every push to `main`
- Generates version numbers using `YYYY.MM.DD` + build number
- Creates 3 APK variants (universal, arm64-v8a, armeabi-v7a)
- Creates GitHub releases with all APKs attached
## Prerequisites
- GitHub CLI (`gh`) installed
- Java 17+ installed (for keytool)
- Git repository initialized with GitHub remote
## Step 1: Generate Signing Keystore
⚠️ **IMPORTANT**: Store the keystore securely! Without it, you cannot publish updates to your app.
```bash
# Navigate to project root
cd /path/to/simple-notes-sync
# Generate keystore (replace values as needed)
keytool -genkey -v \
-keystore android/app/simple-notes-release.jks \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-alias simple-notes
# You will be prompted for:
# - Keystore password (remember this!)
# - Key password (remember this!)
# - Your name, organization, etc.
```
**Store these securely:**
- Keystore password
- Key password
- Alias: `simple-notes`
- Keystore file: `android/app/simple-notes-release.jks`
⚠️ **BACKUP**: Make a backup of the keystore file in a secure location (NOT in the repository).
## Step 2: Base64 Encode Keystore
```bash
# Create base64 encoded version
base64 android/app/simple-notes-release.jks > simple-notes-release.jks.b64
# Or on macOS:
base64 -i android/app/simple-notes-release.jks -o simple-notes-release.jks.b64
```
## Step 3: Set GitHub Secrets
Using GitHub CLI (recommended):
```bash
# Set KEYSTORE_BASE64 secret
gh secret set KEYSTORE_BASE64 < simple-notes-release.jks.b64
# Set KEYSTORE_PASSWORD (will prompt for input)
gh secret set KEYSTORE_PASSWORD
# Set KEY_PASSWORD (will prompt for input)
gh secret set KEY_PASSWORD
# Set KEY_ALIAS (value: simple-notes)
printf "simple-notes" | gh secret set KEY_ALIAS
```
Or manually via GitHub web interface:
1. Go to repository Settings → Secrets and variables → Actions
2. Click "New repository secret"
3. Add these secrets:
- `KEYSTORE_BASE64`: Paste content of `simple-notes-release.jks.b64`
- `KEYSTORE_PASSWORD`: Your keystore password
- `KEY_PASSWORD`: Your key password
- `KEY_ALIAS`: `simple-notes`
## Step 4: Verify Setup
```bash
# Check secrets are set
gh secret list
# Expected output:
# KEYSTORE_BASE64 Updated YYYY-MM-DD
# KEYSTORE_PASSWORD Updated YYYY-MM-DD
# KEY_PASSWORD Updated YYYY-MM-DD
# KEY_ALIAS Updated YYYY-MM-DD
```
## Step 5: Cleanup
```bash
# Remove sensitive files (they're in .gitignore, but double-check)
rm simple-notes-release.jks.b64
rm -f android/key.properties # Generated by workflow
# Verify keystore is NOT tracked by git
git status | grep -i jks
# Should return nothing
```
## Step 6: Trigger First Build
```bash
# Commit and push to main
git add .
git commit -m "🚀 feat: Add GitHub Actions deployment workflow"
git push origin main
# Or manually trigger workflow
gh workflow run build-production-apk.yml
```
## Verification
1. Go to GitHub repository → Actions tab
2. Check workflow run status
3. Once complete, go to Releases tab
4. Verify release was created with 3 APK variants
5. Download and test one of the APKs
## Troubleshooting
### Build fails with "Keystore not found"
- Check `KEYSTORE_BASE64` secret is set correctly
- Verify base64 encoding was done without line breaks
### Build fails with "Incorrect password"
- Verify `KEYSTORE_PASSWORD` and `KEY_PASSWORD` are correct
- Re-set secrets if needed
### APK files not found
- Check build logs for errors in assembleRelease step
- Verify APK output paths match workflow expectations
### Updates don't work
- Ensure you're using the same keystore for all builds
- Verify `applicationId` in build.gradle.kts matches
## Security Notes
- ✅ Keystore is base64-encoded in GitHub secrets (secure)
- ✅ Passwords are stored in GitHub secrets (encrypted)
-`key.properties` and `.jks` files are in `.gitignore`
- ⚠️ Never commit keystore files to repository
- ⚠️ Keep backup of keystore in secure location
- ⚠️ Don't share keystore passwords
## Versioning
Versions follow this pattern:
- **Version Name**: `YYYY.MM.DD` (e.g., `2025.01.15`)
- **Version Code**: GitHub run number (e.g., `42`)
- **Release Tag**: `vYYYY.MM.DD-prod.BUILD` (e.g., `v2025.01.15-prod.42`)
This ensures:
- Semantic versioning based on release date
- Incremental version codes for Play Store compatibility
- Clear distinction between builds
## APK Variants
The workflow generates 3 APK variants:
1. **Universal APK** (~5 MB)
- Works on all devices
- Larger file size
- Recommended for most users
2. **arm64-v8a APK** (~3 MB)
- For modern devices (2018+)
- Smaller, faster
- 64-bit ARM processors
3. **armeabi-v7a APK** (~3 MB)
- For older devices
- 32-bit ARM processors
Users can choose based on their device - Obtanium auto-updates work with all variants.

File diff suppressed because it is too large Load Diff

269
QUICKSTART.en.md Normal file
View File

@@ -0,0 +1,269 @@
# Quick Start Guide - Simple Notes Sync 📝
> Step-by-step installation and setup guide
**🌍 Languages:** [Deutsch](QUICKSTART.md) · **English**
---
## Prerequisites
- ✅ Android 8.0+ smartphone/tablet
- ✅ WiFi connection
- ✅ Own server with Docker (optional - for self-hosting)
---
## Option 1: With own server (Self-Hosted) 🏠
### Step 1: Setup WebDAV Server
On your server (e.g. Raspberry Pi, NAS, VPS):
```bash
# Clone repository
git clone https://github.com/inventory69/simple-notes-sync.git
cd simple-notes-sync/server
# Configure environment variables
cp .env.example .env
nano .env
```
**Adjust in `.env`:**
```env
WEBDAV_PASSWORD=your-secure-password-here
```
**Start server:**
```bash
docker compose up -d
```
**Find IP address:**
```bash
ip addr show | grep "inet " | grep -v 127.0.0.1
```
➡️ **Note down:** `http://YOUR-SERVER-IP:8080/`
---
### Step 2: Install App
1. **Download APK:** [Latest version](https://github.com/inventory69/simple-notes-sync/releases/latest)
- Choose: `simple-notes-sync-vX.X.X-standard-universal.apk`
2. **Allow installation:**
- Android: Settings → Security → Enable "Unknown sources" for your browser
3. **Open and install APK**
---
### Step 3: Configure App
1. **Open app**
2. **Open settings** (⚙️ icon top right)
3. **Configure server settings:**
| Field | Value |
|------|------|
| **WebDAV Server URL** | `http://YOUR-SERVER-IP:8080/` |
| **Username** | `noteuser` |
| **Password** | (your password from `.env`) |
| **Gateway SSID** | Name of your WiFi network |
4. **Press "Test connection"**
- ✅ Success? → Continue to step 4
- ❌ Error? → See [Troubleshooting](#troubleshooting)
5. **Enable auto-sync** (toggle switch)
6. **Choose sync interval:**
- **15 min** - Maximum currency (~0.8% battery/day)
- **30 min** - Recommended (~0.4% battery/day) ⭐
- **60 min** - Maximum battery life (~0.2% battery/day)
---
### Step 4: Create First Note
1. Back to main view (← arrow)
2. **"Add note"** (+ icon)
3. Enter title and text
4. **Save** (💾 icon)
5. **Wait for auto-sync** (or manually: ⚙️ → "Sync now")
🎉 **Done!** Your notes will be automatically synchronized!
---
## Option 2: Local notes only (no server) 📱
You can also use Simple Notes **without a server**:
1. **Install app** (see step 2 above)
2. **Use without server configuration:**
- Notes are only stored locally
- No auto-sync
- Perfect for offline-only use
---
## 🔋 Disable Battery Optimization
For reliable auto-sync:
1. **Settings****Apps****Simple Notes Sync**
2. **Battery****Battery usage**
3. Select: **"Don't optimize"** or **"Unrestricted"**
💡 **Note:** Android Doze Mode may still delay sync in standby (~60 min). This is normal and affects all apps.
---
## 📊 Sync Intervals in Detail
| Interval | Syncs/day | Battery/day | Battery/sync | Use case |
|-----------|-----------|----------|-----------|----------------|
| **15 min** | ~96 | ~0.8% (~23 mAh) | ~0.008% | ⚡ Maximum currency (multiple devices) |
| **30 min** | ~48 | ~0.4% (~12 mAh) | ~0.008% | ✓ **Recommended** - balanced |
| **60 min** | ~24 | ~0.2% (~6 mAh) | ~0.008% | 🔋 Maximum battery life |
---
## 🐛 Troubleshooting
### Connection test fails
**Problem:** "Connection failed" during test
**Solutions:**
1. **Server running?**
```bash
docker compose ps
# Should show "Up"
```
2. **Same WiFi?**
- Smartphone and server must be on same network
- Check SSID in app settings
3. **IP address correct?**
```bash
ip addr show | grep "inet "
# Check if IP in URL matches
```
4. **Firewall?**
```bash
# Open port 8080 (if firewall active)
sudo ufw allow 8080/tcp
```
5. **Check server logs:**
```bash
docker compose logs -f
```
---
### Auto-sync not working
**Problem:** Notes are not automatically synchronized
**Solutions:**
1. **Auto-sync enabled?**
- ⚙️ Settings → Toggle "Auto-sync" must be **ON**
2. **Battery optimization disabled?**
- See [Disable Battery Optimization](#-disable-battery-optimization)
3. **On correct WiFi?**
- Sync only works when SSID = Gateway SSID
- Check current SSID in Android settings
4. **Test manually:**
- ⚙️ Settings → "Sync now"
- Works? → Auto-sync should work too
---
### Notes not showing up
**Problem:** After installation, no notes visible even though they exist on server
**Solution:**
1. **Manually sync once:**
- ⚙️ Settings → "Sync now"
2. **Check server data:**
```bash
docker compose exec webdav ls -la /data/
# Should show .json files
```
---
### Sync errors
**Problem:** Error message during sync
**Solutions:**
1. **"401 Unauthorized"** → Wrong password
- Check password in app settings
- Compare with `.env` on server
2. **"404 Not Found"** → Wrong URL
- Should end with `/` (e.g. `http://192.168.1.100:8080/`)
3. **"Network error"** → No connection
- See [Connection test fails](#connection-test-fails)
---
## 📱 Updates
### Automatic with Obtainium (recommended)
1. **[Install Obtainium](https://github.com/ImranR98/Obtanium/releases/latest)**
2. **Add app:**
- URL: `https://github.com/inventory69/simple-notes-sync`
- Enable auto-update
3. **Done!** Obtainium notifies you of new versions
### Manual
1. Download new APK from [Releases](https://github.com/inventory69/simple-notes-sync/releases/latest)
2. Install (overwrites old version)
3. All data remains intact!
---
## 🆘 Further Help
- **GitHub Issues:** [Report problem](https://github.com/inventory69/simple-notes-sync/issues)
- **Complete docs:** [DOCS.en.md](DOCS.en.md)
- **Server setup details:** [server/README.en.md](server/README.en.md)
---
**Version:** 1.1.0 · **Created:** December 2025

View File

@@ -1,208 +1,269 @@
# 🚀 Quick Start Guide # Quick Start Guide - Simple Notes Sync 📝
## ✅ Server ist bereits gestartet! > Schritt-für-Schritt Anleitung zur Installation und Einrichtung
Der WebDAV-Server läuft bereits auf: **🌍 Sprachen:** **Deutsch** · [English](QUICKSTART.en.md)
- **Lokal:** `http://localhost:8080/`
- **Im Netzwerk:** `http://192.168.0.188:8080/`
### Credentials
- **Username:** `noteuser`
- **Password:** `SimpleNotes2025!`
## 📱 Nächste Schritte: Android App erstellen
### Option 1: Mit Android Studio (Empfohlen)
1. **Android Studio öffnen**
```
File → New → New Project
```
2. **Template wählen:**
- Empty Views Activity
3. **Projekt konfigurieren:**
```
Name: Simple Notes
Package: com.example.simplenotes
Save location: /home/liq/gitProjects/simple-notes-sync/android/
Language: Kotlin
Minimum SDK: API 24 (Android 7.0)
Build configuration: Kotlin DSL
```
4. **Dependencies hinzufügen:**
Öffne `app/build.gradle.kts` und füge hinzu:
```kotlin
dependencies {
// ... existing dependencies
// WebDAV
implementation("com.github.thegrizzlylabs:sardine-android:0.8")
// Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
// JSON
implementation("com.google.code.gson:gson:2.10.1")
// WorkManager
implementation("androidx.work:work-runtime-ktx:2.9.0")
}
```
Und in `settings.gradle.kts`:
```kotlin
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") } // Für Sardine
}
}
```
5. **Code implementieren:**
Alle Code-Beispiele findest du in:
- [ANDROID_GUIDE.md](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/ANDROID_GUIDE.md)
Kopiere der Reihe nach:
- `models/Note.kt`
- `models/SyncStatus.kt`
- `storage/NotesStorage.kt`
- `utils/DeviceIdGenerator.kt`
- `utils/NotificationHelper.kt`
- `utils/Extensions.kt`
- `utils/Constants.kt`
- UI Layouts aus dem Guide
- Activities (MainActivity, NoteEditorActivity, SettingsActivity)
- `sync/WebDavSyncService.kt`
- `sync/WifiSyncReceiver.kt`
- `sync/SyncWorker.kt`
- `adapters/NotesAdapter.kt`
6. **AndroidManifest.xml anpassen:**
```xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
...
android:usesCleartextTraffic="true">
```
7. **Build & Run:**
```
Build → Make Project
Run → Run 'app'
```
8. **In der App konfigurieren:**
- Einstellungen öffnen
- Server URL: `http://192.168.0.188:8080/`
- Username: `noteuser`
- Password: `SimpleNotes2025!`
- Heim-WLAN SSID: `DeinWLANName`
- "Verbindung testen" → sollte erfolgreich sein ✓
### Option 2: Schritt-für-Schritt Implementation
Folge dem [IMPLEMENTATION_PLAN.md](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/IMPLEMENTATION_PLAN.md) mit 6 Sprints:
1. **Sprint 1:** Server & Foundation (bereits done ✓)
2. **Sprint 2:** Basic UI (4-6h)
3. **Sprint 3:** Settings & WebDAV (6h)
4. **Sprint 4:** Auto-Sync (6h)
5. **Sprint 5:** Conflicts & Errors (6h)
6. **Sprint 6:** Polish & Testing (6h)
## 🧪 Server testen
Der Server läuft bereits. Teste ihn:
```bash
# Einfacher Test
curl -u noteuser:SimpleNotes2025! http://localhost:8080/
# Test-Notiz hochladen
echo '{"id":"test-123","title":"Test","content":"Hello World","createdAt":1703001234567,"updatedAt":1703001234567,"deviceId":"test","syncStatus":"SYNCED"}' > test.json
curl -u noteuser:SimpleNotes2025! \
-T test.json \
http://localhost:8080/test.json
# Test-Notiz abrufen
curl -u noteuser:SimpleNotes2025! http://localhost:8080/test.json
# Löschen
curl -u noteuser:SimpleNotes2025! \
-X DELETE \
http://localhost:8080/test.json
```
## 📊 Server Management
```bash
cd /home/liq/gitProjects/simple-notes-sync/server
# Status
docker-compose ps
# Logs
docker-compose logs -f
# Stoppen
docker-compose down
# Neu starten
docker-compose up -d
# Daten ansehen
ls -la notes-data/
```
## 🔧 Troubleshooting
### Server nicht erreichbar von Android
1. **Firewall prüfen:**
```bash
sudo ufw status
sudo ufw allow 8080
```
2. **Ping-Test:**
```bash
ping 192.168.0.188
```
3. **Port-Test:**
```bash
telnet 192.168.0.188 8080
```
### Permission Denied in Android
- Android 13+: POST_NOTIFICATIONS Permission akzeptieren
- Internet Permission in Manifest vorhanden?
- `usesCleartextTraffic="true"` gesetzt?
## 📚 Weitere Hilfe
- **Vollständige Doku:** [project-docs/simple-notes-sync](https://github.com/inventory69/project-docs/tree/main/simple-notes-sync)
- **Android Code:** [ANDROID_GUIDE.md](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/ANDROID_GUIDE.md)
- **Server Setup:** [SERVER_SETUP.md](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/SERVER_SETUP.md)
- **Notifications:** [NOTIFICATIONS.md](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/NOTIFICATIONS.md)
--- ---
**Server Status:** ✅ Running on `http://192.168.0.188:8080/` ## Voraussetzungen
**Next:** Android App in Android Studio erstellen
**Estimated Time:** 18-24 Stunden für vollständige App
Viel Erfolg! 🚀 - ✅ Android 8.0+ Smartphone/Tablet
- ✅ WLAN-Verbindung
- ✅ Eigener Server mit Docker (optional - für Self-Hosting)
---
## Option 1: Mit eigenem Server (Self-Hosted) 🏠
### Schritt 1: WebDAV Server einrichten
Auf deinem Server (z.B. Raspberry Pi, NAS, VPS):
```bash
# Repository klonen
git clone https://github.com/inventory69/simple-notes-sync.git
cd simple-notes-sync/server
# Umgebungsvariablen konfigurieren
cp .env.example .env
nano .env
```
**In `.env` anpassen:**
```env
WEBDAV_PASSWORD=dein-sicheres-passwort-hier
```
**Server starten:**
```bash
docker compose up -d
```
**IP-Adresse finden:**
```bash
ip addr show | grep "inet " | grep -v 127.0.0.1
```
➡️ **Notiere dir:** `http://DEINE-SERVER-IP:8080/`
---
### Schritt 2: App installieren
1. **APK herunterladen:** [Neueste Version](https://github.com/inventory69/simple-notes-sync/releases/latest)
- Wähle: `simple-notes-sync-vX.X.X-standard-universal.apk`
2. **Installation erlauben:**
- Android: Einstellungen → Sicherheit → "Unbekannte Quellen" für deinen Browser aktivieren
3. **APK öffnen und installieren**
---
### Schritt 3: App konfigurieren
1. **App öffnen**
2. **Einstellungen öffnen** (⚙️ Icon oben rechts)
3. **Server-Einstellungen konfigurieren:**
| Feld | Wert |
|------|------|
| **WebDAV Server URL** | `http://DEINE-SERVER-IP:8080/` |
| **Benutzername** | `noteuser` |
| **Passwort** | (dein Passwort aus `.env`) |
| **Gateway SSID** | Name deines WLAN-Netzwerks |
4. **"Verbindung testen"** drücken
- ✅ Erfolg? → Weiter zu Schritt 4
- ❌ Fehler? → Siehe [Troubleshooting](#troubleshooting)
5. **Auto-Sync aktivieren** (Toggle Switch)
6. **Sync-Intervall wählen:**
- **15 Min** - Maximale Aktualität (~0.8% Akku/Tag)
- **30 Min** - Empfohlen (~0.4% Akku/Tag) ⭐
- **60 Min** - Maximale Akkulaufzeit (~0.2% Akku/Tag)
---
### Schritt 4: Erste Notiz erstellen
1. Zurück zur Hauptansicht (← Pfeil)
2. **"Notiz hinzufügen"** (+ Icon)
3. Titel und Text eingeben
4. **Speichern** (💾 Icon)
5. **Warten auf Auto-Sync** (oder manuell: ⚙️ → "Jetzt synchronisieren")
🎉 **Fertig!** Deine Notizen werden automatisch synchronisiert!
---
## Option 2: Nur lokale Notizen (kein Server) 📱
Du kannst Simple Notes auch **ohne Server** nutzen:
1. **App installieren** (siehe Schritt 2 oben)
2. **Ohne Server-Konfiguration verwenden:**
- Notizen werden nur lokal gespeichert
- Kein Auto-Sync
- Perfekt für reine Offline-Nutzung
---
## 🔋 Akku-Optimierung deaktivieren
Für zuverlässigen Auto-Sync:
1. **Einstellungen****Apps****Simple Notes Sync**
2. **Akku****Akkuverbrauch**
3. Wähle: **"Nicht optimieren"** oder **"Unbeschränkt"**
💡 **Hinweis:** Android Doze Mode kann trotzdem Sync im Standby verzögern (~60 Min). Das ist normal und betrifft alle Apps.
---
## 📊 Sync-Intervalle im Detail
| Intervall | Syncs/Tag | Akku/Tag | Akku/Sync | Anwendungsfall |
|-----------|-----------|----------|-----------|----------------|
| **15 Min** | ~96 | ~0.8% (~23 mAh) | ~0.008% | ⚡ Maximal aktuell (mehrere Geräte) |
| **30 Min** | ~48 | ~0.4% (~12 mAh) | ~0.008% | ✓ **Empfohlen** - ausgewogen |
| **60 Min** | ~24 | ~0.2% (~6 mAh) | ~0.008% | 🔋 Maximale Akkulaufzeit |
---
## 🐛 Troubleshooting
### Verbindungstest schlägt fehl
**Problem:** "Verbindung fehlgeschlagen" beim Test
**Lösungen:**
1. **Server läuft?**
```bash
docker compose ps
# Sollte "Up" zeigen
```
2. **Gleiche WLAN?**
- Smartphone und Server müssen im selben Netzwerk sein
- Prüfe SSID in App-Einstellungen
3. **IP-Adresse korrekt?**
```bash
ip addr show | grep "inet "
# Prüfe ob IP in URL stimmt
```
4. **Firewall?**
```bash
# Port 8080 öffnen (falls Firewall aktiv)
sudo ufw allow 8080/tcp
```
5. **Server-Logs prüfen:**
```bash
docker compose logs -f
```
---
### Auto-Sync funktioniert nicht
**Problem:** Notizen werden nicht automatisch synchronisiert
**Lösungen:**
1. **Auto-Sync aktiviert?**
- ⚙️ Einstellungen → Toggle "Auto-Sync" muss **AN** sein
2. **Akku-Optimierung deaktiviert?**
- Siehe [Akku-Optimierung](#-akku-optimierung-deaktivieren)
3. **Im richtigen WLAN?**
- Sync funktioniert nur wenn SSID = Gateway SSID
- Prüfe aktuelle SSID in Android-Einstellungen
4. **Manuell testen:**
- ⚙️ Einstellungen → "Jetzt synchronisieren"
- Funktioniert das? → Auto-Sync sollte auch funktionieren
---
### Notizen werden nicht angezeigt
**Problem:** Nach Installation sind keine Notizen da, obwohl welche auf dem Server liegen
**Lösung:**
1. **Einmalig manuell synchronisieren:**
- ⚙️ Einstellungen → "Jetzt synchronisieren"
2. **Server-Daten prüfen:**
```bash
docker compose exec webdav ls -la /data/
# Sollte .json Dateien zeigen
```
---
### Fehler beim Sync
**Problem:** Fehlermeldung beim Synchronisieren
**Lösungen:**
1. **"401 Unauthorized"** → Passwort falsch
- Prüfe Passwort in App-Einstellungen
- Vergleiche mit `.env` auf Server
2. **"404 Not Found"** → URL falsch
- Sollte enden mit `/` (z.B. `http://192.168.1.100:8080/`)
3. **"Network error"** → Keine Verbindung
- Siehe [Verbindungstest schlägt fehl](#verbindungstest-schlägt-fehl)
---
## 📱 Updates
### Automatisch mit Obtainium (empfohlen)
1. **[Obtainium installieren](https://github.com/ImranR98/Obtanium/releases/latest)**
2. **App hinzufügen:**
- URL: `https://github.com/inventory69/simple-notes-sync`
- Auto-Update aktivieren
3. **Fertig!** Obtainium benachrichtigt dich bei neuen Versionen
### Manuell
1. Neue APK von [Releases](https://github.com/inventory69/simple-notes-sync/releases/latest) herunterladen
2. Installieren (überschreibt alte Version)
3. Alle Daten bleiben erhalten!
---
## 🆘 Weitere Hilfe
- **GitHub Issues:** [Problem melden](https://github.com/inventory69/simple-notes-sync/issues)
- **Vollständige Docs:** [DOCS.md](DOCS.md)
- **Server Setup Details:** [server/README.md](server/README.md)
---
**Version:** 1.1.0 · **Erstellt:** Dezember 2025

78
README.en.md Normal file
View File

@@ -0,0 +1,78 @@
# Simple Notes Sync 📝
> Minimalist offline notes with auto-sync to your own server
[![Android](https://img.shields.io/badge/Android-8.0%2B-green.svg)](https://www.android.com/)
[![Material Design 3](https://img.shields.io/badge/Material-Design%203-green.svg)](https://m3.material.io/)
[![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
**📱 [APK Download](https://github.com/inventory69/simple-notes-sync/releases/latest)** · **📖 [Documentation](DOCS.en.md)** · **🚀 [Quick Start](QUICKSTART.en.md)**
**🌍 Languages:** [Deutsch](README.md) · **English**
---
## Features
- 📝 Offline-First - Notes always available
- 🔄 Auto-Sync - Configurable intervals (15/30/60 min)
- 🏠 Self-Hosted - WebDAV on your server
- 🔐 Privacy-First - No cloud, no tracking
- 🔋 Battery-friendly - ~0.2-0.8% per day
---
## 🚀 Quick Start
### 1. Server Setup
```bash
cd server
cp .env.example .env
# Set password in .env
docker compose up -d
```
➡️ **Details:** [Server Setup Guide](server/README.en.md)
### 2. App Installation
1. [Download APK](https://github.com/inventory69/simple-notes-sync/releases/latest)
2. Install & open
3. ⚙️ Settings → Configure server
4. Enable auto-sync
➡️ **Details:** [Complete guide](QUICKSTART.en.md)
---
## 📚 Documentation
- **[Quick Start Guide](QUICKSTART.en.md)** - Step-by-step guide for end users
- **[Server Setup](server/README.en.md)** - Configure WebDAV server
- **[Complete Docs](DOCS.en.md)** - Features, troubleshooting, build instructions
---
## 🛠️ Development
```bash
cd android
./gradlew assembleStandardRelease
```
➡️ **Details:** [Build instructions in DOCS.en.md](DOCS.en.md)
---
## 🤝 Contributing
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
---
## 📄 License
MIT License - see [LICENSE](LICENSE)
**v1.1.0** · Built with Kotlin + Material Design 3

127
README.md
View File

@@ -1,130 +1,73 @@
# Simple Notes Sync 📝 # Simple Notes Sync 📝
> **Minimalistische Android Notiz-App mit automatischer WLAN-Synchronisierung** > Minimalistische Offline-Notizen mit Auto-Sync zu deinem eigenen Server
[![Android](https://img.shields.io/badge/Android-8.0%2B-green.svg)](https://www.android.com/) [![Android](https://img.shields.io/badge/Android-8.0%2B-green.svg)](https://www.android.com/)
[![Kotlin](https://img.shields.io/badge/Kotlin-1.9%2B-blue.svg)](https://kotlinlang.org/)
[![Material Design 3](https://img.shields.io/badge/Material-Design%203-green.svg)](https://m3.material.io/) [![Material Design 3](https://img.shields.io/badge/Material-Design%203-green.svg)](https://m3.material.io/)
[![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
Schlanke Offline-Notizen ohne Schnickschnack - deine Daten bleiben bei dir. Automatische Synchronisierung zu deinem eigenen WebDAV-Server, kein Google, kein Microsoft, keine Cloud. **📱 [APK Download](https://github.com/inventory69/simple-notes-sync/releases/latest)** · **📖 [Dokumentation](DOCS.md)** · **🚀 [Quick Start](QUICKSTART.md)**
## ✨ Features **🌍 Sprachen:** **Deutsch** · [English](README.en.md)
- 📝 **Offline-First** - Notizen lokal gespeichert, immer verfügbar
- 🔄 **Auto-Sync** - Konfigurierbare Intervalle (15/30/60 Min.) mit ~0.2-0.8% Akku/Tag
- 🏠 **Self-Hosted** - Deine Daten auf deinem Server (WebDAV)
- 🎨 **Material Design 3** - Modern & Dynamic Theming
- 🔋 **Akkuschonend** - Optimiert für Hintergrund-Synchronisierung
- 🔐 **Privacy-First** - Kein Tracking, keine Analytics, keine Cloud
- 🚫 **Keine Berechtigungen** - Nur Internet für WebDAV Sync
## 📥 Quick Download
**Android APK:** [📱 Neueste Version herunterladen](https://github.com/inventory69/simple-notes-sync/releases/latest)
💡 **Tipp:** Nutze [Obtainium](https://github.com/ImranR98/Obtainium) für automatische Updates!
--- ---
## 🚀 Schnellstart ## Features
### 1⃣ WebDAV Server starten - 📝 Offline-First - Notizen immer verfügbar
- 🔄 Auto-Sync - Konfigurierbare Intervalle (15/30/60 Min)
- 🏠 Self-Hosted - WebDAV auf deinem Server
- 🔐 Privacy-First - Keine Cloud, kein Tracking
- 🔋 Akkuschonend - ~0.2-0.8% pro Tag
```fish ---
## 🚀 Quick Start
### 1. Server Setup
```bash
cd server cd server
cp .env.example .env cp .env.example .env
# Passwort in .env anpassen # Passwort in .env setzen
docker compose up -d docker compose up -d
``` ```
### 2⃣ App installieren & konfigurieren ➡️ **Details:** [Server Setup Guide](server/README.md)
1. APK herunterladen und installieren ### 2. App Installation
2. App öffnen → **Einstellungen** (⚙️)
3. Server konfigurieren:
- URL: `http://192.168.0.XXX:8080/notes`
- Benutzername: `noteuser`
- Passwort: (aus `.env`)
4. **Auto-Sync aktivieren**
5. **Sync-Intervall wählen** (15/30/60 Min.)
**Fertig!** Notizen werden automatisch synchronisiert 🎉 1. [APK herunterladen](https://github.com/inventory69/simple-notes-sync/releases/latest)
2. Installieren & öffnen
3. ⚙️ Einstellungen → Server konfigurieren
4. Auto-Sync aktivieren
➡️ **Details:** [Vollständige Anleitung](QUICKSTART.md)
--- ---
## ⚙️ Sync-Intervalle ## 📚 Dokumentation
| Intervall | Akku/Tag | Anwendungsfall | - **[Quick Start Guide](QUICKSTART.md)** - Schritt-für-Schritt Anleitung für Endbenutzer
|-----------|----------|----------------| - **[Server Setup](server/README.md)** - WebDAV Server konfigurieren
| **15 Min** | ~0.8% (~23 mAh) | ⚡ Maximale Aktualität | - **[Vollständige Docs](DOCS.md)** - Features, Troubleshooting, Build-Anleitung
| **30 Min** | ~0.4% (~12 mAh) | ✓ Empfohlen - Ausgewogen |
| **60 Min** | ~0.2% (~6 mAh) | 🔋 Maximale Akkulaufzeit |
💡 **Hinweis:** Android Doze Mode kann Sync im Standby auf ~60 Min. verzögern (betrifft alle Apps).
--- ---
## <EFBFBD> Neue Features in v1.1.0 ## 🛠️ Entwicklung
### Konfigurierbare Sync-Intervalle ```bash
- ⏱️ Wählbare Intervalle: 15/30/60 Minuten
- 📊 Transparente Akkuverbrauchs-Anzeige
- <20> Sofortige Anwendung ohne App-Neustart
### Über-Sektion
- <20> App-Version & Build-Datum
- 🌐 Links zu GitHub Repo & Entwickler
- ⚖️ Lizenz-Information
### Verbesserungen
- 🎯 Benutzerfreundliche Doze-Mode Erklärung
- 🔕 Keine störenden Sync-Fehler Toasts im Hintergrund
- 📝 Erweiterte Debug-Logs für Troubleshooting
---
## 🛠️ Selbst bauen
```fish
cd android cd android
./gradlew assembleStandardRelease ./gradlew assembleStandardRelease
# APK: android/app/build/outputs/apk/standard/release/
``` ```
--- ➡️ **Details:** [Build-Anleitung in DOCS.md](DOCS.md)
## 🐛 Troubleshooting
### Auto-Sync funktioniert nicht
1. **Akku-Optimierung deaktivieren**
- Einstellungen → Apps → Simple Notes → Akku → Nicht optimieren
2. **WLAN-Verbindung prüfen**
- Funktioniert nur im selben Netzwerk wie Server
3. **Server-Status checken**
- Settings → "Verbindung testen"
### Server nicht erreichbar
```fish
# Status prüfen
docker compose ps
# Logs ansehen
docker compose logs -f
# IP-Adresse finden
ip addr show | grep "inet " | grep -v 127.0.0.1
```
Mehr Details: [📖 Dokumentation](DOCS.md)
--- ---
## 🤝 Contributing ## 🤝 Contributing
Contributions sind willkommen! Bitte öffne ein Issue oder Pull Request. Beiträge sind willkommen! Siehe [CONTRIBUTING.md](CONTRIBUTING.md) für Details.
--- ---
@@ -132,6 +75,4 @@ Contributions sind willkommen! Bitte öffne ein Issue oder Pull Request.
MIT License - siehe [LICENSE](LICENSE) MIT License - siehe [LICENSE](LICENSE)
--- **v1.1.0** · Gebaut mit Kotlin + Material Design 3
**Version:** 1.1.0 · **Status:** ✅ Produktiv · **Gebaut mit:** Kotlin + Material Design 3

View File

@@ -1,335 +0,0 @@
# Simple Notes Sync 📝
> Minimalistische Android-App für Offline-Notizen mit automatischer WLAN-Synchronisierung
Eine schlanke Notiz-App ohne Schnickschnack - perfekt für schnelle Gedanken, die automatisch zu Hause synchronisiert werden.
---
## ✨ Features
- 📝 **Offline-first** - Notizen werden lokal gespeichert und sind immer verfügbar
- 🔄 **Auto-Sync** - Automatische Synchronisierung wenn du im Heimnetzwerk bist
- 🏠 **WebDAV Server** - Deine Daten bleiben bei dir (Docker-Container)
- 🔋 **Akkuschonend** - Nur ~0.4% Akkuverbrauch pro Tag
- 🚫 **Keine Cloud** - Keine Google, keine Microsoft, keine Drittanbieter
- 🔐 **Privacy** - Keine Tracking, keine Analytics, keine Standort-Berechtigungen
---
## 📥 Installation
### Android App
**Option 1: APK herunterladen**
1. Neueste [Release](../../releases/latest) öffnen
2. `app-debug.apk` herunterladen
3. APK auf dem Handy installieren
**Option 2: Selbst bauen**
```bash
cd android
./gradlew assembleDebug
# APK: android/app/build/outputs/apk/debug/app-debug.apk
```
### WebDAV Server
Der Server läuft als Docker-Container und speichert deine Notizen.
```bash
cd server
cp .env.example .env
nano .env # Passwort anpassen!
docker-compose up -d
```
**Server testen:**
```bash
curl -u noteuser:dein_passwort http://192.168.0.XXX:8080/
```
---
## 🚀 Schnellstart
1. **Server starten** (siehe oben)
2. **App installieren** und öffnen
3. **Einstellungen öffnen** (⚙️ Symbol oben rechts)
4. **Server konfigurieren:**
- Server-URL: `http://192.168.0.XXX:8080/notes`
- Benutzername: `noteuser`
- Passwort: (aus `.env` Datei)
- Auto-Sync: **AN**
5. **Fertig!** Notizen werden jetzt automatisch synchronisiert
---
## 💡 Wie funktioniert Auto-Sync?
Die App prüft **alle 30 Minuten**, ob:
- ✅ WLAN verbunden ist
- ✅ Server im gleichen Netzwerk erreichbar ist
- ✅ Neue Notizen vorhanden sind
Wenn alle Bedingungen erfüllt → **Automatische Synchronisierung**
**Wichtig:** Funktioniert nur im selben Netzwerk wie der Server (kein Internet-Zugriff nötig!)
---
## 🔋 Akkuverbrauch
| Komponente | Verbrauch/Tag |
|------------|---------------|
| WorkManager (alle 30 Min) | ~0.3% |
| Netzwerk-Checks | ~0.1% |
| **Total** | **~0.4%** |
Bei einem 3000 mAh Akku entspricht das ~12 mAh pro Tag.
---
## 📱 Screenshots
_TODO: Screenshots hinzufügen_
---
## 🛠️ Technische Details
Mehr Infos zur Architektur und Implementierung findest du in der [technischen Dokumentation](DOCS.md).
**Stack:**
- **Android:** Kotlin, Material Design 3, WorkManager
- **Server:** Docker, WebDAV (bytemark/webdav)
- **Sync:** Sardine Android (WebDAV Client)
---
## 📄 Lizenz
[Lizenz hier einfügen]
---
## 🤝 Beitragen
Contributions sind willkommen! Bitte öffne ein Issue oder Pull Request.
---
## 📄 Lizenz
MIT License - siehe [LICENSE](LICENSE)
---
**Projekt Start:** 19. Dezember 2025
**Status:** ✅ Funktional & Produktiv
## 📖 Dokumentation
### In diesem Repository:
- **[QUICKSTART.md](QUICKSTART.md)** - Schnellstart-Anleitung
- **[server/README.md](server/README.md)** - Server-Verwaltung
### Vollständige Dokumentation (project-docs):
- [README.md](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/README.md) - Projekt-Übersicht & Architektur
- [IMPLEMENTATION_PLAN.md](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/IMPLEMENTATION_PLAN.md) - Detaillierter Sprint-Plan
- [SERVER_SETUP.md](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/SERVER_SETUP.md) - Server-Setup Details
- [ANDROID_GUIDE.md](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/ANDROID_GUIDE.md) - 📱 Kompletter Android-Code
- [NOTIFICATIONS.md](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/NOTIFICATIONS.md) - Notification-System Details
- [WINDOWS_SETUP.md](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/WINDOWS_SETUP.md) - 🪟 Windows + Android Studio Setup
- [CODE_REFERENCE.md](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/CODE_REFERENCE.md) - Schnelle Code-Referenz
## ⚙️ Server Konfiguration
**Standard-Credentials:**
- Username: `noteuser`
- Password: Siehe `.env` im `server/` Verzeichnis
**Server-URL:**
- Lokal: `http://localhost:8080/`
- Im Netzwerk: `http://YOUR_IP:8080/`
IP-Adresse finden:
```bash
ip addr show | grep "inet " | grep -v 127.0.0.1
```
## 📱 Android App Setup
### Vorraussetzungen
- Android Studio Hedgehog (2023.1.1) oder neuer
- JDK 17
- Min SDK 24 (Android 7.0)
- Target SDK 34 (Android 14)
### In App konfigurieren
1. App starten
2. Einstellungen öffnen
3. Server-URL eintragen (z.B. `http://192.168.1.100:8080/`)
4. Username & Passwort eingeben
5. Heim-WLAN SSID eingeben
6. "Verbindung testen"
## 🔧 Entwicklung
### Server-Management
```bash
# Status prüfen
docker-compose ps
# Logs anschauen
docker-compose logs -f
# Neustarten
docker-compose restart
# Stoppen
docker-compose down
```
### Android-Build
```bash
cd android
./gradlew assembleDebug
# APK Location:
# app/build/outputs/apk/debug/app-debug.apk
```
## 🧪 Testing
### Server-Test
```bash
# Testdatei hochladen
echo '{"id":"test","title":"Test","content":"Hello"}' > test.json
curl -u noteuser:password -T test.json http://localhost:8080/test.json
# Datei abrufen
curl -u noteuser:password http://localhost:8080/test.json
# Datei löschen
curl -u noteuser:password -X DELETE http://localhost:8080/test.json
```
### Android-App
1. Notiz erstellen → speichern → in Liste sichtbar ✓
2. WLAN verbinden → Auto-Sync ✓
3. Server offline → Fehlermeldung ✓
4. Konflikt-Szenario → Auflösung ✓
## 📦 Deployment
### Server (Production)
**Option 1: Lokaler Server (Raspberry Pi, etc.)**
```bash
docker-compose up -d
```
**Option 2: VPS (DigitalOcean, Hetzner, etc.)**
```bash
# Mit HTTPS (empfohlen)
# Zusätzlich: Reverse Proxy (nginx/Caddy) + Let's Encrypt
```
### Android App
```bash
# Release Build
./gradlew assembleRelease
# APK signieren
# Play Store Upload oder Direct Install
```
## 🔐 Security
**Entwicklung:**
- ✅ HTTP Basic Auth
- ✅ Nur im lokalen Netzwerk
**Produktion:**
- ⚠️ HTTPS mit SSL/TLS (empfohlen)
- ⚠️ Starkes Passwort
- ⚠️ Firewall-Regeln
- ⚠️ VPN für externen Zugriff
## 🐛 Troubleshooting
### Server startet nicht
```bash
# Port bereits belegt?
sudo netstat -tlnp | grep 8080
# Logs checken
docker-compose logs webdav
```
### Android kann nicht verbinden
- Ist Android im gleichen WLAN?
- Ist die Server-IP korrekt?
- Firewall blockiert Port 8080?
- Credentials korrekt?
```bash
# Ping zum Server
ping YOUR_SERVER_IP
# Port erreichbar?
telnet YOUR_SERVER_IP 8080
```
## 📝 TODO / Roadmap
### Version 1.0 (MVP)
- [x] Docker WebDAV Server
- [ ] Android Basic CRUD
- [ ] Auto-Sync bei WLAN
- [ ] Error Handling
- [ ] Notifications
### Version 1.1
- [ ] Suche
- [ ] Dark Mode
- [ ] Markdown-Support
### Version 2.0
- [ ] Desktop-Client (Flutter Desktop)
- [ ] Tags/Kategorien
- [ ] Verschlüsselung
- [ ] Shared Notes
## 📄 License
MIT License - siehe [LICENSE](LICENSE)
## 👤 Author
Created for personal use - 2025
## 🙏 Acknowledgments
- [bytemark/webdav](https://hub.docker.com/r/bytemark/webdav) - Docker WebDAV Server
- [Sardine Android](https://github.com/thegrizzlylabs/sardine-android) - WebDAV Client
- [Android WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager) - Background Tasks
---
**Project Start:** 19. Dezember 2025
**Status:** 🚧 In Development

5
android/.gitignore vendored
View File

@@ -13,3 +13,8 @@
.externalNativeBuild .externalNativeBuild
.cxx .cxx
local.properties local.properties
# Signing configuration (contains sensitive keys)
key.properties
*.jks
*.keystore

157
android/LOCAL_BUILDS.md Normal file
View File

@@ -0,0 +1,157 @@
# Lokale Gradle Builds mit Release-Signierung
Dieses Dokument erklärt, wie du lokal signierte APKs erstellst, die mit den GitHub Release-APKs kompatibel sind.
## Problem
- **GitHub Actions** erstellt signierte Release-APKs mit dem Production-Keystore
- **Lokale Debug-Builds** verwenden einen temporären Debug-Key
-**Resultat:** Nutzer können lokale Debug-APKs NICHT über Release-APKs installieren (Signature Mismatch!)
## Lösung: Lokale Release-Builds mit Production-Key
### 1⃣ Keystore-Konfiguration einrichten
Du hast bereits den Keystore: `/android/app/simple-notes-release.jks`
Erstelle eine `key.properties` Datei im `/android/` Ordner:
```bash
cd /home/liq/gitProjects/simple-notes-sync/android
cp key.properties.example key.properties
```
Bearbeite `key.properties` mit den echten Werten:
```properties
storeFile=simple-notes-release.jks
storePassword=<dein-keystore-password>
keyAlias=<dein-key-alias>
keyPassword=<dein-key-password>
```
**Wichtig:** Die Werte müssen **exakt** mit den GitHub Secrets übereinstimmen:
- `KEYSTORE_PASSWORD``storePassword`
- `KEY_ALIAS``keyAlias`
- `KEY_PASSWORD``keyPassword`
### 2⃣ Lokal signierte Release-APKs bauen
```bash
cd android
./gradlew assembleStandardRelease
```
Die signierten APKs findest du dann hier:
```
android/app/build/outputs/apk/standard/release/
├── app-standard-universal-release.apk
├── app-standard-arm64-v8a-release.apk
└── app-standard-armeabi-v7a-release.apk
```
### 3⃣ F-Droid Flavor bauen (optional)
```bash
./gradlew assembleFdroidRelease
```
### 4⃣ Beide Flavors gleichzeitig bauen
```bash
./gradlew assembleStandardRelease assembleFdroidRelease
```
## Verifizierung der Signatur
Um zu prüfen, ob dein lokaler Build die gleiche Signatur wie die Release-Builds hat:
```bash
# Signatur von lokalem Build anzeigen
keytool -printcert -jarfile app/build/outputs/apk/standard/release/app-standard-universal-release.apk
# Signatur von GitHub Release-APK anzeigen (zum Vergleich)
keytool -printcert -jarfile ~/Downloads/simple-notes-sync-v1.1.0-standard-universal.apk
```
Die **SHA256** Fingerprints müssen **identisch** sein!
## Troubleshooting
### ❌ Build schlägt fehl: "Keystore not found"
**Problem:** `key.properties` oder Keystore-Datei fehlt
**Lösung:**
1. Prüfe, ob `key.properties` existiert: `ls -la key.properties`
2. Prüfe, ob der Keystore existiert: `ls -la app/simple-notes-release.jks`
### ❌ "Signature mismatch" beim Update
**Problem:** Der lokale Build verwendet einen anderen Key als die Release-Builds
**Lösung:**
1. Vergleiche die Signaturen mit `keytool` (siehe oben)
2. Stelle sicher, dass `key.properties` die **exakten** GitHub Secret-Werte enthält
3. Deinstalliere die alte Version und installiere die neue (als letzter Ausweg)
### ❌ Build verwendet Debug-Signatur
**Problem:** `build.gradle.kts` findet `key.properties` nicht
**Lösung:**
```bash
# Prüfe, ob die Datei im richtigen Verzeichnis liegt
ls -la android/key.properties # ✅ Richtig
ls -la android/app/key.properties # ❌ Falsch
```
## Sicherheitshinweise
⚠️ **NIEMALS** diese Dateien committen:
- `key.properties` (in `.gitignore`)
- `*.jks` / `*.keystore` (in `.gitignore`)
**Schon in `.gitignore`:**
```gitignore
key.properties
*.jks
*.keystore
```
⚠️ Die GitHub Secrets (`KEYSTORE_PASSWORD`, etc.) und die lokale `key.properties` müssen **synchron** bleiben!
## Workflow-Vergleich
### GitHub Actions Build
```yaml
- Lädt Keystore aus Base64 Secret
- Erstellt key.properties aus Secrets
- Baut mit: ./gradlew assembleStandardRelease
- ✅ Produktions-signiert
```
### Lokaler Build
```bash
# Mit key.properties konfiguriert:
./gradlew assembleStandardRelease
# ✅ Produktions-signiert (gleiche Signatur wie GitHub!)
# Ohne key.properties:
./gradlew assembleStandardRelease
# ⚠️ Debug-signiert (inkompatibel mit Releases!)
```
## Quick Reference
```bash
# Release-APK bauen (signiert, klein, optimiert)
./gradlew assembleStandardRelease
# Debug-APK bauen (unsigniert, groß, debuggable)
./gradlew assembleStandardDebug
# APK per HTTP Server verteilen
cd app/build/outputs/apk/standard/release
python3 -m http.server 8892
```

View File

@@ -17,8 +17,8 @@ android {
applicationId = "dev.dettmer.simplenotes" applicationId = "dev.dettmer.simplenotes"
minSdk = 24 minSdk = 24
targetSdk = 36 targetSdk = 36
versionCode = 2 // 🔥 F-Droid Release v1.1.0 versionCode = 3 // 🔥 Bugfix: Spurious Sync Error Notifications + Sync Icon Bug
versionName = "1.1.0" // 🔥 Configurable Sync Interval + About Section versionName = "1.1.1" // 🔥 Bugfix: Server-Erreichbarkeits-Check + Notification-Improvements
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -45,6 +45,9 @@ class MainActivity : AppCompatActivity() {
private lateinit var adapter: NotesAdapter private lateinit var adapter: NotesAdapter
private val storage by lazy { NotesStorage(this) } private val storage by lazy { NotesStorage(this) }
// Track pending deletions to prevent flicker when notes reload
private val pendingDeletions = mutableSetOf<String>()
private val prefs by lazy { private val prefs by lazy {
getSharedPreferences(Constants.PREFS_NAME, Context.MODE_PRIVATE) getSharedPreferences(Constants.PREFS_NAME, Context.MODE_PRIVATE)
} }
@@ -91,6 +94,9 @@ class MainActivity : AppCompatActivity() {
Logger.enableFileLogging(this) Logger.enableFileLogging(this)
} }
// Alte Sync-Notifications beim App-Start löschen
NotificationHelper.clearSyncNotifications(this)
// Permission für Notifications (Android 13+) // Permission für Notifications (Android 13+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestNotificationPermission() requestNotificationPermission()
@@ -117,7 +123,7 @@ class MainActivity : AppCompatActivity() {
Logger.d(TAG, "📡 BroadcastReceiver registered (sync-completed)") Logger.d(TAG, "📡 BroadcastReceiver registered (sync-completed)")
// Reload notes // Reload notes (scroll to top wird in loadNotes() gemacht)
loadNotes() loadNotes()
// Trigger Auto-Sync beim App-Wechsel in Vordergrund (Toast) // Trigger Auto-Sync beim App-Wechsel in Vordergrund (Toast)
@@ -142,10 +148,21 @@ class MainActivity : AppCompatActivity() {
// Update last sync timestamp // Update last sync timestamp
prefs.edit().putLong(PREF_LAST_AUTO_SYNC_TIME, System.currentTimeMillis()).apply() prefs.edit().putLong(PREF_LAST_AUTO_SYNC_TIME, System.currentTimeMillis()).apply()
// GLEICHER Sync-Code wie manueller Sync (funktioniert!)
lifecycleScope.launch { lifecycleScope.launch {
try { try {
val syncService = WebDavSyncService(this@MainActivity) val syncService = WebDavSyncService(this@MainActivity)
// ⭐ WICHTIG: Server-Erreichbarkeits-Check VOR Sync (wie in SyncWorker)
val isReachable = withContext(Dispatchers.IO) {
syncService.isServerReachable()
}
if (!isReachable) {
Logger.d(TAG, "⏭️ Auto-sync ($source): Server not reachable - skipping silently")
return@launch
}
// Server ist erreichbar → Sync durchführen
val result = withContext(Dispatchers.IO) { val result = withContext(Dispatchers.IO) {
syncService.syncNotes() syncService.syncNotes()
} }
@@ -236,6 +253,9 @@ class MainActivity : AppCompatActivity() {
val note = adapter.currentList[position] val note = adapter.currentList[position]
val notesCopy = adapter.currentList.toMutableList() val notesCopy = adapter.currentList.toMutableList()
// Track pending deletion to prevent flicker
pendingDeletions.add(note.id)
// Remove from list immediately for visual feedback // Remove from list immediately for visual feedback
notesCopy.removeAt(position) notesCopy.removeAt(position)
adapter.submitList(notesCopy) adapter.submitList(notesCopy)
@@ -246,13 +266,15 @@ class MainActivity : AppCompatActivity() {
"Notiz gelöscht", "Notiz gelöscht",
Snackbar.LENGTH_LONG Snackbar.LENGTH_LONG
).setAction("RÜCKGÄNGIG") { ).setAction("RÜCKGÄNGIG") {
// UNDO: Restore note in list // UNDO: Remove from pending deletions and restore
pendingDeletions.remove(note.id)
loadNotes() loadNotes()
}.addCallback(object : Snackbar.Callback() { }.addCallback(object : Snackbar.Callback() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
if (event != DISMISS_EVENT_ACTION) { if (event != DISMISS_EVENT_ACTION) {
// Snackbar dismissed without UNDO → Actually delete the note // Snackbar dismissed without UNDO → Actually delete the note
storage.deleteNote(note.id) storage.deleteNote(note.id)
pendingDeletions.remove(note.id)
loadNotes() loadNotes()
} }
} }
@@ -276,10 +298,21 @@ class MainActivity : AppCompatActivity() {
private fun loadNotes() { private fun loadNotes() {
val notes = storage.loadAllNotes() val notes = storage.loadAllNotes()
adapter.submitList(notes)
// Filter out notes that are pending deletion (prevent flicker)
val filteredNotes = notes.filter { it.id !in pendingDeletions }
// Submit list with callback to scroll to top after list is updated
adapter.submitList(filteredNotes) {
// Scroll to top after list update is complete
// Wichtig: Nach dem Erstellen/Bearbeiten einer Notiz
if (filteredNotes.isNotEmpty()) {
recyclerViewNotes.scrollToPosition(0)
}
}
// Material 3 Empty State Card // Material 3 Empty State Card
emptyStateCard.visibility = if (notes.isEmpty()) { emptyStateCard.visibility = if (filteredNotes.isEmpty()) {
android.view.View.VISIBLE android.view.View.VISIBLE
} else { } else {
android.view.View.GONE android.view.View.GONE
@@ -305,8 +338,21 @@ class MainActivity : AppCompatActivity() {
try { try {
showToast("Starte Synchronisation...") showToast("Starte Synchronisation...")
// Start sync // Create sync service
val syncService = WebDavSyncService(this@MainActivity) val syncService = WebDavSyncService(this@MainActivity)
// ⭐ WICHTIG: Server-Erreichbarkeits-Check VOR Sync (wie in SyncWorker)
val isReachable = withContext(Dispatchers.IO) {
syncService.isServerReachable()
}
if (!isReachable) {
Logger.d(TAG, "⏭️ Manual Sync: Server not reachable - aborting")
showToast("Server nicht erreichbar")
return@launch
}
// Server ist erreichbar → Sync durchführen
val result = withContext(Dispatchers.IO) { val result = withContext(Dispatchers.IO) {
syncService.syncNotes() syncService.syncNotes()
} }

View File

@@ -1,5 +1,6 @@
package dev.dettmer.simplenotes.adapters package dev.dettmer.simplenotes.adapters
import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -11,6 +12,7 @@ import androidx.recyclerview.widget.RecyclerView
import dev.dettmer.simplenotes.R import dev.dettmer.simplenotes.R
import dev.dettmer.simplenotes.models.Note import dev.dettmer.simplenotes.models.Note
import dev.dettmer.simplenotes.models.SyncStatus import dev.dettmer.simplenotes.models.SyncStatus
import dev.dettmer.simplenotes.utils.Constants
import dev.dettmer.simplenotes.utils.toReadableTime import dev.dettmer.simplenotes.utils.toReadableTime
import dev.dettmer.simplenotes.utils.truncate import dev.dettmer.simplenotes.utils.truncate
@@ -39,14 +41,25 @@ class NotesAdapter(
textViewContent.text = note.content.truncate(100) textViewContent.text = note.content.truncate(100)
textViewTimestamp.text = note.updatedAt.toReadableTime() textViewTimestamp.text = note.updatedAt.toReadableTime()
// Sync status icon // Sync Icon nur zeigen wenn Sync konfiguriert ist
val syncIcon = when (note.syncStatus) { val prefs = itemView.context.getSharedPreferences(Constants.PREFS_NAME, Context.MODE_PRIVATE)
SyncStatus.SYNCED -> android.R.drawable.ic_menu_upload val serverUrl = prefs.getString(Constants.KEY_SERVER_URL, null)
SyncStatus.PENDING -> android.R.drawable.ic_popup_sync val isSyncConfigured = !serverUrl.isNullOrEmpty()
SyncStatus.CONFLICT -> android.R.drawable.ic_dialog_alert
SyncStatus.LOCAL_ONLY -> android.R.drawable.ic_menu_save if (isSyncConfigured) {
// Sync status icon
val syncIcon = when (note.syncStatus) {
SyncStatus.SYNCED -> android.R.drawable.ic_menu_upload
SyncStatus.PENDING -> android.R.drawable.ic_popup_sync
SyncStatus.CONFLICT -> android.R.drawable.ic_dialog_alert
SyncStatus.LOCAL_ONLY -> android.R.drawable.ic_menu_save
}
imageViewSyncStatus.setImageResource(syncIcon)
imageViewSyncStatus.visibility = View.VISIBLE
} else {
// Sync nicht konfiguriert → Icon verstecken
imageViewSyncStatus.visibility = View.GONE
} }
imageViewSyncStatus.setImageResource(syncIcon)
itemView.setOnClickListener { itemView.setOnClickListener {
onNoteClick(note) onNoteClick(note)

View File

@@ -52,7 +52,28 @@ class SyncWorker(
} }
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Logger.d(TAG, "📍 Step 2: Before syncNotes() call") Logger.d(TAG, "📍 Step 2: Checking server reachability (Pre-Check)")
}
// ⭐ KRITISCH: Server-Erreichbarkeits-Check VOR Sync
// Verhindert Fehler-Notifications in fremden WiFi-Netzen
// Wartet bis Netzwerk bereit ist (DHCP, Routing, Gateway)
if (!syncService.isServerReachable()) {
Logger.d(TAG, "⏭️ Server not reachable - skipping sync (no error)")
Logger.d(TAG, " Reason: Server offline/wrong network/network not ready/not configured")
Logger.d(TAG, " This is normal in foreign WiFi or during network initialization")
if (BuildConfig.DEBUG) {
Logger.d(TAG, "✅ SyncWorker.doWork() SUCCESS (silent skip)")
Logger.d(TAG, "═══════════════════════════════════════")
}
// Success zurückgeben (kein Fehler, Server ist halt nicht erreichbar)
return@withContext Result.success()
}
if (BuildConfig.DEBUG) {
Logger.d(TAG, "📍 Step 3: Server reachable - proceeding with sync")
Logger.d(TAG, " SyncService: $syncService") Logger.d(TAG, " SyncService: $syncService")
} }
@@ -73,13 +94,13 @@ class SyncWorker(
} }
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Logger.d(TAG, "📍 Step 3: Processing result") Logger.d(TAG, "📍 Step 4: Processing result")
Logger.d(TAG, "📦 Sync result: success=${result.isSuccess}, count=${result.syncedCount}, error=${result.errorMessage}") Logger.d(TAG, "📦 Sync result: success=${result.isSuccess}, count=${result.syncedCount}, error=${result.errorMessage}")
} }
if (result.isSuccess) { if (result.isSuccess) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Logger.d(TAG, "📍 Step 4: Success path") Logger.d(TAG, "📍 Step 5: Success path")
} }
Logger.i(TAG, "✅ Sync successful: ${result.syncedCount} notes") Logger.i(TAG, "✅ Sync successful: ${result.syncedCount} notes")
@@ -109,7 +130,7 @@ class SyncWorker(
Result.success() Result.success()
} else { } else {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Logger.d(TAG, "📍 Step 4: Failure path") Logger.d(TAG, "📍 Step 5: Failure path")
} }
Logger.e(TAG, "❌ Sync failed: ${result.errorMessage}") Logger.e(TAG, "❌ Sync failed: ${result.errorMessage}")
NotificationHelper.showSyncError( NotificationHelper.showSyncError(

View File

@@ -20,6 +20,7 @@ import java.net.InetSocketAddress
import java.net.NetworkInterface import java.net.NetworkInterface
import java.net.Proxy import java.net.Proxy
import java.net.Socket import java.net.Socket
import java.net.URL
import javax.net.SocketFactory import javax.net.SocketFactory
class WebDavSyncService(private val context: Context) { class WebDavSyncService(private val context: Context) {
@@ -188,6 +189,40 @@ class WebDavSyncService(private val context: Context) {
return prefs.getString(Constants.KEY_SERVER_URL, null) return prefs.getString(Constants.KEY_SERVER_URL, null)
} }
/**
* Prüft ob WebDAV-Server erreichbar ist (ohne Sync zu starten)
* Verwendet Socket-Check für schnelle Erreichbarkeitsprüfung
*
* @return true wenn Server erreichbar ist, false sonst
*/
suspend fun isServerReachable(): Boolean = withContext(Dispatchers.IO) {
return@withContext try {
val serverUrl = getServerUrl()
if (serverUrl == null) {
Logger.d(TAG, "❌ Server URL not configured")
return@withContext false
}
val url = URL(serverUrl)
val host = url.host
val port = if (url.port > 0) url.port else url.defaultPort
Logger.d(TAG, "🔍 Checking server reachability: $host:$port")
// Socket-Check mit 2s Timeout
// Gibt dem Netzwerk Zeit für Initialisierung (DHCP, Routing, Gateway)
val socket = Socket()
socket.connect(InetSocketAddress(host, port), 2000)
socket.close()
Logger.d(TAG, "✅ Server is reachable")
true
} catch (e: Exception) {
Logger.d(TAG, "❌ Server not reachable: ${e.message}")
false
}
}
suspend fun testConnection(): SyncResult = withContext(Dispatchers.IO) { suspend fun testConnection(): SyncResult = withContext(Dispatchers.IO) {
return@withContext try { return@withContext try {
val sardine = getSardine() ?: return@withContext SyncResult( val sardine = getSardine() ?: return@withContext SyncResult(

View File

@@ -6,12 +6,15 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Handler
import android.os.Looper
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import dev.dettmer.simplenotes.MainActivity import dev.dettmer.simplenotes.MainActivity
object NotificationHelper { object NotificationHelper {
private const val TAG = "NotificationHelper"
private const val CHANNEL_ID = "notes_sync_channel" private const val CHANNEL_ID = "notes_sync_channel"
private const val CHANNEL_NAME = "Notizen Synchronisierung" private const val CHANNEL_NAME = "Notizen Synchronisierung"
private const val CHANNEL_DESCRIPTION = "Benachrichtigungen über Sync-Status" private const val CHANNEL_DESCRIPTION = "Benachrichtigungen über Sync-Status"
@@ -38,6 +41,17 @@ object NotificationHelper {
} }
} }
/**
* Löscht alle Sync-Notifications
* Sollte beim App-Start aufgerufen werden um alte Notifications zu entfernen
*/
fun clearSyncNotifications(context: Context) {
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE)
as NotificationManager
manager.cancel(SYNC_NOTIFICATION_ID)
Logger.d(TAG, "🗑️ Cleared old sync notifications")
}
/** /**
* Zeigt Erfolgs-Notification nach Sync * Zeigt Erfolgs-Notification nach Sync
*/ */
@@ -240,6 +254,7 @@ object NotificationHelper {
/** /**
* Zeigt Fehler-Notification * Zeigt Fehler-Notification
* Auto-Cancel nach 30 Sekunden
*/ */
fun showSyncError(context: Context, message: String) { fun showSyncError(context: Context, message: String) {
// PendingIntent für App-Öffnung // PendingIntent für App-Öffnung
@@ -266,5 +281,11 @@ object NotificationHelper {
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) val manager = context.getSystemService(Context.NOTIFICATION_SERVICE)
as NotificationManager as NotificationManager
manager.notify(SYNC_NOTIFICATION_ID, notification) manager.notify(SYNC_NOTIFICATION_ID, notification)
// ⭐ NEU: Auto-Cancel nach 30 Sekunden
Handler(Looper.getMainLooper()).postDelayed({
manager.cancel(SYNC_NOTIFICATION_ID)
Logger.d(TAG, "🗑️ Auto-cancelled error notification after 30s timeout")
}, 30_000)
} }
} }

View File

@@ -0,0 +1,20 @@
🐛 Bugfixes v1.1.1
✅ Keine Fehler-Notifications mehr in fremden WiFi-Netzwerken!
- Server-Erreichbarkeits-Check vor jedem Sync (2s Timeout)
- Stiller Abbruch wenn Server nicht erreichbar
- 80% schnellerer Abbruch: 2s statt 10+ Sekunden
✅ Keine Fehler beim WiFi-Connect / Nach-Hause-Kommen!
- Pre-Check wartet bis Netzwerk bereit ist (DHCP, Routing, Gateway)
- Kein Fehler mehr bei Netzwerk-Initialisierung
🔧 Notification-Verbesserungen:
- Alte Notifications werden beim App-Start gelöscht
- Fehler-Notifications verschwinden automatisch nach 30 Sekunden
- Bessere Batterie-Effizienz (keine langen Timeouts mehr)
📱 UI-Fixes:
- Sync-Icon wird nicht mehr angezeigt wenn Sync nicht konfiguriert ist
- Swipe-to-Delete: Kein Flackern mehr beim schnellen Löschen mehrerer Notizen
- Nach dem Speichern einer Notiz landet man automatisch ganz oben in der Liste

View File

@@ -0,0 +1,20 @@
🐛 Bugfixes v1.1.1
✅ No more error notifications in foreign WiFi networks!
- Server reachability check before each sync (2s timeout)
- Silent abort when server is unreachable
- 80% faster abort: 2s instead of 10+ seconds
✅ No more errors when connecting to WiFi / arriving home!
- Pre-check waits until network is ready (DHCP, routing, gateway)
- No more errors during network initialization
🔧 Notification improvements:
- Old notifications are cleared on app start
- Error notifications disappear automatically after 30 seconds
- Better battery efficiency (no more long timeouts)
📱 UI fixes:
- Sync icon no longer shown when sync is not configured
- Swipe-to-delete: No more flickering when quickly deleting multiple notes
- After saving a note, you automatically land at the top of the list

View File

@@ -0,0 +1,37 @@
Simple Notes Sync is a minimalist note-taking app with WebDAV synchronization.
KEY FEATURES:
• Create and edit simple notes
• WebDAV synchronization with your own server
• Automatic synchronization on home WiFi
• Configurable sync interval (15/30/60 minutes)
• Transparent battery usage display
• Material Design 3 with Dynamic Colors (Android 12+)
• Swipe-to-delete with confirmation dialog
• Server backup & restore
• Fully usable offline
• No ads, no trackers
PRIVACY:
Your data stays with you! The app only communicates with your own WebDAV server. No cloud services, no tracking libraries, no analytics tools.
SYNCHRONIZATION:
• Supports all WebDAV servers (Nextcloud, ownCloud, etc.)
• Configurable interval: 15, 30, or 60 minutes
• Measured battery consumption: only ~0.4% per day (at 30min)
• Doze Mode optimized for reliable background syncs
• Manual synchronization available anytime
• Conflict-free merging through timestamps
MATERIAL DESIGN 3:
• Modern user interface
• Dynamic Colors (Material You) on Android 12+
• Dark Mode support
• Intuitive gestures (Swipe-to-delete)
Open Source under MIT License
Source code: https://github.com/inventory69/simple-notes-sync

View File

@@ -0,0 +1 @@
Simple note-taking app with WebDAV synchronization

View File

@@ -0,0 +1 @@
Simple Notes Sync

View File

@@ -0,0 +1,21 @@
# Android Signing Configuration
#
# ANLEITUNG FÜR LOKALE BUILDS:
# 1. Kopiere diese Datei nach "key.properties" (ohne .example)
# 2. Fülle die Werte mit deinen echten Keystore-Daten aus
# 3. Die key.properties Datei ist in .gitignore und wird NICHT committet
#
# WICHTIG: Diese Datei darf NIEMALS ins Git-Repository gelangen!
# Sie enthält sensible Signing-Keys für die App-Veröffentlichung.
# Pfad zum Keystore (relativ zum android/app Ordner)
storeFile=simple-notes-release.jks
# Keystore Password
storePassword=DEIN_KEYSTORE_PASSWORD
# Key Alias (meist "key0" oder ein selbst gewählter Name)
keyAlias=DEIN_KEY_ALIAS
# Key Password
keyPassword=DEIN_KEY_PASSWORD

192
android/scripts/README.md Normal file
View File

@@ -0,0 +1,192 @@
# Android Build Scripts
Nützliche Scripts für die lokale Entwicklung und Release-Erstellung.
## 📜 Verfügbare Scripts
### 1. `create-keystore.fish` - Neuen Release-Keystore erstellen
**Wann verwenden:**
- ✅ Erstmaliges Setup des Projekts
- ✅ Keystore-Passwort vergessen
- ✅ Keystore beschädigt oder verloren
-**NICHT** verwenden, wenn bereits User existieren (macht alte APKs inkompatibel!)
**Verwendung:**
```bash
cd /home/liq/gitProjects/simple-notes-sync/android
./scripts/create-keystore.fish
```
**Das Script:**
1. Erstellt einen neuen 4096-Bit RSA-Keystore
2. Generiert `app/simple-notes-release.jks`
3. Erstellt `key.properties` mit den Zugangsdaten
4. Zeigt Base64-kodierten Keystore für GitHub Secrets
5. Gibt SHA256-Fingerprint zur Verifikation aus
**Output:**
-`app/simple-notes-release.jks` - Der Keystore
-`key.properties` - Lokale Signing-Konfiguration
- 📋 GitHub Secrets zum Kopieren
---
### 2. `verify-secrets.fish` - GitHub Secrets & Keystore verifizieren
**Wann verwenden:**
- ✅ Nach `create-keystore.fish` zur Verifikation
- ✅ Vor einem Release-Build zum Troubleshooting
- ✅ Um zu prüfen ob alles korrekt konfiguriert ist
**Verwendung:**
```bash
cd /home/liq/gitProjects/simple-notes-sync/android
./scripts/verify-secrets.fish
```
**Das Script prüft:**
- GitHub CLI Installation & Authentifizierung
- Ob alle 4 erforderlichen GitHub Secrets gesetzt sind
- Ob `key.properties` lokal existiert
- Ob der Keystore existiert
- Zeigt SHA256-Fingerprint des Keystores
**Output:**
- ✅ Status aller Secrets
- ✅ Status der lokalen Konfiguration
- 💡 Empfehlungen bei Problemen
---
### 3. `build-release-local.fish` - Lokal signierte Release-APKs bauen
**Wann verwenden:**
- ✅ Lokale Test-APKs erstellen, die mit Releases kompatibel sind
- ✅ APKs vor dem GitHub Release testen
- ✅ Schneller als GitHub Actions für Tests
**Voraussetzung:**
- `key.properties` muss existieren (via `create-keystore.fish` erstellt)
**Verwendung:**
```bash
cd /home/liq/gitProjects/simple-notes-sync/android
./scripts/build-release-local.fish
```
**Interaktive Auswahl:**
1. Standard Flavor (empfohlen)
2. F-Droid Flavor
3. Beide Flavors
**Output:**
- `app/build/outputs/apk/standard/release/` - Signierte Standard APKs
- `app/build/outputs/apk/fdroid/release/` - Signierte F-Droid APKs
---
## 🚀 Kompletter Workflow (von 0 auf Release)
### Erstmaliges Setup
```bash
cd /home/liq/gitProjects/simple-notes-sync/android
# 1. Keystore erstellen (mit automatischer GitHub Secrets-Konfiguration!)
./scripts/create-keystore.fish
# → Folge den Anweisungen, speichere die Passwörter!
# → GitHub Secrets werden automatisch via GitHub CLI gesetzt
# 2. Verifiziere die Konfiguration
./scripts/verify-secrets.fish
# → Prüft ob alle Secrets gesetzt sind
# → Zeigt Keystore-Informationen
# 3. Teste lokalen Build
./scripts/build-release-local.fish
# → Wähle "1" für Standard Flavor
# 4. Verifiziere Signatur
keytool -printcert -jarfile app/build/outputs/apk/standard/release/app-standard-universal-release.apk
```
### Vor jedem Release
```bash
# 1. Code committen und pushen
git add .
git commit -m "✨ Neue Features"
git push origin main
# 2. GitHub Actions erstellt automatisch Release
# → Workflow läuft: .github/workflows/build-production-apk.yml
# → Erstellt Release mit signierten APKs
# Optional: Lokalen Test-Build vorher
./scripts/build-release-local.fish
```
---
## 🔐 Sicherheitshinweise
### ⚠️ Diese Dateien NIEMALS committen:
- `key.properties` - Enthält Keystore-Passwörter
- `*.jks` / `*.keystore` - Der Keystore selbst
- Beide sind bereits in `.gitignore`
### ✅ Diese Werte sicher speichern:
- Keystore-Passwort
- Key-Alias
- Key-Passwort
- Base64-kodierter Keystore (für GitHub Secrets)
**Empfehlung:** Nutze einen Passwort-Manager (Bitwarden, 1Password, etc.)
---
## 🛠️ Troubleshooting
### "Keystore not found"
```bash
# Prüfe ob Keystore existiert
ls -la app/simple-notes-release.jks
# Falls nicht: Neu erstellen
./scripts/create-keystore.fish
```
### "key.properties not found"
```bash
# Prüfe ob Datei existiert
ls -la key.properties
# Falls nicht: Keystore neu erstellen oder manuell anlegen
./scripts/create-keystore.fish
```
### "Signature mismatch" beim App-Update
**Problem:** Lokaler Build hat andere Signatur als GitHub Release
**Ursache:** Unterschiedliche Keystores oder Passwörter
**Lösung:**
1. Vergleiche SHA256-Fingerprints:
```bash
# Lokal
keytool -list -v -keystore app/simple-notes-release.jks
# GitHub Release-APK
keytool -printcert -jarfile ~/Downloads/simple-notes-v1.1.0.apk
```
2. Müssen **identisch** sein!
3. Falls nicht: GitHub Secrets mit lokaler `key.properties` synchronisieren
---
## 📚 Weitere Dokumentation
- `../LOCAL_BUILDS.md` - Detaillierte Anleitung für lokale Builds
- `../.github/workflows/build-production-apk.yml` - GitHub Actions Workflow
- `../app/build.gradle.kts` - Build-Konfiguration

View File

@@ -0,0 +1,104 @@
#!/usr/bin/env fish
# Simple Notes Sync - Lokaler Release Build
# Erstellt signierte APKs, die mit GitHub Release-APKs kompatibel sind
set -l SCRIPT_DIR (dirname (status --current-filename))
set -l ANDROID_DIR (realpath "$SCRIPT_DIR/..")
set -l KEY_PROPERTIES "$ANDROID_DIR/key.properties"
echo "🔨 Simple Notes Sync - Lokaler Release Build"
echo ""
# 1. Prüfe ob key.properties existiert
if not test -f "$KEY_PROPERTIES"
echo "❌ Fehler: key.properties nicht gefunden!"
echo ""
echo "Bitte erstelle die Datei:"
echo " cd $ANDROID_DIR"
echo " cp key.properties.example key.properties"
echo ""
echo "Und fülle sie mit den echten Keystore-Daten aus."
echo "Siehe: android/LOCAL_BUILDS.md"
exit 1
end
# 2. Prüfe ob Keystore existiert
set -l KEYSTORE "$ANDROID_DIR/app/simple-notes-release.jks"
if not test -f "$KEYSTORE"
echo "❌ Fehler: Keystore nicht gefunden!"
echo " Erwartet: $KEYSTORE"
exit 1
end
echo "✅ key.properties gefunden"
echo "✅ Keystore gefunden"
echo ""
# 3. Build-Typ abfragen
echo "Welchen Build möchtest du erstellen?"
echo " 1) Standard Flavor (empfohlen)"
echo " 2) F-Droid Flavor"
echo " 3) Beide Flavors"
echo ""
read -P "Auswahl [1-3]: " -n 1 choice
echo ""
echo ""
switch $choice
case 1
echo "🏗️ Baue Standard Release APKs..."
cd "$ANDROID_DIR"
./gradlew assembleStandardRelease --no-daemon
if test $status -eq 0
echo ""
echo "✅ Build erfolgreich!"
echo ""
echo "📦 APKs findest du hier:"
echo " $ANDROID_DIR/app/build/outputs/apk/standard/release/"
ls -lh "$ANDROID_DIR/app/build/outputs/apk/standard/release/"*.apk
end
case 2
echo "🏗️ Baue F-Droid Release APKs..."
cd "$ANDROID_DIR"
./gradlew assembleFdroidRelease --no-daemon
if test $status -eq 0
echo ""
echo "✅ Build erfolgreich!"
echo ""
echo "📦 APKs findest du hier:"
echo " $ANDROID_DIR/app/build/outputs/apk/fdroid/release/"
ls -lh "$ANDROID_DIR/app/build/outputs/apk/fdroid/release/"*.apk
end
case 3
echo "🏗️ Baue Standard + F-Droid Release APKs..."
cd "$ANDROID_DIR"
./gradlew assembleStandardRelease assembleFdroidRelease --no-daemon
if test $status -eq 0
echo ""
echo "✅ Build erfolgreich!"
echo ""
echo "📦 Standard APKs:"
echo " $ANDROID_DIR/app/build/outputs/apk/standard/release/"
ls -lh "$ANDROID_DIR/app/build/outputs/apk/standard/release/"*.apk
echo ""
echo "📦 F-Droid APKs:"
echo " $ANDROID_DIR/app/build/outputs/apk/fdroid/release/"
ls -lh "$ANDROID_DIR/app/build/outputs/apk/fdroid/release/"*.apk
end
case '*'
echo "❌ Ungültige Auswahl"
exit 1
end
echo ""
echo "💡 Tipp: Du kannst die APK per HTTP Server verteilen:"
echo " cd app/build/outputs/apk/standard/release"
echo " python3 -m http.server 8892"

View File

@@ -0,0 +1,279 @@
#!/usr/bin/env fish
# Simple Notes Sync - Keystore Generator
# Erstellt einen neuen Release-Keystore für App-Signierung
set -l SCRIPT_DIR (dirname (status --current-filename))
set -l ANDROID_DIR (realpath "$SCRIPT_DIR/..")
set -l KEYSTORE_PATH "$ANDROID_DIR/app/simple-notes-release.jks"
set -l KEY_PROPERTIES "$ANDROID_DIR/key.properties"
echo "🔐 Simple Notes Sync - Keystore Generator"
echo ""
echo "⚠️ WICHTIG: Dieser Keystore wird für alle zukünftigen App-Releases verwendet!"
echo " Speichere die Zugangsdaten sicher ab (z.B. in einem Passwort-Manager)."
echo ""
# Prüfe ob Keystore bereits existiert
if test -f "$KEYSTORE_PATH"
echo "⚠️ Ein Keystore existiert bereits:"
echo " $KEYSTORE_PATH"
echo ""
read -P "Möchtest du ihn überschreiben? (Dies macht alte APKs inkompatibel!) [j/N]: " -n 1 overwrite
echo ""
if not string match -qi "j" $overwrite
echo "❌ Abgebrochen."
exit 1
end
echo "🗑️ Lösche alten Keystore..."
rm "$KEYSTORE_PATH"
end
echo ""
echo "📝 Bitte gib die folgenden Informationen ein:"
echo ""
# App-Informationen sammeln
read -P "Dein Name (z.B. 'Max Mustermann'): " developer_name
read -P "Organisation (z.B. 'dettmer.dev'): " organization
read -P "Stadt: " city
read -P "Land (z.B. 'DE'): " country
echo ""
echo "🔒 Keystore-Passwörter:"
echo ""
echo "Möchtest du sichere Passwörter automatisch generieren lassen?"
read -P "[J/n]: " -n 1 auto_generate
echo ""
if string match -qi "n" $auto_generate
# Manuelle Passwort-Eingabe
echo ""
echo "📝 Manuelle Passwort-Eingabe:"
echo ""
while true
read -sP "Keystore-Passwort: " keystore_password
echo ""
read -sP "Keystore-Passwort (Bestätigung): " keystore_password_confirm
echo ""
if test "$keystore_password" = "$keystore_password_confirm"
break
else
echo "❌ Passwörter stimmen nicht überein. Bitte erneut eingeben."
echo ""
end
end
while true
read -sP "Key-Passwort: " key_password
echo ""
read -sP "Key-Passwort (Bestätigung): " key_password_confirm
echo ""
if test "$key_password" = "$key_password_confirm"
break
else
echo "❌ Passwörter stimmen nicht überein. Bitte erneut eingeben."
echo ""
end
end
else
# Automatische Passwort-Generierung
echo ""
echo "🔐 Generiere sichere Passwörter (32 Zeichen, alphanumerisch)..."
# Generiere sichere, zufällige Passwörter (alphanumerisch, 32 Zeichen)
set keystore_password (openssl rand -base64 32 | tr -d '/+=' | head -c 32)
set key_password (openssl rand -base64 32 | tr -d '/+=' | head -c 32)
echo "✅ Passwörter generiert"
echo ""
echo "⚠️ WICHTIG: Speichere diese Passwörter jetzt in deinem Passwort-Manager!"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Keystore-Passwort: $keystore_password"
echo "Key-Passwort: $key_password"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
read -P "Passwörter gespeichert? Drücke Enter zum Fortfahren..."
end
set -l key_alias "simple-notes-key"
echo ""
echo "🏗️ Erstelle Keystore..."
echo ""
# Keystore erstellen
keytool -genkey \
-v \
-keystore "$KEYSTORE_PATH" \
-alias "$key_alias" \
-keyalg RSA \
-keysize 4096 \
-validity 10000 \
-storepass "$keystore_password" \
-keypass "$key_password" \
-dname "CN=$developer_name, OU=Simple Notes Sync, O=$organization, L=$city, C=$country"
if test $status -ne 0
echo ""
echo "❌ Fehler beim Erstellen des Keystores!"
exit 1
end
echo ""
echo "✅ Keystore erfolgreich erstellt!"
echo ""
# key.properties erstellen
echo "📝 Erstelle key.properties..."
echo "storeFile=simple-notes-release.jks" > "$KEY_PROPERTIES"
echo "storePassword=$keystore_password" >> "$KEY_PROPERTIES"
echo "keyAlias=$key_alias" >> "$KEY_PROPERTIES"
echo "keyPassword=$key_password" >> "$KEY_PROPERTIES"
echo "✅ key.properties erstellt"
echo ""
# Keystore-Info anzeigen
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📋 KEYSTORE-INFORMATIONEN - SICHER SPEICHERN!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Keystore-Pfad: $KEYSTORE_PATH"
echo "Key-Alias: $key_alias"
echo "Keystore-Passwort: $keystore_password"
echo "Key-Passwort: $key_password"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Base64-kodierten Keystore für GitHub Secrets
echo "🔐 Base64-kodierter Keystore für GitHub Secrets:"
echo ""
set -l keystore_base64 (base64 -w 0 "$KEYSTORE_PATH")
echo "$keystore_base64"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# GitHub Secrets konfigurieren
echo "<22> GitHub Secrets konfigurieren..."
echo ""
# Prüfe ob GitHub CLI installiert ist
if not command -v gh &> /dev/null
echo "⚠️ GitHub CLI (gh) nicht gefunden!"
echo ""
echo "📝 Manuelle Konfiguration erforderlich:"
echo ""
echo "1. Gehe zu: https://github.com/inentory69/simple-notes-sync/settings/secrets/actions"
echo "2. Erstelle/Aktualisiere folgende Secrets:"
echo ""
echo " KEYSTORE_BASE64:"
echo " $keystore_base64"
echo ""
echo " KEYSTORE_PASSWORD:"
echo " $keystore_password"
echo ""
echo " KEY_ALIAS:"
echo " $key_alias"
echo ""
echo " KEY_PASSWORD:"
echo " $key_password"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
else
# GitHub CLI verfügbar - automatisch Secrets erstellen
echo "✅ GitHub CLI gefunden"
echo ""
# Prüfe ob authentifiziert
if not gh auth status &> /dev/null
echo "⚠️ Nicht bei GitHub authentifiziert!"
echo ""
read -P "Möchtest du dich jetzt authentifizieren? [j/N]: " -n 1 do_auth
echo ""
if string match -qi "j" $do_auth
gh auth login
else
echo "❌ Überspringe automatische Secret-Konfiguration"
echo ""
echo "📝 Manuelle Konfiguration erforderlich (siehe oben)"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
return
end
end
echo "🔐 Erstelle/Aktualisiere GitHub Secrets..."
echo ""
set -l repo "inventory69/simple-notes-sync"
# KEYSTORE_BASE64
echo "$keystore_base64" | gh secret set KEYSTORE_BASE64 --repo $repo
if test $status -eq 0
echo "✅ KEYSTORE_BASE64 gesetzt"
else
echo "❌ Fehler beim Setzen von KEYSTORE_BASE64"
end
# KEYSTORE_PASSWORD
echo "$keystore_password" | gh secret set KEYSTORE_PASSWORD --repo $repo
if test $status -eq 0
echo "✅ KEYSTORE_PASSWORD gesetzt"
else
echo "❌ Fehler beim Setzen von KEYSTORE_PASSWORD"
end
# KEY_ALIAS
echo "$key_alias" | gh secret set KEY_ALIAS --repo $repo
if test $status -eq 0
echo "✅ KEY_ALIAS gesetzt"
else
echo "❌ Fehler beim Setzen von KEY_ALIAS"
end
# KEY_PASSWORD
echo "$key_password" | gh secret set KEY_PASSWORD --repo $repo
if test $status -eq 0
echo "✅ KEY_PASSWORD gesetzt"
else
echo "❌ Fehler beim Setzen von KEY_PASSWORD"
end
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "✅ GitHub Secrets erfolgreich konfiguriert!"
echo ""
echo "🔍 Verifizieren:"
echo " gh secret list --repo $repo"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
end
# Signatur-Fingerprint anzeigen
echo "🔑 SHA256-Fingerprint (zur Verifikation):"
keytool -list -v -keystore "$KEYSTORE_PATH" -storepass "$keystore_password" | grep "SHA256:"
echo ""
echo "✅ Setup abgeschlossen!"
echo ""
echo "💡 Nächste Schritte:"
echo " 1. Speichere die obigen Informationen in einem Passwort-Manager"
echo " 2. Konfiguriere die GitHub Secrets (siehe oben)"
echo " 3. Teste den lokalen Build:"
echo " cd $ANDROID_DIR"
echo " ./gradlew assembleStandardRelease"
echo ""

View File

@@ -0,0 +1,150 @@
#!/usr/bin/env fish
# Simple Notes Sync - GitHub Secrets Verifier
# Verifiziert ob die GitHub Secrets korrekt konfiguriert sind
set -l repo "inventory69/simple-notes-sync"
echo "🔍 GitHub Secrets Verifier"
echo ""
# Prüfe ob GitHub CLI installiert ist
if not command -v gh &> /dev/null
echo "❌ GitHub CLI (gh) nicht gefunden!"
echo ""
echo "Installation:"
echo " Arch Linux: sudo pacman -S github-cli"
echo " Ubuntu: sudo apt install gh"
echo " macOS: brew install gh"
echo ""
exit 1
end
# Prüfe Authentifizierung
if not gh auth status &> /dev/null
echo "❌ Nicht bei GitHub authentifiziert!"
echo ""
echo "Authentifizierung starten:"
echo " gh auth login"
echo ""
exit 1
end
echo "✅ GitHub CLI authentifiziert"
echo ""
# Liste alle Secrets auf
echo "📋 Konfigurierte Secrets für $repo:"
echo ""
gh secret list --repo $repo
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Prüfe ob alle erforderlichen Secrets vorhanden sind
set -l required_secrets "KEYSTORE_BASE64" "KEYSTORE_PASSWORD" "KEY_ALIAS" "KEY_PASSWORD"
set -l missing_secrets
for secret in $required_secrets
if not gh secret list --repo $repo | grep -q "^$secret"
set -a missing_secrets $secret
end
end
if test (count $missing_secrets) -gt 0
echo "❌ Fehlende Secrets:"
for secret in $missing_secrets
echo " - $secret"
end
echo ""
echo "💡 Tipp: Führe create-keystore.fish aus, um die Secrets zu erstellen"
else
echo "✅ Alle erforderlichen Secrets sind konfiguriert!"
echo ""
echo "Required Secrets:"
for secret in $required_secrets
echo "$secret"
end
end
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Prüfe ob key.properties lokal existiert
set -l SCRIPT_DIR (dirname (status --current-filename))
set -l ANDROID_DIR (realpath "$SCRIPT_DIR/..")
set -l KEY_PROPERTIES "$ANDROID_DIR/key.properties"
if test -f "$KEY_PROPERTIES"
echo "✅ Lokale key.properties gefunden: $KEY_PROPERTIES"
echo ""
echo "📋 Inhalt (Passwörter verborgen):"
cat "$KEY_PROPERTIES" | sed 's/\(Password=\).*/\1***HIDDEN***/g'
else
echo "⚠️ Lokale key.properties nicht gefunden: $KEY_PROPERTIES"
echo ""
echo "💡 Tipp: Führe create-keystore.fish aus, um sie zu erstellen"
end
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Prüfe ob Keystore existiert
set -l KEYSTORE "$ANDROID_DIR/app/simple-notes-release.jks"
if test -f "$KEYSTORE"
echo "✅ Keystore gefunden: $KEYSTORE"
# Zeige Keystore-Info (wenn key.properties existiert)
if test -f "$KEY_PROPERTIES"
set -l store_password (grep "storePassword=" "$KEY_PROPERTIES" | cut -d'=' -f2)
echo ""
echo "🔑 Keystore-Informationen:"
keytool -list -v -keystore "$KEYSTORE" -storepass "$store_password" 2>/dev/null | grep -E "(Alias|Creation date|Valid|SHA256)" | head -10
end
else
echo "⚠️ Keystore nicht gefunden: $KEYSTORE"
echo ""
echo "💡 Tipp: Führe create-keystore.fish aus, um ihn zu erstellen"
end
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Zusammenfassung
set -l issues 0
if test (count $missing_secrets) -gt 0
set issues (math $issues + 1)
end
if not test -f "$KEY_PROPERTIES"
set issues (math $issues + 1)
end
if not test -f "$KEYSTORE"
set issues (math $issues + 1)
end
if test $issues -eq 0
echo "🎉 Alles konfiguriert! Du kannst jetzt Releases erstellen."
echo ""
echo "🚀 Nächste Schritte:"
echo " 1. Lokalen Build testen:"
echo " ./scripts/build-release-local.fish"
echo ""
echo " 2. Code committen und pushen:"
echo " git push origin main"
echo ""
echo " 3. GitHub Actions erstellt automatisch Release"
else
echo "⚠️ $issues Problem(e) gefunden - siehe oben"
echo ""
echo "💡 Lösung: Führe create-keystore.fish aus"
end
echo ""

86
server/README.en.md Normal file
View File

@@ -0,0 +1,86 @@
# WebDAV Server - Simple Notes Sync
**🌍 Languages:** [Deutsch](README.md) · **English**
---
## Quick Start
```bash
# 1. Adjust environment variables
cp .env.example .env
nano .env
# 2. Start server
docker-compose up -d
# 3. Check logs
docker-compose logs -f
# 4. Test
curl -u noteuser:your_password http://localhost:8080/
```
## Server URL
**Local:** `http://localhost:8080/`
**On network:** `http://YOUR_IP:8080/` (e.g. `http://192.168.1.100:8080/`)
Find IP address:
```bash
ip addr show | grep "inet " | grep -v 127.0.0.1
```
## Credentials
Default (see `.env`):
- Username: `noteuser`
- Password: See `.env` file
## Management
```bash
# Check status
docker-compose ps
# View logs
docker-compose logs -f
# Restart
docker-compose restart
# Stop
docker-compose down
# Update
docker-compose pull
docker-compose up -d
```
## Data Location
Your notes are stored in: `./data/`
**Backup:**
```bash
# Create backup
tar -czf notes-backup-$(date +%Y%m%d).tar.gz data/
# Restore backup
tar -xzf notes-backup-YYYYMMDD.tar.gz
```
## External Access (HTTPS)
For access from outside your home network, use a reverse proxy like Caddy or nginx:
**Example with Caddy:**
```Caddyfile
notes.yourdomain.com {
reverse_proxy localhost:8080
}
```
**Important:** Always use HTTPS for external access to protect your credentials!

View File

@@ -1,5 +1,9 @@
# WebDAV Server - Simple Notes Sync # WebDAV Server - Simple Notes Sync
**🌍 Sprachen:** **Deutsch** · [English](README.en.md)
---
## Quick Start ## Quick Start
```bash ```bash