Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
bf681d3
Initial plan
Copilot Apr 5, 2026
3c76251
docs: add project overview and Android modernization roadmap
Copilot Apr 5, 2026
07913df
refactor: use try-with-resources when reading PO files
Copilot Apr 5, 2026
dc45c60
build: add Ant build entrypoint and SDK checks
Copilot Apr 5, 2026
0d33adc
wip: run full ant pipeline and capture dex failure
Copilot Apr 5, 2026
8801c44
build: produce full debug APK with modern SDK tools
Copilot Apr 5, 2026
47c3cbf
chore: apply final review polish for build/docs
Copilot Apr 5, 2026
d0f4385
build: finalize full ant apk pipeline and review fixes
Copilot Apr 5, 2026
477bb34
ci: add apk build workflow and release asset publishing
Copilot Apr 5, 2026
f8030e6
ci: make apk artifact paths workspace-relative
Copilot Apr 5, 2026
1fb711c
ui: first modernization pass and modern install compatibility updates
Copilot Apr 5, 2026
2f40ad4
chore: apply final validation feedback fixes
Copilot Apr 5, 2026
15b134f
chore: finalize review feedback on storage notice/docs
Copilot Apr 5, 2026
0b11daf
fix: avoid duplicate storage notice and clarify targetSdk rationale
Copilot Apr 5, 2026
90c6d5f
chore: scaffold initial Gradle migration with CI fallback
Copilot Apr 5, 2026
ddb847c
build: start gradle migration with debug package split and ci fallback
Copilot Apr 5, 2026
d5f2747
build: complete gradle-only migration and remove ant
Copilot Apr 5, 2026
be9d967
chore: address remaining review feedback
Copilot Apr 5, 2026
15d867a
fix: resolve gradle ci compile blockers and permission handling
Copilot Apr 5, 2026
f3843e0
fix: move manifest comment outside uses-sdk tag
Copilot Apr 5, 2026
5e9072b
feat: incremental UI and MainActivity modernization with smoke-test docs
Copilot Apr 5, 2026
b78e68e
feat: add guarded SAF open/save flow with legacy fallback
Copilot Apr 5, 2026
16b9a82
fix: address SAF review follow-ups and cleanup
Copilot Apr 5, 2026
be0f88c
Modernize manifest and target SDK to Android 14
Copilot Apr 5, 2026
5ba94f0
Update manifest permissions and docs for modern Android targeting
Copilot Apr 5, 2026
7b6a664
Harden SAF URI validation for content resolver security
Copilot Apr 5, 2026
3f11497
Sanitize SAF document URIs via DocumentsContract
Copilot Apr 5, 2026
7619273
Migrate MainActivity to Activity Result API and modernize Java level
Copilot Apr 6, 2026
c4ce4f9
Address review follow-ups for URI support and build config cleanup
Copilot Apr 6, 2026
44e534b
Apply final MainActivity cleanup from review feedback
Copilot Apr 6, 2026
ffdce5c
Migrate legacy fragments/preferences to AndroidX and modernize themes
Copilot Apr 6, 2026
4add8c3
Modernize core collections and cleanup legacy resource/correctness pa…
Copilot Apr 6, 2026
8038879
Plan CI minSdk compatibility fix
Copilot Apr 6, 2026
84bb6f6
Fix CI minSdk manifest merge by pinning AndroidX versions
Copilot Apr 6, 2026
c01d2bf
Raise minSdk to 24 and remove legacy file chooser/storage paths
Copilot Apr 6, 2026
88ff614
Finalize API 24 modernization cleanup and SAF hardening
Copilot Apr 6, 2026
24e8176
Exclude obsolete kotlin-stdlib-jdk7/jdk8 to fix duplicate class CI fa…
Copilot Apr 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/build-apk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: build-apk

on:
workflow_dispatch:
push:
branches:
- main
tags:
- 'v*'
pull_request:

permissions:
contents: write

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4.1.7

- name: Set up Java
uses: actions/setup-java@v4.2.1
with:
distribution: temurin
java-version: '17'

- name: Set up Android SDK
uses: android-actions/setup-android@v3.2.2

- name: Install required SDK components
run: |
yes | sdkmanager "platforms;android-34" "build-tools;34.0.0"

- name: Build debug APK
run: |
chmod +x ./gradlew
./gradlew :app:assembleDebug
mkdir -p bin
cp app/build/outputs/apk/debug/app-debug.apk bin/ve-debug.apk

- name: Upload APK artifact
uses: actions/upload-artifact@v4.3.3
with:
name: ve-debug-apk
path: ${{ github.workspace }}/bin/ve-debug.apk
if-no-files-found: error

