A client for Pleroma and Mastodon instances written using Flutter

  chat, Flutter App, Social Media

Fedi for Pleroma and Mastodon

Fedi is open-source client for Pleroma and Mastodon social networks written using Flutter.

Pleroma and Mastodon are parts of Fediverse (decentralized social network). The main idea of Fediverse – nobody owns Fediverse. Anybody can run their server instance and use it to communicate with other people.

So Fedi is an open-source mobile client for social networks and has features similar to Twitter.

Follow us on Fediverse fediapp@fedi.app

Fedi

Android

iOS

Releases

Articles about how Fedi works inside

Table of Contents

  • Features
  • Coming soon
  • Known issues
  • Data gathering
  • Push notifications
  • Localization
  • License
  • Feedback
  • For developers
    • Flutter version & FVM
    • Libraries
    • Icons
    • Tests
    • Backlog
    • How to build from source

Features

  • Pleroma and Mastodon support
  • Offline mode. Access to cached data even without network
  • Custom emojis. With emoji reactions support on Pleroma
  • Customizable home timelines
  • Multi-account support
  • Push notifications via PushRelayFCM and FCM
  • Supports receive and send share intends
  • Scheduled and Draft Statuses
  • Filters
  • Instance details, announcements, trends, activity history, limits
  • Day & Night theme
  • Bookmarks, Hashtags, Lists, Featured tags, Suggestions
  • Messenger-like UI for Conversations(DM) and Pleroma chats
  • Customizable real-time notifications & timeline updates via WebSockets and Push Notifications
  • Fetch data from Remote instances via Public API. So you can access full data on remote instance if currently logged instance hasn’t synchronized all data yet
  • A lot of settings options(global or per-instance). For example: Always show NSFW or Auto-load media content
  • Threads & Polls
  • Special UI for media-only timelines
  • Mutes & Blocks
  • Editing profile
  • Sign up support

Coming soon

  • Admin API;
  • Support other Fediverse instances: Pixelfed, Misskey, Peertube, GNU Social, Friendica and others;
  • Pleroma: scrobbles, mascot and recently added new features;
  • Mastodon: recently added new features.
  • Adopt UI for large screens;
  • Display timelines from different instances on single Home page(currently you should switch instances to see related data);
  • Remember timeline position via Markers API;
  • OnBoarding & Tutorial. Popular instances suggestions;
  • A lot of minor UX improvements in backlog.

Feel free to open issues if you have suggestions

Known issues

  • Text is not selectable, but you can copy or share whole statuses
  • Instances with special chars like ü are not supported

Data gathering

Fedi doesn’t use any special analytics service to track users. However Fedi uses Firebase services for PushNotifications(optional) and CrashReporting(optional).

You can completely remove Firebase via manual building from source.

Crash reports via Firebase Crashlytics

Fedi gathers crashes and non-fatal errors to make app more stable.

  • You can build app from source and remove Crashlytics library via .env config(details below)
  • You can disable gathering via settings inside app(option is disabled by default)

Push notifications

Push notifications are implemented via PushRelayFCM server

PushRelayFCM is Ruby on Rails server which handles web pushes and relays them to FCM.

From 2.5.0 version Fedi uses PushRelayFCM mode without decryption on server-side. So all private data is safe.

PushRelayFCM and Fedi can work in two modes:

  • Without server-side decryption (2.5.0 and newer) – relay simple proxy encrypted messages
  • With server-side decryption (before 2.5.0) -decrypt messages and have access to notification content and user access_token. It is not used from 2.5.0 version, but is still supported in Fedi(see below why you still may want to use it).

Without server-side decryption way

(Used in AppStore/GooglePlay versions from 2.5.0)

  1. Fedi subscribes to /api/v1/push/subscription with subscription[endpoint] set to relay server URL
  2. Instances send web push notifications to relay server
  3. PushRelayFCM doesn’t decrypt message
  4. PushRelayFCM proxies notifications to Fedi app via FCM
  5. Fedi doesn’t decrypt message and use FCM message with encrypted data as simple trigger to load latest notification via REST API (this will be improved in future releases)
  6. Fedi displays notification

Since PushRelayServer doesn’t know private decryption keys, it can’t access any private data.

Pros
  • Doesn’t have access to user private data
  • Uses rich notifications layouts and actions provided by awesome_notifications
Cons
  • Delivery may be delayed. Because PushRelayFCM sends FCM push message without notification (FCM calls it data message). Read awesome_notifications and firebase_messaging documentation for details. Fedi uses :mutable_content=>true, :content_available=>true, :priority=>"high", to increase delivery priority
Why Fedi doesn’t decrypt message on client-side?

Because it is hard to implement with Flutter. There are no 3rd party Flutter libraries to decrypt ECDH p256v1 by now. It is possible to decrypt it in Kotlin/Swift and it will be done in the future.

With server-side decryption way

