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:
parent
5d2b41a110
commit
4acf227e30
12 changed files with 260 additions and 4 deletions
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Your seed bank in your pocket — offline, encrypted, no account.
|
||||
1
apps/app_seeds/fastlane/metadata/android/en-US/title.txt
Normal file
1
apps/app_seeds/fastlane/metadata/android/en-US/title.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
Tanemaki
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Tu banco de semillas en el bolsillo: sin conexión, cifrado, sin cuenta.
|
||||
1
apps/app_seeds/fastlane/metadata/android/es-ES/title.txt
Normal file
1
apps/app_seeds/fastlane/metadata/android/es-ES/title.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
Tanemaki
|
||||
54
apps/app_seeds/test/data/inventory_scale_test.dart
Normal file
54
apps/app_seeds/test/data/inventory_scale_test.dart
Normal 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',
|
||||
);
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue