Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/vocdoni/vocdoni-mobile

Decentralized, transparent, verifiable and anonymous voting app
https://github.com/vocdoni/vocdoni-mobile

decentralized decentralized-voting e-gov e-government evoting flutter governance open-gov vote vote-application voting voting-app voting-application

Last synced: 3 days ago
JSON representation

Decentralized, transparent, verifiable and anonymous voting app

Awesome Lists containing this project

README

        

# Vocdoni Mobile Client
Official implementation of the Vocdoni core features.

## Flavors

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.

## Development

### Data Architecture and internal Models

The State Management architecture of the app is built on top of the [Eventual package](https://pub.dev/packages/eventual). Eventual allows to track updates on objects and rebuild their corresponding UI accordingly.

#### Model classes

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.

#### Usage

**Initialize and read Global Model's data in `globals.dart`**

```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.

```dart
// 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 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`.

#### Extra methods

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.

#### General

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.

### Internationalization

![Translations](https://hosted.weblate.org/widgets/vocdoni/-/mobile/svg-badge.svg)

- 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](https://hosted.weblate.org/projects/vocdoni/mobile/).

#### Translation management

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 app
- `origin/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}}?"`

### Dependencies

The project makes use of the [DVote Flutter](https://pub.dev/packages/dvote) plugin. Please, [see the repository](https://github.com/vocdoni/dvote-flutter) for more details.

## Integration

### Deep linking

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` on `https://app.vocdoni.net/.well-known/assetlinks.json`
- Also place `linking/assetlink.json` on `https://app.dev.vocdoni.net/.well-known/assetlinks.json`
- Place `linking/apple-app-site-association` on `https://app.vocdoni.net/.well-known/apple-app-site-association`

#### Supported Paths and Parameters
- `app.vocdoni.net` and `app.dev.vocdoni.net`
- `https://app.vocdoni.net/entities/#/`
- ~~`https://app.vocdoni.net/entities/#/`~~
- ~~`https://app.vocdoni.net/news/#/`~~
- `https://app.vocdoni.net/validation/#//`

The same applies to `app.dev.vocdoni.net`

- `vocdoni.page.link` and `vocdonidev.page.link`
- They wrap dynamic links
- The `link` query string parameter is extracted, which should contain a link like the ones above from `app.vocdoni.net` and `app.dev.vocdoni.net`

#### Supported Schemas

The app also accepts URI's using the `vocdoni:` schema, with the same paths and parameters as above:
- `vocdoni://vocdoni.app/entities/#/`
- ~~`vocdoni://vocdoni.app/processes/#//`~~
- ~~`vocdoni://vocdoni.app/posts/#//`~~
- `vocdoni://vocdoni.app/validation/#//`

#### Show an entity

On developoment, you can test it by running `make launch-ios-org` or `make launch-android-org`

### Push notifications

The `data` field of incoming push notifications [is expected to contain](https://gitlab.com/vocdoni/client-mobile/-/issues/197) 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 Feed
- `new-process`: A process has been created
- `process-ended`: The end block has been reached

- The `message` is a copy of the relevant text contained within `notification` (not always present)

### Permissions

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`.

## Troubleshooting

- 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
- ~~Can't compile iOS because `App.framework` is built for another architecture~~
- ~~Run `rm -Rf ios/Flutter/App.framework` and try again~~