Skip to content

feat(app): make everything free and add voluntary Ko-fi support - #12

Merged
IgnacioLD merged 1 commit into
mainfrom
feat/free-for-all-kofi
Nov 10, 2026
Merged

IgnacioLD merged 1 commit into
mainfrom
feat/free-for-all-kofi

Conversation

@IgnacioLD

Copy link
Copy Markdown
Owner

Glosso is now free for everyone, forever. The paid tier is removed completely, not switched off. The app never sold anything (it always ran in the open beta where the server unlocked everything), so there are no paying users to migrate.

Removed

App (:shared, :androidApp, iOS)

  • shared/.../billing/* (AccessPolicy, AccessController, ProductAccess, SubscriptionRepository, SubscriptionStatus, BillingPeriods/Text, SubscriptionPlan), PaywallSheet, SubscriptionViewModel, iOS NoSubscriptions.
  • Every lock, "Pro" and "Free" badge and paywall entry in Lessons, Today, Stories, Exercises, free practice, You and Library. All levels, stories, exercises, personalized practice and the phonetic profile are simply available.
  • The beta / Beta Founder banner (Onboarding, You, About) and the founder line in "Change phone".
  • Play Billing: androidApp/.../billing/*, debug/release FakeBilling, FakeSubscriptionRepository, the BILLING permission, libs.billing, the verifyRelease*Entitlement tasks, the glossoBilling and glossoSubscriptionId build inputs, PlatformActions.openSubscriptionManagement, ScreenshotProtection, PlatformActivity.
  • 60 now-unused strings in all four locales (en, es, fr, de) plus the Play Billing error strings in androidApp/res.
  • Tests that only existed for this (AccessPolicy/Controller, ProductAccess, Paywall*, ContentGate, entitlement guards, FakeSubscriptionRepository, billing periods/copy...). Ordering coverage moved to CourseOrderTest.

Server

  • server/.../billing/*, POST /v1/billing/verify, POST /v1/billing/rtdn, the billing rate limit and RTDN auth gauge, plans/limits/pricing/founder config, paywall/purchase analytics events.
  • EntitlementService/Store/JdbcEntitlementStore become AccountService/AccountStore/JdbcAccountStore: device identity, Google sign-in, linked accounts and account moves (with profile merge) are kept. No new founder, grant or entitlement rows are written.
  • /v1/me and /v1/auth/google now return {"linkedAccounts":[...]}. /v1/config returns flags and client, plus fixed "mode":"BETA" and "beta":{"active":true} so already installed app builds (which lock A2+ unless the beta reads as open) keep everything unlocked.
  • No migration drops tables or deletes data. The legacy entitlements, lifetime_grants and beta_founder* data stay untouched; an account move still carries them over instead of letting the cascade delete them.

Added

  • KOFI_URL in shared/src/commonMain/kotlin/com/glosso/app/Support.kt (line 18), blank for now. While blank, the support entry is hidden everywhere.
  • "Support Glosso" section in About and a row at the bottom of You, opening the URL in the browser. Translated into en, es, fr, de.
  • SupportGlossoTest: hidden with a blank URL, shown with one, copy present in every locale.

Licensing

Runtime classpath lock and licenses.json refreshed (Play Billing, datatransport, Firebase encoders, play-services-location and javax.inject are gone). NOTICE now only names Google Play services and Google Identity (googleid). The Play In-App Review entry was removed too: it was in the curated catalogue and NOTICE but is not on the runtime classpath.

Tests

  • ./gradlew :engine:jvmTest: 463 passed, 0 failed
  • LC_ALL=en_US.UTF-8 ./gradlew :server:test: 347 passed, 0 failed (embedded Postgres included)
  • ./gradlew :androidApp:testDebugUnitTest: 283 passed, 0 failed
  • ./gradlew :shared:testDebugUnitTest: 142 passed, 0 failed
  • ./gradlew :androidApp:lintDebug: 0 errors, 73 warnings (all GradleDependency / AGP version / UseKtx / VectorPath / launcher icon, none in changed code)
  • ./gradlew :androidApp:assembleDebug and :shared:compileKotlinIosSimulatorArm64: OK
  • python3 -m unittest tools/licensing/collect_test.py: 24 passed
  • UI checked on an emulator at 1080x2340 / 480 dpi with a temporary dummy KOFI_URL: Today, Lessons (A1 and A2), You, Library, Settings and About show no lock or Pro UI; the support row opens the browser.

Env vars no longer used

GLOSSO_PLAY_SERVICE_ACCOUNT_JSON, GLOSSO_PLAY_SERVICE_ACCOUNT_JSON_BASE64, GLOSSO_PLAY_PACKAGE, GLOSSO_PLAY_PRODUCT_ID, GLOSSO_PLAY_TRIAL_OFFER_ID, GLOSSO_BILLING_CACHE_TTL_SECONDS, GLOSSO_RTDN_SECRET, GLOSSO_RTDN_SUBSCRIPTION, GLOSSO_PRODUCT_MODE, GLOSSO_BETA_END_DATE, GLOSSO_BETA_FOUNDER_ENABLED, GLOSSO_BETA_FOUNDER_UNTIL, GLOSSO_BETA_FOUNDER_BENEFIT_KIND, GLOSSO_BETA_FOUNDER_BENEFIT_VALUE, GLOSSO_BETA_FOUNDER_BENEFIT_LABEL, GLOSSO_PLANS_JSON, GLOSSO_LIMITS_JSON, GLOSSO_PRICING_JSON, GLOSSO_FLAG_BETA_BANNER, GLOSSO_FLAG_PRO, GLOSSO_FLAG_PERSONALIZED. Removed from deploy/docker-compose.yml; deploy/.env.example was not edited (not readable in this environment) and still lists them.

Glosso is free for everyone, forever. Remove the paid tier completely
instead of switching it off:

- app: drop the paywall, Pro locks and badges, AccessPolicy/AccessController,
  the subscription repository, Play Billing and its debug/release fakes, the
  release entitlement guard tasks and the glossoBilling/glossoSubscriptionId
  build inputs; every lesson, story, exercise, personalized practice and the
  phonetic profile is simply available. Remove the beta/founder banner.
- server: drop Play purchase verification and RTDN, plans, lifetime grants
  and Beta Founder marking. EntitlementService/Store become
  AccountService/Store and keep device identity, Google sign-in and account
  moves. /v1/config keeps mode=BETA and beta.active=true for older app builds.
  No migration drops tables or data.
- add a "Support Glosso" entry in About and You, hidden while KOFI_URL is
  blank, translated in every app language.
- refresh the license lock and catalogue, NOTICE, README and docs.
@IgnacioLD
IgnacioLD merged commit 760284d into main Nov 10, 2026
4 checks passed
@IgnacioLD
IgnacioLD deleted the feat/free-for-all-kofi branch November 10, 2026 13:48
IgnacioLD added a commit that referenced this pull request Nov 10, 2026
…mport from a file (#14)

## What

Prepares the Android app for F-Droid. The rules that apply, with
sources, are in `docs/FDROID.md`.

- **Product flavors** `playstore` (today's behaviour) and `fdroid` in
`:androidApp`.
- `androidx.credentials`, `credentials-play-services-auth` and
`googleid` move from `:shared` to `playstoreImplementation`.
  - `GoogleSignIn` moves to `androidApp/src/playstore`.
- `accountSignIn` in `:shared` is now a plain `var` that defaults to
`UnavailableSignIn`. Only the playstore `Flavor.install()` replaces it,
so "Change phone" is hidden in fdroid.
- **Usage statistics are off by default in fdroid** (Tracking
anti-feature: opt-in and disabled by default). Onboarding shows a new
notice saying so. Playstore is unchanged.
- **`dependenciesInfo.includeInApk = false`**. The Play-encrypted
dependency block is flagged by `fdroid scanner`. AABs keep it.
- **Import the on-device scoring model from a file**, in both flavors.
- New "Import from a file" action wherever the model download is offered
(Settings, Offline, onboarding).
  - Uses the system picker (SAF), so no storage permission is needed.
- Each picked file is copied while its SHA-256 is computed. It is
installed only if the digest matches the pinned digest of the released
pack or the one the server publishes, which is the same check a download
uses.
- File names do not matter. Anything else is deleted, and a localized
error is shown.
  - Lesson packs are not importable: they have no file format yet.
- **Licensing gate:** one runtime-classpath lock per flavor
(`tools/licensing/runtime-classpath-{fdroid,playstore}.txt`). Each
flavor's `preBuild` checks its own lock. The catalogue covers the union
of both, and `licenses.json` is unchanged.
- **F-Droid metadata:**
- `fastlane/metadata/android/{en-US,es-ES,fr-FR,de-DE}`: title, short
and full description, and `changelogs/2226.txt`.
  - Three en-US phone screenshots.
- Draft recipe `metadata/me.shirobyte42.glosso.yml` (AntiFeatures:
`TetheredNet`).
- **Docs:** README build instructions for both flavors, the NOTICE
Play-services entry, CONTRIBUTING, and task names that no longer exist
with flavors (`testDebugUnitTest`, `lintDebug`, `installDebug`,
`connectedDebugAndroidTest`).

## Proof

- **fdroid release runtime classpath** (`:androidApp:dependencies
--configuration fdroidReleaseRuntimeClasspath`):
- 0 matches for `com.google.android.gms`, `com.google.firebase`,
`com.google.android.libraries.identity`, `com.android.billingclient`,
`com.google.android.play`, `credentials-play-services`.
- The only `com.google` artifacts are `accompanist-drawablepainter` and
`guava:listenablefuture` (both Apache-2.0).
  - The playstore classpath has 32 matching lines.
- **fdroid release APK:** none of those packages appears in its dex.
`fdroid scanner` (fdroidserver 2.4.5, refreshed signatures) reports 0
problems.
- **Source scan:** the fdroidserver source scanner, run with `gradle:
[fdroid]` on the unmodified tree, reports 0 problems. As a control,
moving `googleid` to a plain `implementation` gives 1. So the recipe
needs no `prebuild` stripping.
- **Recipe:** `fdroid readmeta` and `fdroid lint` pass, using
fdroiddata's `config/`.

## Tests

| Check | Result |
|---|---|
| `:engine:jvmTest` | 463 passed |
| `LC_ALL=en_US.UTF-8 :server:test` | 347 passed |
| `:androidApp:testFdroidDebugUnitTest` | 292 passed |
| `:androidApp:testPlaystoreDebugUnitTest` | 291 passed |
| `:shared:testDebugUnitTest` | 142 passed |
| `:androidApp:lintFdroidDebug` | 0 errors, 73 warnings, the same 73 as
on the base branch |
| `:androidApp:assembleFdroidRelease`,
`:androidApp:assemblePlaystoreDebug` | OK |
| `:shared:compileKotlinIosSimulatorArm64` | OK |
| `gitleaks dir . --redact` | only the 2 known false positives |

New tests:
- `ModelPackImportTest`:
  - valid files in any order install;
  - a corrupt or truncated file is rejected and leaves nothing behind;
  - a partial pick reports the pack as incomplete;
  - an unreadable file leaves no partial copy.
- `FdroidFlavorTest`:
  - no sign-in is installed;
- no googleid, Play services or credentials-play-services class is on
the classpath;
  - statistics default to off.
- `PlaystoreFlavorTest`: Google sign-in is still wired.
- An `AnalyticsTest` case for the default-off build.

**UI check** on emulator-5558 (1080x2340, density 480), fdroid debug
build:
- the onboarding statistics-off notice shows;
- importing a corrupt file is rejected with the error;
- importing the real model plus vocabulary installs it ("Ready on this
device");
- Settings has no Account / "Change phone" section and the statistics
switch is off.

## Open decisions for the owner

- **TetheredNet:** an in-app server URL setting would remove it.
- **NonFreeAssets risk:** the licence of the lesson texts and audio
(glosso-data) needs to be stated.
- **Signing:** F-Droid signs with its own key (the default), or
developer-signed reproducible builds (`Binaries` +
`AllowedAPKSigningKeys`). This has to be decided before the first
F-Droid release.
- **Donate field:** left out of the recipe because the Ko-fi URL is
still empty.

https://claude.ai/code/session_01FcVpZoyr8VdRLp1QecX1Ng

Replaces #13, rebased onto main after #12 was squash-merged (same diff,
single commit).

https://claude.ai/code/session_01FcVpZoyr8VdRLp1QecX1Ng

Co-authored-by: IgnacioLD <178957758+IgnacioLD@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant