Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ jobs:
bundler-cache: true
- run: ruby -c fastlane/Fastfile
- run: bundle exec fastlane lanes
- name: Test the Play upload configuration without publishing
run: bundle exec ruby test/tool/fastlane_test.rb
- name: Check tools did not mutate the checkout
run: |
git diff --exit-code
Expand Down
24 changes: 15 additions & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,12 @@ jobs:
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "Building $version ($number)."
# Before the slow build, so a changelog Play would refuse fails in
# seconds. Into the runner's temporary directory, not the checkout.
- name: Write the Play release notes
# seconds. Package the notes and artwork into the runner's temporary
# directory, leaving the checkout and generated screenshots untouched.
- name: Prepare the Play listing and release notes
run: |
notes="$RUNNER_TEMP/play-release-notes/en-US/changelogs"
mkdir -p "$notes"
dart run tool/release_notes.dart > "$notes/default.txt"
cat "$notes/default.txt"
dart run tool/play_metadata.dart --output="$RUNNER_TEMP/play-metadata"
cat "$RUNNER_TEMP/play-metadata/en-US/changelogs/default.txt"
- run: dart run build_runner build
- name: Build the web bundle
env:
Expand Down Expand Up @@ -169,6 +168,12 @@ jobs:
build/debug-info/android
if-no-files-found: error
retention-days: 30
- uses: actions/upload-artifact@v7
with:
name: opensplit-play-metadata-${{ github.sha }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/play-metadata
if-no-files-found: error
retention-days: 30

# Mint short-lived credentials only AFTER builds and artifact uploads.
- name: Recheck current main before deployment
Expand Down Expand Up @@ -246,9 +251,10 @@ jobs:
# update; 4 and up interrupt testers with a blocking flow, which is
# for a build the server can no longer talk to an older client from.
PLAY_UPDATE_PRIORITY: ${{ inputs.update_priority || vars.PLAY_UPDATE_PRIORITY || '0' }}
# Written above, from CHANGELOG.md. en-US is the store listing's
# language; Play refuses notes in one the listing does not have.
PLAY_RELEASE_NOTES_DIR: ${{ runner.temp }}/play-release-notes
# Written above, from CHANGELOG.md and the committed artwork.
# en-US is the store listing's language; images affect the listing
# across tracks, even though the bundle goes to closed testing.
PLAY_METADATA_DIR: ${{ runner.temp }}/play-metadata
run: bundle exec fastlane android closed
- name: Remove temporary upload key
if: always()
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ migrate_working_dir/
/build/
/coverage/

# Launcher generator intermediates; tool/brand_icons.dart publishes site/icons.
/web/icons/
/web/favicon.png

# Browser-test server serves test/, so copy the shipped SQLite assets here.
/test/browser/sqlite3.wasm
/test/browser/drift_worker.js
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ What changed in each version of the app, newest first. The format is
[Semantic Versioning](https://semver.org/), and the file is kept by
[cider](https://pub.dev/packages/cider): see "Versions" in docs/runbook.md.

## [3.1.0] - 2026-10-11
### Added
- Amount and percentage splits, and multiple payers, start evenly and share the remainder as you edit. Saved splits keep their values.

### Changed
- A refreshed Material design with indigo colours, Google Sans Flex, consistent fields and clearer expense entry.
- Groups share their cover art and name as they open, with transitions that respect reduced motion.
- Recorded payments open in a dedicated editor showing who paid whom, with an optional note.
- Custom split values have a small pin and an even-split reset.
- The launch screen, landing page, emails and Play artwork match the new design.

### Fixed
- Android device colours refresh when you return to the app or switch light and dark mode. Settings can check colour availability again.
- A saved payment keeps its sender, recipient and payment type while editing.
- Expense previews and saved splits assign leftover cents to the same people.

## [3.0.0] - 2026-10-10
### Added
- On the web, notifications now arrive when OpenSplit isn't open, and tapping one opens the group.
Expand Down Expand Up @@ -74,6 +90,7 @@ What changed in each version of the app, newest first. The format is
### Added
- The first build, to Play internal testing.

[3.1.0]: https://github.com/eigeninteractive/opensplit/compare/v3.0.0...v3.1.0
[3.0.0]: https://github.com/eigeninteractive/opensplit/compare/v2.0.0...v3.0.0
[2.0.0]: https://github.com/eigeninteractive/opensplit/compare/v1.0.0...v2.0.0
[1.0.0]: https://github.com/eigeninteractive/opensplit/releases/tag/v1.0.0
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ leaving it to this file: *Settings → About* links to the source, and
`REPOSITORY_URL` is a build-time define so that a fork's copy points at the
fork.

The typeface is not ours and is not under that licence. Instrument Sans is
The typeface is not ours and is not under that licence. Google Sans Flex is
bundled under the SIL Open Font License 1.1, with the notice
and the licence text in [assets/google_fonts/LICENSE](assets/google_fonts/LICENSE)
— which the app also shows, under *About → Open-source licences*.
Expand Down
Binary file modified android/app/src/main/res/drawable-hdpi/android12splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-hdpi/ic_launcher_monochrome.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-hdpi/ic_stat_opensplit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-hdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-mdpi/android12splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-mdpi/ic_launcher_monochrome.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-mdpi/ic_stat_opensplit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-mdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-night-hdpi/android12splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-night-hdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-night-mdpi/android12splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-night-mdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-night-v21/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-night-xhdpi/android12splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-night-xhdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-night-xxhdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-night-xxxhdpi/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-night/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-v21/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-xhdpi/android12splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified android/app/src/main/res/drawable-xhdpi/ic_launcher_monochrome.png
Binary file modified android/app/src/main/res/drawable-xhdpi/ic_stat_opensplit.png
Binary file modified android/app/src/main/res/drawable-xhdpi/splash.png
Binary file modified android/app/src/main/res/drawable-xxhdpi/android12splash.png
Binary file modified android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png
Binary file modified android/app/src/main/res/drawable-xxhdpi/ic_launcher_monochrome.png
Binary file modified android/app/src/main/res/drawable-xxhdpi/ic_stat_opensplit.png
Binary file modified android/app/src/main/res/drawable-xxhdpi/splash.png
Binary file modified android/app/src/main/res/drawable-xxxhdpi/android12splash.png
Binary file modified android/app/src/main/res/drawable-xxxhdpi/ic_stat_opensplit.png
Binary file modified android/app/src/main/res/drawable-xxxhdpi/splash.png
Binary file modified android/app/src/main/res/drawable/background.png
Binary file modified android/app/src/main/res/mipmap-hdpi/launcher_icon.png
Binary file modified android/app/src/main/res/mipmap-mdpi/launcher_icon.png
Binary file modified android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
Binary file modified android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
Binary file modified android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
2 changes: 1 addition & 1 deletion android/app/src/main/res/values-night-v31/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#131318</item>
<item name="android:windowSplashScreenBackground">#121318</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
</style>
<!-- Same as the pre-31 NormalTheme, and stated again because this file
Expand Down
2 changes: 1 addition & 1 deletion android/app/src/main/res/values-night/colors.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="surface">#131318</color>
<color name="surface">#121318</color>
</resources>
2 changes: 1 addition & 1 deletion android/app/src/main/res/values-v31/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#FCF8FF</item>
<item name="android:windowSplashScreenBackground">#FBF8FF</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
</style>
<!-- Same as the pre-31 NormalTheme, and stated again because this file
Expand Down
4 changes: 2 additions & 2 deletions android/app/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
Both are kept in step with lib/presentation/theme.dart by a test — see
test/presentation/theme_test.dart. -->
<resources>
<color name="surface">#fcf8ff</color>
<color name="ic_launcher_background">#E3DFFF</color>
<color name="surface">#fbf8ff</color>
<color name="ic_launcher_background">#DEE0FF</color>
</resources>
4 changes: 2 additions & 2 deletions assets/brand/lockup-horizontal.svg
4 changes: 2 additions & 2 deletions assets/brand/lockup-vertical.svg
Binary file modified assets/brand/mark-1024-dark.png
Binary file modified assets/brand/mark-1024-light.png
2 changes: 1 addition & 1 deletion assets/brand/mark-on-dark.svg
2 changes: 1 addition & 1 deletion assets/brand/mark-on-primary-container.svg
2 changes: 1 addition & 1 deletion assets/brand/mark.svg
Binary file added assets/google_fonts/GoogleSansFlex-Medium.ttf
Binary file not shown.
Binary file added assets/google_fonts/GoogleSansFlex-Regular.ttf
Binary file not shown.
Binary file added assets/google_fonts/GoogleSansFlex-SemiBold.ttf
Binary file not shown.
Binary file removed assets/google_fonts/InstrumentSans-Medium.ttf
Binary file not shown.
Binary file removed assets/google_fonts/InstrumentSans-Regular.ttf
Binary file not shown.
Binary file removed assets/google_fonts/InstrumentSans-SemiBold.ttf
Binary file not shown.
11 changes: 5 additions & 6 deletions assets/google_fonts/LICENSE
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
Fonts bundled with OpenSplit, so that the app never fetches one from a
third-party host at runtime.

Instrument Sans
Google Sans Flex
SIL Open Font License 1.1

InstrumentSans-Regular.ttf, InstrumentSans-Medium.ttf and
InstrumentSans-SemiBold.ttf are the exact files package:google_fonts
GoogleSansFlex-Regular.ttf, GoogleSansFlex-Medium.ttf and
GoogleSansFlex-SemiBold.ttf are the exact files package:google_fonts
would otherwise download: each was fetched from the URL that package
builds and checked against the SHA-256 and byte length recorded in its
own font descriptors.
Expand All @@ -20,16 +20,15 @@ Instrument Sans
Roboto
Not bundled. Flutter's web engine needs a font family named "Roboto" to
exist or it downloads one from fonts.gstatic.com; pubspec.yaml satisfies
that name with Instrument Sans, so no Roboto file ships with this app.
that name with Google Sans Flex, so no Roboto file ships with this app.

Nothing here is part of OpenSplit's own AGPL-3.0 licence, which covers the code
and not the typefaces it sets text in.


COPYRIGHT NOTICES

Copyright 2022 The Instrument Sans Project Authors
(https://github.com/Instrument/instrument-sans)
Copyright 2015 Google LLC. All Rights Reserved.

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
Expand Down
Binary file modified assets/icon/icon-dark.png
Binary file modified assets/icon/icon-foreground.png
2 changes: 1 addition & 1 deletion assets/icon/icon-foreground.svg
Binary file modified assets/icon/icon-monochrome.png
Binary file modified assets/icon/icon.png
Binary file modified assets/splash/splash-icon-dark.png
Binary file modified assets/splash/splash-icon.png
92 changes: 92 additions & 0 deletions docs/automatic-splits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Automatic split remainders

## Behaviour

New amount and percentage splits start evenly across selected participants.
Typing a value fixes only that person's value. The unedited rows divide the
remainder evenly. The same rule applies to selected people who paid when
several people paid. Share counts remain ratios, initially one each.

The default fields show just their values. Custom values have a small pin
button; its tooltip describes the action, **Let [name]'s value adjust**.
Tapping the pin or clearing the value lets that person share the remainder
again. **Split evenly** resets the selected participants, and **Divide evenly**
does the same for multiple payers. These actions appear only when the section
has custom values. There are no repeated Auto/Manual labels or instructional
footers. Screen-reader hints still distinguish fixed values from ones that
follow the remainder; validation errors remain visible and announced.

While typing, the field keeps the raw text and caret; a cleared field shows
its computed value when focus leaves. Focusing a value does not fix it.

Amounts use integer currency minor units. Automatic percentages retain the
model's six-decimal precision so rounding percentages does not skew an
otherwise equal money split. Largest-remainder allocation distributes
indivisible units in a stable order. A new expense generates its UUID locally
when the draft opens, so the preview and saved allocation use the same rounding
seed. This allocates an identifier in memory; it does not create an expense,
insert a placeholder row, enqueue a sync operation, or reserve anything on the
server. Cancel discards the unsaved draft. Save supplies that UUID to the normal
repository transaction, which writes the complete entry, history and outbox
item together. The server already accepts client-generated entry identifiers.

Manual values never change to force a balance. If they exceed the total, if
every row is manual and a remainder is left, or if an input cannot be parsed,
the editor explains the problem and refuses saving. A selected payer must pay
more than zero; a participant may owe zero.

Changing the total, currency, or selection recalculates automatic rows.
Currency changes reinterpret the raw typed amounts with the chosen currency's
precision; invalid manual values must be corrected explicitly. Deselected
rows do not contribute, but retain their overrides if selected again. Switching
methods retains each method's own overrides rather than translating unrelated
weights into amounts or percentages.

When reopening an expense, all saved values of its active amount/percentage
method, and saved values for multiple payers, start manual. This preserves the
agreed allocation. The user can release individual overrides or reset that
section to automatic. No automatic/manual metadata is persisted: it describes
the editing session, not the recorded transaction.

## Structure

`RemainderAllocation` is a pure integer calculation shared by money,
percentages and payers. Form state stores selections and raw manual overrides;
automatic values are derived. The same derivation drives fields, previews,
validation and the saved draft. The domain's existing split and payer
validators remain the final write boundary. No API or database migration is
required.

Material text fields, checkboxes and icon/text buttons provide interactions.
The field adapter owns only Flutter's text controller and focus lifecycle; it
does not own financial values or implement another input widget.

## Verification

Cover fixed-value preservation, exact conservation, stable rounding, all
manual and no-member cases, zero and invalid inputs, selection/method/total/
currency changes, reopening, multiple payers, and preview/save equivalence.
Exercise typing and clearing while focused, reset actions, field refresh after
external form changes, and narrow layouts with large text in widget tests.
Regenerate product screenshots and their promotional derivatives after the
visible UI changes, then build and render through local Wrangler.

Validated on 11 October 2026:

- All 657 Flutter tests passed with `REQUIRE_BACKEND=true` against the local
Worker, including domain, form, repository and widget regressions.
- Static analysis reported no issues; the production web build succeeded.
- Actual Flutter renders and the regenerated store/landing assets were checked.
- At a 390 × 844 browser viewport, amount and multiple-payer overrides of ₹120
left ₹180 for the other automatic row on a ₹300 expense. Reset restored
₹150 each. A 40% override produced 60% and matching ₹120/₹180 previews.

After simplifying the controls, all 53 targeted domain, form and widget tests
passed, including screen-reader hints, individual pins, section resets and
narrow layouts. Static analysis remained clean and the fix dry run found
nothing to fix. Flutter screenshots, promotional derivatives and the
production web bundle were regenerated. The rebuilt local Wrangler preview
confirmed ₹120/₹180 and the pin restoring ₹150 each without status helper rows.

The live demo draft was left unsaved. Browser rendering and automated tests
cover this change; it has not been verified on a physical Android device.
Loading