Git Product home page Git Product logo

tail_app's Introduction

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

tail_app's People

Contributors

codel1417 avatar dependabot[bot] avatar github-actions[bot] avatar mastertailer avatar snowowlx avatar weblate avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

tail_app's Issues

Ears need homing

Lets discuss on Telegram - having the building blocks is good - but a user wont understand that LETWIST 90 followed by LETWIST 100 will result in almost no move at all. Probably invisible to them. Thats why (I think) Crumpet App sends home commands after a pause, after the Move Buttons are pressed

Rework Sequences to support USERMOVE

Use the built in USERMOVE system for Sequences

  • Add usermove specific edit screen
  • add repeat slider
  • limit to 5 positions/steps
  • Add command generator
  • keep track of which usermove commands are stored in the Gear
  • Automatically send top 4 sequences to gear on connect

Make OTA screens more friendly

  • Combine download & upload buttons (Begin Update)
  • Add Lotties to update screen to be friendlier
  • Handle Success
  • Handle Failures (might already do this?)
  • Add check for battery level

Some edited descriptions for the app..

Kitsune Mode

If you connect many instances of the same devices, this mode will add random pauses to their move-start times, giving it a different visual effect.

Keep Screen On

This mode stops the screen from switching off whilst your gear is connected

Automatic Error Reporting

Sends error reports to Sentry

Development Menu

Advanced options for developers.

Set up IOS build environment

  • acquire MacOS device
  • get an apple developer license
  • install xcode
  • configure the environment to compile flutter
  • migrate config to GitHub actions

UninitializedPropertyAccessException: lateinit property serviceBindManager has not been initialized

Sentry Issue: TAIL-APP-7X

RuntimeException: Unable to destroy activity {com.codel1417.tail_app/com.codel1417.tail_app.MainActivity}: kotlin.UninitializedPropertyAccessException: lateinit property serviceBindManager has not been initialized
    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:5827)
    at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:5859)
    at android.app.ActivityThread.handleRelaunchActivityInner(ActivityThread.java:6142)
    at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:6056)
    at android.app.servertransaction.ActivityRelaunchItem.execute(ActivityRelaunchItem.java:77)
...
(12 additional frame(s) were not displayed)

UninitializedPropertyAccessException: lateinit property serviceBindManager has not been initialized
    at com.nankai.flutter_nearby_connections.FlutterNearbyConnectionsPlugin.onDetachedFromEngine(FlutterNearbyConnectionsPlugin.kt:139)
    at io.flutter.embedding.engine.FlutterEngineConnectionRegistry.remove(FlutterEngineConnectionRegistry.java:272)
    at io.flutter.embedding.engine.FlutterEngineConnectionRegistry.remove(FlutterEngineConnectionRegistry.java:280)
    at io.flutter.embedding.engine.FlutterEngineConnectionRegistry.removeAll(FlutterEngineConnectionRegistry.java:288)
    at io.flutter.embedding.engine.FlutterEngineConnectionRegistry.destroy(FlutterEngineConnectionRegistry.java:123)
...
(22 additional frame(s) were not displayed)

Set up fastlane

Fastlane simplifies auto uploading of new app versions

  • Add gem file to repo with fastlane added
  • Add a CI job to upload using fastlane when pushed to master (beta) or a release (production)
  • sign apps to be uploaded
  • create fastlane configuration
  • Handle secrets
  • Store secrets locally in a safe and redundant place
  • Create changelog file and link to fastlane config #130
  • Link to store listings for prod and beta tracks #145
  • Document fastlane use and required setup to use.

Android secrets can be passed directly from an environment variable.
iOS using the json setup requires a file path to be used.

Version and build number need to be set from the values found in the build jobs. Set them as outputs.

Add missing EarGear moves

Crumpet has additional moves. Copy them

Waiting on EarGear to be on the unified TailCoNTROL firmware which will include unified move commands

Swap out Bluetooth library

flutter_reactive_ble is barely supported and buggy on iOS. Switch to flutter_blue_plus

This would also let me reduce the number of streams used, reducing the complexity of managing state for gear.

Need to use central characteristic streams for receiving events

Add onboarding UI before showing main UI

  • welcome
  • privacy policy
  • question if the user has gear/ link to store.
  • get required Bluetooth permission with description
  • get optional notification permission for foreground service

Pull to scan

Enable pull to refresh on all shell pages to scan for known devices

  • Add pull to refresh logic to shell pages
    • Should be aware if the pare is scrolled to top
  • Add option to settings to always scan

Likely depends on #80

Alternative solution, only check for pull to refresh on the gear bar. This significantly reduces complexity and could be done from all directions

Add guided tutorial

  • Tutorial for connecting gear
    • Show focus on Scan button
    • Show focus on First gear which appears
  • Show focus on Gear icon after connecting
  • Show focus on sending an action
  • Direct users to Triggers page
    • Show Focus on the Walking trigger
    • Have the user enable the trigger

Secondary on demand tutorials for making a custom move or using the joystick

Add a changelog feature

Include a changelog in the app, and display it if it has been updated.

  • Add a CHANGELOG.md file
  • Use same changelog file for #121

Perhaps a different visual language for this?

Screenshot 2024-03-03 182914

I thought they were buttons and tried to press them. I understand they are just telling me it applies to these devices. Perhaps the outline of the shape isnt necessary - just the device name and a tick?

Add Privacy Policy

Add a privacy policy describing how sentry and plausible are used

No PII is stored or collected

Sentry used for error reporting. All PII (IP addresses) are stripped

Plausible only aggregates metrics and cannot be used to identify a specific user

Pause scanning when all known gear is connected

Scanning happens in the background whenever the shell is loaded, There is no reason to scan when all devices are connected

  • Move passive scanning to own stateful widget, placed before shell
  • Use MultiValueListener monitoring connection state of every known gear
    • pass child so Shell does not rebuild on state change
  • Add Continuous/30 Second scan modes

Prerequisite for #79

_Exception: Exception: Disconnected from device

Update device state on disconnected exception

Sentry Issue: TAIL-APP-3Y

_Exception: Exception: Disconnected from device
  File "BluetoothManager.dart", line 152, in KnownDevices.connect.<fn>.<fn>
...
(6 additional frame(s) were not displayed)

Rename app

tail_app was a placeholder and isn't user friendly.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.