(It is not used in AppStore/GooglePlay versions from 2.5.0)

  1. Fedi subscribes to /api/v1/push/subscription with subscription[endpoint] set to relay server URL
  2. Instances send Web push notifications to relay server
  3. PushRelayFCM decrypts notifications
  4. PushRelayFCM relays notifications to Fedi app via FCM
  5. Fedi displays notification
  • PushRelayFCM has access to titlebody and access_token
  • access_token is sensitive data. It is possible to login into your account if someone knows access_token
Pros
  • Faster push delivery. FCM message(notification type) with notification.title and notification.body, which has higher priority than message without notification.title & notification.body fields. Actually it is more affects iOS, than Android. Read awesome_notifications and firebase_messaging documentation for details.
Cons
  • Private data access is main reason why Fedi moved to Without server-side decryption way
  • Doesn’t use rich notifications layouts and actions provided by awesome_notifications

Localization

App uses flutter_localization API bundle with Flutter SDK.

It uses .arb files located in lib/l10n and generates .dart classes in /lib/generated/ folder.

After you make changes in .arb files you should do additional actions to regenerate Dart classes

Completed

  • English, Russian

Help translate Fedi

  • Help translate Fedi with Weblate
  • It is easy to suggest fixes even without registering

License

Feedback

  • The best option is to create issue for this repository

For developers

  • Null-safety support
  • Feature-based folder structure
  • Prefers composition over inheritance
  • Dependency Injection is implemented via provider
  • Prefers StatelessWidget and async UI update via StreamBuidler and BehaviourSubject & StreamController in controller classes
  • Prefers divide Widgets in small sub Widgets with const constructor(for better performance) if possible
  • Provides data to nested elements via provider
  • Prefers Repository pattern. Almost all network data is cached in local SQLite database. UI always displays data from single source. It may be network-only or from database(if data is cached). Doesn’t cache and merge data in memory to achieve data consistency
  • Prefers Effective Dart name and style code conventions
  • Prefers long file & classes names like account_follower_account_cached_list_bloc_impl.dart and AccountFollowerAccountCachedListBloc
  • It is easy to understand what classes do
  • It is easy to navigate in IDE by typing start letters of name
  • One class = one file
  • Prefers interfaces for Bussines Logic and Services
  • Simple append I to implementation class name. AccountFollowerAccountCachedListBloc is implementation and IAccountFollowerAccountCachedListBloc is interface
  • Code readability: you can see small list of public methods/fields in interface file instead of exploring long file with implementations
  • It is useful to implement extensions for interfaces not for implementations
  • It is useful to extend several interfaces in one child to separate logic
  • It is useful to create tests and mocks

Flutter version & FVM

To build Fedi you need to specify Flutter version in .fvm/fvm_config.json field flutterSdkVersion.

You can achieve this by specifing your system Flutter version by using flutter version $version or using FVM

Flutter Version Management(FVM)

Fedi uses Flutter Version Management to specify Flutter version to build app.

FVM also helps manage several SDK’s versions on local machine

Config is already done, so you just run fvm install in repo folder and configure IDE to use .fvm/flutter_sdk folder instead of system Flutter SDK.

To use flutter version specified in .fvm/fvm_config.json you should prepend fvm like fvm flutter install

More info you can found in FVM documentation

Libraries

You can find full list in pubspec.yaml where each library has comment why it’s used

Icons

Tests

  • Fedi have unit-tests for Business Logic and Services classes
  • Integration & UI tests are not implemented yet

Backlog

How to build from source

Clone repository

git clone https://github.com/Big-Fig/Fediverse.app

Go to repository folder

cd Fediverse.app

Install FVM

Install Flutter version used by this project

fvm install

Copy default .env config

Copy config for prod and dev flavors

cp env_example.env env_prod.env
cp env_example.env env_dev.env

In Example config you can find out how to disable some features like Push notifications.

To enable all features you should change app id, create Firebase project, and edit config file.

Run
Run from command line
  • Download all required libraries
fvm flutter pub get
  • Run by Flavor
fvm flutter run --flavor dev

or

fvm flutter run --flavor prod
Run from IDE
  • Specify Flutter SDK path
  • File->Preferences->Languages & Frameworks->Flutter to <Project_Root>/.fvm/flutter_sdk
Config Flutter SDK
  • Dart SDK should be configured automatically. But you can check Dart SDK path at (File->Preferences->Languages & Frameworks->Dart).It should be <Project_Root>/.fvm/flutter_sdk/bin/cache/dart-sdk
Config Dart SDK
  • Specify flavor(prod or dev) in Run Configurations
Run->Edit configurations
Run configurations
  • Click Pub get in IDE or run fvm pub get in terminal
  • Connect device or run emulator
  • Run

Flavors

There are two flavors.

Implementation details: Build flavors in Flutter (Android and iOS) with different Firebase projects per flavor

prod

Is used for production builds

dev

Is used for development builds. You can use only prod flavor if you don’t need special config for development

Config

Main purpose of config files is to exclude sensitive data from source control and quickly enable/disable and config some features like Push Notifications

