Skip to content

OpenTails/tail_app

Repository files navigation

The Tail Company App

Weblate project translated Sponsor GitHub Actions Workflow Status Codecov

A Cross Platform Tail Company gear control App

Features

  • Supports Android and IOS
  • Firmware Updates
  • The same actions/moves from Crumpet
  • Triggers for walking, shaking and other gestures
  • Custom Actions
  • Custom Sound Effects
  • Joystick for manually moving gear
  • Dark Mode (Based on system settings)
  • Color Themes
  • Background mode on IOS
  • Tail Blog
  • Tablet support
  • Sound Actions

Have a suggestion?

Small or large, feel free to leave suggestions for new features, or changes to existing features.

Note

As long as the suggestion is not related to a specific day or event

Special Thanks

  • @darkgrue for helping me with gear firmware behavior & developing the firmware the Gear uses
  • @MasterTailer for providing useful feedback and suggestions, and creating the gear this app controls
  • @ToeiRei for inspiring me to use more privacy-preserving infrastructure like plausible, and for managing & assisting in the translation of this app
  • @leinir for creating the Crumpet Android app
  • The Tail Company Telegram Channel for motivating me over time.

Development

Requirements

Tip

Follow the instructions here to set up a Flutter environment

Hardware

  • 8GB of ram
  • Dual Core CPU
  • 80gb of unused storage (Required for Android Studio & XCode, Sources for IOS & Android apps, etc)
  • 2018 or newer Apple Mac (For IOS) Older macs with OpenCore-Legacy Patcher may work

Software

Updating EN Localizations

To update EN localization strings, the file translation_string_definitions.dart needs to be updated.

String message() => Intl.message('Displayed Message', name: 'message', desc: 'A description of the string and where it is used');

The Displayed Message is the string that appears in the UI. The name is the variable name. This must match the variable name used such as message() but without the (). The desc is a description of the string for use by translators.

When translation_string_definitions.dart is updated, the job localization_strings_update.yml updates the generated localization file messages_en.arb which makes the strings available to Weblate.

When non EN translations are updated in Weblate, A pull request will automatically open with the changes. This may take a few minutes.

Building

Preparing for build

Tip

A pre-made build script exists at scripts/build,sh

Important

These commands must be run before building or running.

# Install and enable required tools
dart pub global activate intl_translation