- name: Publish release asset on tag
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v2.0.8
with:
files: |
${{ github.workspace }}/bin/ve-debug.apk
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
*~
bin/
gen/
obj/
.project
.settings/
.gradle/
app/build/
build/

res/values-*/strings.xml

various/play-featured-graphics.png

31 changes: 5 additions & 26 deletions AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.pryds.ve"
android:versionCode="8"
android:versionName="0.1.4" >
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >

<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="18" />

<application
android:allowBackup="true"
android:backupAgent="eu.pryds.ve.VeBackupAgent"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.Holo.Light" >
android:theme="@style/AppTheme" >

<meta-data
android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAILlof6VZJzaJUtfLSNUh6Add5cYguHu2b6I5K-Q" />

<activity
android:name="eu.pryds.ve.MainActivity"
android:exported="true"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand All @@ -33,27 +24,15 @@
</activity>
<activity
android:name="eu.pryds.ve.SettingsActivity"
android:exported="false"
android:label="@string/title_activity_settings"
android:parentActivityName="eu.pryds.ve.MainActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="eu.pryds.ve.MainActivity" />
</activity>
<activity
android:name="eu.pryds.ve.FileChooser"
android:label="@string/title_activity_file_chooser"
android:parentActivityName="eu.pryds.ve.MainActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="eu.pryds.ve.MainActivity" />
</activity>
<activity
android:name="eu.pryds.ve.DonateActivity"
android:exported="false"
android:label="@string/title_activity_donate"
android:parentActivityName="eu.pryds.ve.MainActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="eu.pryds.ve.MainActivity" />
</activity>
</application>

