Official implementation of the Vocdoni core features.
The app can run in three diferent modes:
- Development
- Uses the development blockchain
- Beta (Android only)
- Uses the development blockchain
- Production
- Uses the production blockchain
Flavors are available on Android. The iOS project uses dev
and production
depending on the XCode target.
The State Management architecture of the app is built on top of the Eventual package. Eventual allows to track updates on objects and rebuild their corresponding UI accordingly.
They can be of the type:
- Single models
- AppStateModel, AccountModel, EntityModel, ProcessModel, FeedModel
- A model can contain references to other models
- Pools of data
- Typically, they contain a collection of single models
- Usually used as global variables
- Their goal is to track whether the collection changes, but not the individual items
- They contain all the model instances known to the system
- Any modification on a model should happen in models obtained from a global pool, since the pool manages persistence
This separation allows for efficient and granular widget tree rebuilds whenever the state is updated. If a single value changes, only the relevant children should rebuild.
Initialize and read Global Model's data in globals.dart
// entitiesPersistence = EntitiesPersistence();
await Global.entitiesPersistence.readAll();
// ...
// entityPool = EntityPoolModel();
await Globals.entityPool.readFromStorage(); // will import and arrange the persisted data
Consume Models in specific places
Typically, a Pool with all the EntityModel's known to the app and then, individual EntityModel
instances when the user selects one.
// Globals.entityPool
// ...
// Widget
@override
Widget build(BuildContext context) {
// From the pool, we grab the first entity model
final myEntity = Globals.entityPool.value.first;
// Consume many values (EventualNotifier) locally
return EventualBuilder(
notifiers: [myEntity.feed, myEntity.processes], // EventualNotifier<T> values that may change over time
builder: (context) {
// rebuilt whenever either of myEntity.feed or myEntity.processes change
// ...
)
);
}
In the example above, updates on specifig Feed items, will not affect the current widget. But as soon as we call myEntity.feed.refresh()
on this instance, the Builder will be triggered because of the changes in isLoading
, hasError
and hasValue
.
Certain models implement the ModelRefreshable
interface. This ensures that callers can call refresh()
to request a refetch of remote data, based on the current model's ID or metadata.
Other models (mainly pools) also implement the ModelPersistable
interface, so that readFromStorage()
and writeToStorage()
can be called.
It is important not to mix the models (account, entity, process, feed, app state) with the Persistence classes. Persistence classes map diretly to dvote-protobuf
classes, which allow for binary serialization and consistently have a 1:1 mapping.
Models can contain both data which is persisted (entity metadata, process metadata) as well as data that is ephemeral (current participants on a vote, selected account). When readFromStorage
is called, data needs to be deserialized and restored properly, often across multiple models.
- Add
import 'package:vocdoni/lib/i18n.dart';
on your widget file - Access the new string with
getText(context, "My new string to translate")
- Parse the new strings with
make lang-extract
- Translate the files on
assets/i18n/*.json
The app's strings translation can be found on Weblate.
Weblate monitors origin/i18n
and pulls from it as new strings are available. After a new translation is added, Weblate compiles the new JSON files in a git remote of its own.
To pull from it, run make init
. This will add a weblate
git remote besides origin
. This way:
weblate/i18n
has the translated strings to integrate into the apporigin/i18n
contains the empty strings that Weblate will show to translators
The translation flow is an iterative loop that looks like:
- Lock the weblate repository
git checkout i18n
git pull weblate i18n
(update our local repo)git push origin i18n
(update the GitHub branch)git checkout main
(check out the latest code)git merge i18n
(integrate the latest translations)git push origin main
(update the GitHub branch)git checkout i18n
git merge main
(integrate the latest code into i18n)make lang-parse
(extract the new strings)git add assets/i18n/*
(stage the language files for commit)git commit -m "Updated strings"
git push origin i18n
(push the new strings for Weblate)git checkout main
- Unlock the Weblate repository
Important:
- Make sure to use the right key prefixes:
"action.createIdentity"
instead of"main.createIdentity"
- In the keys, use no symbols beyond the dot separator
- Use placeholders for data replacement instead of concatenating it later
"question.doYouWantToRemoveName"
=>"Do you want to remove {{NAME}}?"
The project makes use of the DVote Flutter plugin. Please, see the repository for more details.
The app accepts Deep Links from the following domains:
app.vocdoni.net
app.dev.vocdoni.net
vocdoni.page.link
vocdonidev.page.link
To enable them:
- Place
linking/assetlink.json
onhttps://app.vocdoni.net/.well-known/assetlinks.json
- Also place
linking/assetlink.json
onhttps://app.dev.vocdoni.net/.well-known/assetlinks.json
- Also place
- Place
linking/apple-app-site-association
onhttps://app.vocdoni.net/.well-known/apple-app-site-association
app.vocdoni.net
andapp.dev.vocdoni.net
https://app.vocdoni.net/entities/#/<entity-id>
https://app.vocdoni.net/entities/#/<process-id>
https://app.vocdoni.net/news/#/<process-id>
https://app.vocdoni.net/validation/#/<entity-id>/<validation-token>
The same applies to app.dev.vocdoni.net
vocdoni.page.link
andvocdonidev.page.link
- They wrap dynamic links
- The
link
query string parameter is extracted, which should contain a link like the ones above fromapp.vocdoni.net
andapp.dev.vocdoni.net
The app also accepts URI's using the vocdoni:
schema, with the same paths and parameters as above:
vocdoni://vocdoni.app/entities/#/<entity-id>
vocdoni://vocdoni.app/processes/#/<entity-id>/<process-id>
vocdoni://vocdoni.app/posts/#/<entity-id>/<idx>
vocdoni://vocdoni.app/validation/#/<entity-id>/<validation-token>
On developoment, you can test it by running make launch-ios-org
or make launch-android-org
The data
field of incoming push notifications is expected to contain three keys:
{
notification: { ... },
data: {
uri: "https://vocdoni.link/processes/0x1234.../0x2345...",
event: "new-process", // enum, see below
message: "..." // same as notification > body
}
}
- The
uri
field will be used the same as a deep link and will determine where the app navigates to. - The
event
can be one of:entity-updated
: The metadata of the entity has changed (but not the process list or the news feed)new-post
: A new post has been added to the Feednew-process
: A process has been createdprocess-ended
: The end block has been reached
- The
message
is a copy of the relevant text contained withinnotification
(not always present)
In order to drop unused permissions, edit ios/Podfile
and make sure to uncomment the permissions that are not needed after target.build_configurations.each
.
- The
r_scan
plugin breaks the build on iOS and/or is rejected by Google Play- Edit
pubspec.yaml
and comment/uncomment the dependencies accordingly
- Edit
Can't compile iOS becauseApp.framework
is built for another architectureRunrm -Rf ios/Flutter/App.framework
and try again