flutter pub get # Downloads Dependencies
dart run intl_translation:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/Frontend/translation_string_definitions.dart lib/l10n/*.arb
flutter pub run build_runner build --delete-conflicting-outputs  # Generates .g files

Note

To generate the base EN localization file, run

dart pub global activate intl_translation
dart run intl_translation:extract_to_arb --locale=en --output-file='./lib/l10n/messages_en.arb' ./lib/Frontend/translation_string_definitions.dart

To build localization files, run

dart pub global activate intl_translation
dart run intl_translation:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/Frontend/translation_string_definitions.dart lib/l10n/*.arb

To build generated .g files, run

flutter pub run build_runner build --delete-conflicting-outputs

Tip

If you get a flutter version error, run

flutter Upgrade

if you get an error similar to Error: Couldn't resolve the package 'flutter_gen' in 'package:flutter_gen/gen_l10n/app_localizations.dart' run

flutter pub get

if you get an error during riverpod generator such as RangeError (index): Invalid value: Not in inclusive range 0..20491: 77535 try

flutter clean
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
flutter pub get # fixes app_localizations error

Building for each platform

For IOS

The xcode project is configured to to use automatic code signing for develop builds. make sure to open xcode and sign in with your developer account. Check the Signing and Capabilities page of Runner to verify the signing configuration is correct. Release builds are set up to rely on Fastlane to provide the signing certificate.

cd ios
rm Podfile.lock # Handles a CocoaPods error about version management
cd ..
flutter build ipa --debug

Tip

MacOS may display multiple permission prompts such as File Access, KeyChain Access, Device Access (iphone) & Controlling XCode. Accept them for the build to complete. These only need to be accepted once

If you receive an error that IOS is not installed in XCode during build.

  1. Go to XCode (In Top menu bar) -> Settings
  2. Click Platforms
  3. Click on IOS Simulator
  4. Click the small - icon near the bottom of the settings window
  5. Click Delete

If CocoaPods returns a version error, delete ios/Podfile.lock

If Permissions are not working, revert any changes you may have done to ios/Podfile

For Android

Android looks for a key.properties file. If this file is missing the apk is not signed. A keystore jks is also expected at the location specified in the keystore.properties file. The Sentry plugin will run on release builds and expects the sentry env variables to be set

# Build APK
flutter build apk --debug
# build AppBundle
flutter build appbundle --debug

App packages can be found in build/app/output

Additional Commands

Updating app icon

Place the new icon in Assets then update image_path in the icons_launcher section in pubspec.yml

dart pub global activate icons_launcher # downloads the utility
dart pub global run icons_launcher:create

Updating splash screen

Make any changes to the 'flutter_native_splash' section in pubspec.yml

dart run flutter_native_splash:create

Tip

Once this command is run, you may need to add the following to styles.xml to have transparent status and navigation bars on the splash screen on android.

<item name="android:windowTranslucentNavigation">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>

Running tests

To run tests, simply run the command

flutter test

Detecting the environment

In order to filter sentry logs from testing and production, the 'Environment' is guessed

  • debug: if the app was built in debug mode or if the app is in an emulator
  • staging: if the app is on testflight or in firebase test lab
  • production: everything else

Fastlane

Fastlane is a tool to automatically upload apps to the Apple App Store and Google Play Store. Inside the IOS and Android folders is a fastlane folder. Inside is the FastFile which contains the upload config. Secrets are JSON files passed through repository secrets. The script fastlane.sh selects the fastlane folder to use and begins the upload.

Repository Secrets

Some of these values aren't actually secret and can be shared. Specifically the sentry ones

Name Example Value How to get Uses
SENTRY_AUTH_TOKEN sntrys_eyJpYXQiOjE3MTYyNTky... Go to Sentry -> Settings -> Auth Token Authenticate with sentry to upload symbols
SENTRY_ORG Sentry Listed at the top left of sentry when logged in Which org to upload symbols to
SENTRY_PROJECT tail_app Whatever the project is named in sentry Which project to upload symbols to
SENTRY_URL https://sentry.io/ The url to the sentry instance Which instance to upload symbols to
SENTRY_DSN https://sdfghjssdh.ingest.de.sentry.io/ The dsn for the sentry project Which instance to upload errors to
FASTLANE_GITHUB JeqGFIV1yb7emBFLkBk/dA== echo -n your_github_username:your_personal_access_token | base64 -w 0 Store certificates for fastlane match
APPLE {"key_id": "D383SF739", "issuer_id": "6053b7fe-68a8-4acb-89be-165aa6465141", "key": "-----BEGIN PRIVATE KEY-----MIGTAgEAMB----END PRIVATE KEY-----", "in_house": false } Json file of apple credentials https://docs.fastlane.tools/app-store-connect-api/ Authenticate with Apple to upload to TestFlight
FASTLANE_PATCH_PASSWORD hunter2 Make a password Encrypt match certificates
ANDROID_KEY_PROPERTIES storePassword=hunter2
keyPassword=hunter2
keyAlias=upload
storeFile=key.jks
generate an android signing certificate and fill out key.example.properties sign apks
ANDROID_KEY_JKS sdfsfasdFSDgjklsgklsjdfASGHSDLGHJFSD= cat AndroidKeystoreCodel1417.jks | base64 -w 0 base64 form of the jks file
GOOGLE_SECRETS {"type": "service_account", Json file of google credentials https://docs.fastlane.tools/actions/upload_to_play_store/ Authenticate to google to upload builds
CODECOV_TOKEN jlgsfjklsdhjklvsdfjklsdfjklsdfjkjklnsdf generated by codecov Code coverage metrics

Repository Integrations

Sentry

A github app which allows Sentry to authenticate with Github and this repo. It allows Source Code stack trace linking and Creating issues from the Sentry UI.

Weblate

A Webhook to notify Weblate that code was pushed to this repo.

A SSH key is installed in my account which allows Weblate to push translation changes to the repo.

Codecov

The Codecov integration is set up to track test coverage. The job testing.yml runs the unit tests for every push and pull request, and uploads the coverage report to codecov. These reports are visible on pull requests as comments.

Developer Mode Features

  • Gear console
  • Manual OTA Updates
  • Advanced state control for gear
  • Access to app logs
  • Crime
Secret

To enter the in-app Developer Mode, follow these instructions

  1. Go to More
  2. Long press Source Code button, enter the following code
  3. 🦊🐉🦦🦖

To Turn off Developer Mode

  1. go to More -> Settings -> Developer Mode
  2. Turn off showDebugging

Internal URLS

These services are self-hosted in a mini-pc on my tv stand in my apartment

Plausible Uptime

Infrastructure notes

Services are hosted in a Proxmox based machine using unprivileged LXC containers. These containers are based on Ubuntu Server and have unattended upgrades enabled. This machine doesn't use port forwarding, but instead uses Cloudflare Tunnel. Services go down daily at 6:00 AM UTC for offline backups. This Can take up to an hour.

Dynamic Configuration

This app supports updating some config values remotely. These values are located in dynamic_config.json. The file is included in builds and updated after app launch, so changes may not appear immediately.

Json Key Description
sentryProfiles sets options.profilesSampleRate for sentry
sentryTraces sets options.tracesSampleRate for sentry

Other URLS

Apple App Store connect Codecov Code Coverage

Misc

UUIDs were generated using https://www.uuidgenerator.net/version4