Expand Down
137 changes: 137 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,130 @@ Vé is named after one of [Odin's two brothers](http://en.wikipedia.org/wiki/Vil

Vé is open source software, released under the GPL licence, which means you can use it for free, for personal or other purposes. You can even redistribute it, modified or not, provided you do so under the same licence, which means you have to provide your source files for any changes. You can read the [full licence text here](https://github.com/pryds/ve/blob/master/COPYING).

Project structure
-----------------

At a high level, the app is made of:

* `MainActivity`: the main translation editor screen.
* `TranslatableString` and `TranslatableStringCollection`: PO parsing/writing model layer.
* `SettingsActivity`/`SettingsFragment`: translator metadata preferences.
* `res/`: layouts, strings, menus and drawables.

Manual smoke test (baseline)
----------------------------

Run this checklist after each small modernization change:

1. **Open PO file**
* Use **Open file** and load a valid `.po` file.
* Confirm original/translated text areas are populated.
2. **Navigate strings**
* Tap **Previous** and **Next**.
* Confirm string number and content update correctly.
3. **Edit translation**
* Change text in translated field.
* Confirm metadata counters update.
4. **Save file**
* Tap **Save file** and verify success toast.
* Reopen the file and verify edited text persists.
5. **Settings read/write**
* Open **Settings**, edit translator fields, save/back out.
* Return and confirm values persist and are used on save.

Current architecture map
------------------------

* **UI layer (Java + framework widgets)**:
* `MainActivity` drives the translation editor.
* `SettingsActivity` + `SettingsFragment` handle translator identity/preferences.
* **Model/parsing layer**:
* `TranslatableString` stores PO entry/header data.
* `TranslatableStringCollection` parses PO files and serializes back to PO output.
* **Resources/config**:
* `res/layout/activity_main.xml` is the primary editor screen.
* `res/menu/main_activity_actions.xml` defines top app actions.
* **Build system**:
* Gradle-based project (`settings.gradle`, root `build.gradle`, `app/build.gradle`).

Current limitations
-------------------

* File parsing/saving is still done on the main thread (can cause UI jank on large files).
* Storage now uses SAF document URIs for open/save flows.
* `MainActivity` contains multiple responsibilities (navigation, editing, persistence trigger, UI state).
* Automated tests are still limited; manual smoke testing remains important.

Modernization roadmap (incremental)
-----------------------------------

1. **UI polish without behavior changes**
* Improve spacing/typography/section labeling on main editor.
2. **MainActivity maintainability refactor**
* Cache views, split screen-update helpers, preserve current behavior.
3. **Editor UX improvements**
* Avoid unnecessary text resets, preserve cursor position, clarify plural-form state.
4. **Architecture and async improvements**
* Move parse/save work off main thread and continue decoupling UI/model responsibilities.

How to modernize Vé for newer Android versions
----------------------------------------------

This codebase now builds with Gradle, targets Android API level 34 (`targetSdk 34`), and uses a modern minimum API level 24 (`minSdk 24`).

1. **Maintain modern Android baseline**
* Keep dependencies and build tooling current.
2. **Continue modern platform cleanup**
* Replace remaining legacy utilities and disabled legacy features.
3. **Run compatibility validation**
* Test on recent Android versions/emulators (Android 12, 13, 14+) and verify open/edit/save PO flows.

How to improve the codebase
---------------------------

For the highest long-term quality and maintainability:

* Add automated testing (unit tests for PO parser and serialization, plus instrumentation tests for file open/save flows).
* Move parsing/saving work off the main thread to avoid UI jank on large files.
* Introduce architecture boundaries (for example ViewModel + repository-style separation) to reduce Activity complexity.
* Continue reducing synchronous file/parse work on UI thread and validate UX on large files.
* Add static quality gates (Android Lint, CI workflow, formatting/lint checks).
* Audit and remove dead/disabled features or fully modernize them (for example legacy in-app billing/backups).

Build/testing quick start (Gradle)
----------------------------------

This repository now uses Gradle (`gradlew`, root `build.gradle`, `app/build.gradle`) as the build system.

1. Configure an Android SDK path using one of:
* `local.properties` with `sdk.dir=/absolute/path/to/android-sdk`
* `ANDROID_HOME`
* `ANDROID_SDK_ROOT`
2. Ensure your SDK contains:
* `platforms;android-34`
* `build-tools;34.0.0`
* App currently uses `targetSdkVersion` 34 and `minSdk` 24.
* File open/save flows use SAF-based document URIs.
3. Run:
* `./gradlew :app:assembleDebug` to build a debug APK
* Output APK path: `app/build/outputs/apk/debug/app-debug.apk`
* Debug package name is `eu.pryds.ve.debug` (via `applicationIdSuffix ".debug"`), so it can install alongside release builds and avoid update/signature conflicts.

APK exposure for testing (artifact/release)
-------------------------------------------

This repository now includes GitHub Actions workflow:

* Workflow file: `.github/workflows/build-apk.yml`
* Trigger manually from **Actions → build-apk → Run workflow**, or via pull requests/pushes.
* Download the APK from workflow artifacts named **ve-debug-apk**.
* If you push a tag like `v0.1.5`, the workflow also uploads `ve-debug.apk` as a release asset.

Install notes for debug APKs
----------------------------

If Android shows **“invalid package”** while installing a debug APK over an existing install, uninstall the previously installed app variant first and then install the new APK. This typically happens when signatures differ between old and new builds, or when installing a build with a different applicationId/package variant.

Contributing to Vé
------------------

Expand All @@ -25,3 +149,16 @@ Donations of any amount that you see fit are kindly accepted through Bitcoin add

![Donate Bitcoins](https://raw.github.com/pryds/ve/master/various/ve-donations-qr.png)

Contributor workflow (step-by-step)
-----------------------------------

1. **Sync and branch**
* Pull latest changes and create a focused branch for one small change set.
2. **Build**
* Run `./gradlew :app:assembleDebug`.
3. **Run targeted checks**
* Run any relevant existing checks for your change area.
4. **Install and verify**
* Install debug APK and execute the **Manual smoke test (baseline)** checklist above.
5. **Submit small PR**
* Keep PR scope narrow (one modernization slice), include test notes and smoke-test results.
53 changes: 53 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
apply plugin: 'com.android.application'

android {
namespace 'eu.pryds.ve'
compileSdk 34

defaultConfig {
applicationId "eu.pryds.ve"
minSdk 24
targetSdk 34
versionCode 9
versionName "0.1.5"
}

sourceSets {
main {
manifest.srcFile '../AndroidManifest.xml'
java.srcDirs = ['../src']
res.srcDirs = ['../res']
assets.srcDirs = ['../assets']
jniLibs.srcDirs = ['../libs']
aidl.srcDirs = ['../src']
}
}

buildFeatures {
aidl true
}

buildTypes {
debug {
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), '../proguard-project.txt'
}
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}

dependencies {
implementation fileTree(dir: '../libs', include: ['*.jar'])
implementation 'androidx.activity:activity:1.9.3'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.fragment:fragment:1.8.2'
implementation 'androidx.preference:preference:1.2.1'
}
22 changes: 22 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
buildscript {
def agpVersion = project.findProperty("androidGradlePluginVersion") ?: "8.4.2"
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:${agpVersion}"
}
}

allprojects {
repositories {
google()
mavenCentral()
}

configurations.configureEach {
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
}
}
5 changes: 5 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8
android.useAndroidX=true
androidGradlePluginVersion=8.4.2
# Keep legacy switch-case over R.id working: nonFinalResIds=false => generate final IDs.
android.nonFinalResIds=false
Binary file added gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
7 changes: 7 additions & 0 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading
Loading