chore(release): beta packaging — signing, changelog, store metadata, scale test

- Android release signing reads a gitignored key.properties, falling back
  to debug keys so contributors can still build --release.
- CHANGELOG.md (human, newest-first) + Fastlane/F-Droid metadata (en/es)
  with descriptions derived from docs/intro.md.
- docs/release.md: the by-hand build + manual smoke checklist (no CI yet).
- inventory_scale_test guards the list query against N+1 at 3000 varieties.
This commit is contained in:
vjrj 2026-07-09 23:10:01 +02:00
parent 5d2b41a110
commit 4acf227e30
12 changed files with 260 additions and 4 deletions

View file

@ -1,3 +1,6 @@
import java.util.Properties
import java.io.FileInputStream
plugins {
id("com.android.application")
id("kotlin-android")
@ -5,6 +8,17 @@ plugins {
id("dev.flutter.flutter-gradle-plugin")
}
// Release signing is opt-in: drop a `key.properties` (gitignored) next to this
// module's android/ dir to sign release builds with your own keystore. Without
// it, release builds fall back to debug keys so contributors can still run
// `flutter run --release`. See docs/release.md.
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
val hasReleaseKeystore = keystorePropertiesFile.exists()
if (hasReleaseKeystore) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android {
namespace = "org.comunes.tane"
compileSdk = flutter.compileSdkVersion
@ -20,7 +34,6 @@ android {
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "org.comunes.tane"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
@ -30,11 +43,27 @@ android {
versionName = flutter.versionName
}
signingConfigs {
if (hasReleaseKeystore) {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = keystoreProperties["storeFile"]?.let { file(it) }
storePassword = keystoreProperties["storePassword"] as String
}
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
// Sign with the real release keystore when key.properties is present,
// otherwise fall back to debug keys so `flutter run --release` works
// for contributors without the signing material.
signingConfig = if (hasReleaseKeystore) {
signingConfigs.getByName("release")
} else {
signingConfigs.getByName("debug")
}
}
}
}

View file

@ -0,0 +1,6 @@
First public beta of Block 1: the offline, encrypted seed inventory.
- Local sharing (give away / swap / sell) with a printable catalog.
- Encrypted backups and a printable recovery sheet.
- Edit and delete lots and vernacular names.
- Portuguese added; right-to-left layouts verified.

View file

@ -0,0 +1,27 @@
Tanemaki (種まき, "to sow seeds") is a local-first, decentralized app for
keeping and sharing traditional seeds.
Every traditional seed is a letter written by thousands of generations, passed
from hand to hand. Tanemaki helps people and seed-saver collectives keep a
friendly inventory of their bank, decide what they offer, and share it locally
— without a central intermediary that could control, censor, or be fined for
it.
MANAGE YOUR BANK
• Add a seed with just a photo and a name — no Latin required.
• Note the year, how much you have, where it came from, germination tests.
• Search and filter; snap a photo now and name it later.
YOURS, AND ONLY YOURS
• Works fully offline. No account, no server, no trackers.
• Everything is encrypted at rest on your device.
• Save an encrypted backup and keep a printed recovery sheet — restore your
whole bank on a new device.
SHARE, THE WAY IT'S ALWAYS BEEN DONE
• Mark what you have spare to give away, swap or sell.
• Print a catalog of what you share to take to a seed fair.
Tanemaki is free software (AGPL-3.0). No ads, no commissions, no business model
— it exists to support traditional varieties and push back against the seed
monopoly.

View file

@ -0,0 +1 @@
Your seed bank in your pocket — offline, encrypted, no account.

View file

@ -0,0 +1 @@
Tanemaki

View file

@ -0,0 +1,6 @@
Primera beta pública del Bloque 1: el inventario de semillas sin conexión y cifrado.
- Compartir localmente (regalar / intercambiar / vender) con catálogo imprimible.
- Copias cifradas y hoja de recuperación imprimible.
- Editar y borrar lotes y nombres vernáculos.
- Añadido portugués; disposiciones de derecha a izquierda verificadas.

View file

@ -0,0 +1,27 @@
Tanemaki (種まき, "sembrar semillas") es una app local-first y descentralizada
para guardar y compartir semillas tradicionales.
Cada semilla tradicional es una carta escrita por miles de generaciones, que
pasa de mano en mano. Tanemaki ayuda a personas y colectivos de guardianas de
semillas a llevar un inventario amable de su banco, decidir qué ofrecen y
compartirlo localmente, sin un intermediario central que pueda controlarlo,
censurarlo o ser multado por ello.
GESTIONA TU BANCO
• Añade una semilla con solo una foto y un nombre; sin latín.
• Anota el año, cuánta tienes, de dónde viene, pruebas de germinación.
• Busca y filtra; haz una foto ahora y ponle nombre después.
TUYO, Y SOLO TUYO
• Funciona totalmente sin conexión. Sin cuenta, sin servidor, sin rastreadores.
• Todo va cifrado en tu dispositivo.
• Guarda una copia cifrada y ten una hoja de recuperación impresa: recupera
todo tu banco en un dispositivo nuevo.
COMPARTIR, COMO SE HA HECHO SIEMPRE
• Marca lo que te sobra para regalar, intercambiar o vender.
• Imprime un catálogo de lo que compartes para llevar a una feria de semillas.
Tanemaki es software libre (AGPL-3.0). Sin anuncios, sin comisiones, sin modelo
de negocio: existe para apoyar las variedades tradicionales y plantar cara al
monopolio de las semillas.

View file

@ -0,0 +1 @@
Tu banco de semillas en el bolsillo: sin conexión, cifrado, sin cuenta.

View file

@ -0,0 +1 @@
Tanemaki

View file

@ -0,0 +1,54 @@
import 'package:commons_core/commons_core.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:tane/data/variety_repository.dart';
import 'package:tane/db/database.dart';
import 'package:tane/db/enums.dart';
import '../support/test_support.dart';
/// A real seed bank can hold thousands of accessions. This guards that the
/// list query stays a handful of batched queries (not N+1) and returns in a
/// human blink even at that scale the pre-beta performance check.
void main() {
late AppDatabase db;
late VarietyRepository repo;
setUp(() {
db = newTestDatabase();
repo = newTestRepository(db);
});
tearDown(() => db.close());
test('the inventory view loads 3000 varieties quickly', () async {
const count = 3000;
for (var i = 0; i < count; i++) {
final id = await repo.addQuickVariety(
label: 'Variety $i',
category: i.isEven ? 'Poaceae' : 'Fabaceae',
);
// Every few varieties, add a lot so the batched lot/type/share queries
// do real work.
if (i % 5 == 0) {
await repo.addLot(
varietyId: id,
harvestYear: 2020 + (i % 5),
quantity: const Quantity(kind: QuantityKind.handful),
offerStatus: i % 25 == 0 ? OfferStatus.shared : OfferStatus.private,
);
}
}
final sw = Stopwatch()..start();
final view = await repo.watchInventoryView().first;
sw.stop();
expect(view.items, hasLength(count));
// Generous ceiling for CI; typical local runs are well under 500ms. The
// point is to catch an accidental N+1 regression, not to micro-benchmark.
expect(
sw.elapsedMilliseconds,
lessThan(3000),
reason: 'inventory view took ${sw.elapsedMilliseconds}ms for $count rows',
);
});
}