Build script uses config from project root folder depends on flavor, so to build app you should have next files in root folder

env_prod.env
env_dev.env

Those files are excluded from source control.

You can find all possible config variables(with comments) at env_example.env

If you have strange errors or how to clean project

  • fvm flutter clean or flutter clean if you don’t use FVM
  • ./gradlew clean in android folder
  • Product->Clean in XCode
  • File(or Android Studio on Mac)->Invalidate caches & Restart in Android Studio

Sometimes it is also needed to clear iOS pods

cd ios
rm -rf Pods
rm Podfile.lock
pod install

Sometimes you change package version in pubspec.yaml run pub get but version is not changed

rm pubspec.lock
rm .flutter-plugins
rm .flutter-plugins-dependencies
rm .packages
rm -rf .dart_tool
pub get

Sometimes when you change package version in pubspec.yaml and after pub get version is not changed. You can check pubspec.lock to see if version is changed. That may happen when you specify version bounds like >=1.0.0 <2.0.0 or ^1.0.0 which are the same. See Version constraints in official docs.

rm pubspec.lock
rm .flutter-plugins
rm .flutter-plugins-dependencies
rm .packages
rm -rf .dart_tool
pub get

Fedi specifies explicitly version like 1.0.0 to avoid such issues. However, that may cause dependencies version conflict

App ID

Changing App ID is required if you want to setup own PushRelayFCM server and pushes via your Firebase project for FCM.

It is also useful if you want to have several app versions installed on one device

Unfortunately, it is not possible to use APP_ID from config in all places in Gradle and XCode project files. So in some places ID is hardcoded

So, If you want to change app id from com.fediverse.app for prod and from com.fediverse.app2 for dev you should manually change them (in addition to changing id in .env files)

Actually, you should run Find and Replace com.fediverse.app with your package name on ios and android folders. And rename folders at android/app/src/main/kotlin

However, it may cause strange build errors. So you may need full clean

If you still have errors please explore App ID things in the next docs:

iOS group ids
  • receive_sharing_intent lib requires to add group.<app_id> to XCode project. Unfortunately, it is not possible due to our internal issues (we’ve moved app to new iTunes account and can’t use old group id). So we use fork of receive_sharing_intent with custom group ids support.

Fedi uses group fediverse.app for prod and com.fediverse.app2 for dev

Signing

Android

Signing config is required to make release builds

Generate key via Tutorial and put it in android/key/key.jks(exclude from source control)

Create android/key.properties(exclude from source control) file with next template.

storePassword=pass1
keyPassword=pass2
keyAlias=keyName
storeFile=../key/key.jks
iOS

Follow official tutorial

Firebase

To use Firebase service you should generate files from your Firebase project page and put them in the project.

Don’t forget to enable it in .env file

FIREBASE_ENABLED=false

Android
  • Generate google-services.json

Put google-services.json to folder depends on used flavor

  • android/app/src/dev
  • android/app/src/prod
iOS
  • Generate GoogleService-Info.plist

Put GoogleService-Info.plist to folder depends on used flavor

  • ios/config/dev
  • ios/config/prod

For more details see

Push notifications

To enable Push notifications you should

App ID and FCM server key(so and PushRelayFCM instance) are connected. It is not possible to use one PushRelayFCM instance with several App IDs and vice versa

On/Off via .env. Firebase Core integration is required

PUSH_FCM_ENABLED=false
Proxy PushRelayFCM

Is required if PUSH_FCM_ENABLED=true

PUSH_SUBSCRIPTION_KEYS_P256DH

User agent public key. Base64 encoded string of public key of ECDH key using prime256v1 curve.

PUSH_SUBSCRIPTION_KEYS_AUTH

Auth secret. Base64 encoded string of 16 bytes of random data.

More info in Mastodon docs and PushRelayFCM server docs

PUSH_FCM_RELAY_URL=https://pushrelay.example.com/push/
PUSH_SUBSCRIPTION_KEYS_P256DH=BEpPCn0cfs3P0E0fY-gyOuahx5dW5N8qu
PUSH_SUBSCRIPTION_KEYS_AUTH=T5bhIIyre5TDC

Firebase Crashlytics

On/Off via .env. Firebase Core integration is required

CRASHLYTICS_ENABLED=false

Used to catch errors on client-side with error description and stackTrace

You should enable Firebase support and change config variable in .env file to enable crash reporting

Versioning

Android

Uses version from pubspec.yaml

iOS

By default Flutter project config it should use version from pubspec.yaml, However, sometimes it causes strange iOS build errors(version is not changed but should be).

So, Fedi requires a manual increasing version code & name in Runner and Share Extension targets.

Receiving share intents & ShareExtension

XCode project has additional ShareExtension module required by receive_sharing_intent to handle income share events. It is also important to add Target and ShareExtension to the same group ID.

3rd Party

Download A client for Pleroma and Mastodon instances source code on GitHub

https://github.com/Big-Fig/Fediverse.app