Skip to content

Rework the visa-free calculator - #7

Merged
ialakey merged 2 commits into
masterfrom
feat/visa-run-calculator
Sep 6, 2026
Merged

Rework the visa-free calculator#7
ialakey merged 2 commits into
masterfrom
feat/visa-run-calculator

Conversation

@ialakey

@ialakey ialakey commented Sep 6, 2026

Copy link
Copy Markdown
Owner

The visa run calculator assumed every user gets 30 days from a single entry
date, and it counted the remaining days wrongly.

The counting bug

Remaining days were computed by subtracting two DateTimes and reading
.inDays, which truncates. Entering on 1 January with a 30-day allowance:

On It said Correct
28 Jan 10:00 1 3
29 Jan 10:00 0 2
30 Jan 10:00 0 1

The last three days of a stay all read as "0 days left" — the days when the
number matters most.

Not everyone is on the same rule

The guide's own calculator article sets out three regimes, and the screen now
asks which one applies:

Rule Who
30 days per entry Russia, Belarus, China, Kazakhstan
90 days in any 180 EU, USA, Ukraine and most others
30 days in any 365 Bahamas, Barbados, Colombia, Indonesia, Jamaica, Paraguay, St Vincent

This matters because a visa run only resets the counter under the first rule.
Under a rolling window, leaving and coming back buys nothing — and the screen
now says so instead of implying otherwise.

A rolling window also cannot be answered from one date: it counts every day
spent in the country across the window, however many trips that took. So the
screen keeps a list of trips. The single date the old screen stored is
carried over on first run, so nobody loses what they had entered.

The arithmetic is its own file, and tested

lib/data/visa_rule.dart holds the calculation with no Flutter in it, under
15 unit tests. Two things it gets right that are easy to get wrong:

  • Both endpoints count. Enter 1 January, leave 2 January — that is two
    days of stay, as the guide states.
  • Calendar days are anchored to UTC midnight. Duration(days: 1) is
    exactly 24 hours, so adding days to a local date across the last Sunday in
    March gains an hour and can drop a whole day from a difference. A test
    covers the boundary.

The screen

A day counter with a progress ring, the must-leave-by date, and an overstay
warning when the allowance is gone. The deadline can be turned into a real
reminder — that reuses the existing deadlines feature rather than adding a
second list of dates to the app — or exported to the calendar as an all-day
event.

The old screen's animated GIF icons rendered as flat black shapes, its empty
state claimed "29 days left" next to a blank date, and it formatted every
date with a hardcoded Russian y г. suffix regardless of language. All gone.
Six localization keys the old dialog owned are removed; the new strings are
in both languages.

One layout fix worth knowing about

Buttons placed directly in a Row now sit in Expanded. The app's button
theme asks for a full-width minimum size, and a Row hands its children an
unbounded width — together they assert, and the bottom sheet was rendering
with no height at all: visible as a dimmed screen with nothing on it. It is
noted in the theme next to the minimum size that causes it.

Verification

  • dart format, flutter analyze (0 issues), 59 tests — 15 for the rules,
    6 driving the screen itself, including one that asserts the sheet has a
    non-zero size so the invisible-sheet bug cannot come back
  • Exercised on an Android 16 emulator in both themes: adding a trip, switching
    rules and watching the count change, creating the reminder, and the trips
    list

The screen assumed everyone gets 30 days from a single entry date, and its
remaining-days maths truncated: at 10:00 on the day before the deadline it
reported zero days left when two were still available.

Rules
-----
The guide's own calculator article describes three regimes, and the screen
now asks which one applies:

  30 days per entry     Russia, Belarus, China, Kazakhstan
  90 days in any 180    EU, USA, Ukraine and most others
  30 days in any 365    Bahamas, Barbados, Colombia and five more

A rolling window cannot be answered from one date — it counts every day
spent in the country over the last half year — so the screen keeps a list
of trips instead. The single date the old screen stored is carried over on
first run, so nobody loses what they had entered.

Arithmetic
----------
`lib/data/visa_rule.dart` holds the calculation on its own, with 15 tests.
Both the entry day and the exit day count, as the guide states. Calendar
days are anchored to UTC midnight: `Duration(days: 1)` is exactly 24 hours,
so adding days across the March clock change used to lose one.

Screen
------
A day counter with a progress ring, the must-leave-by date, an overstay
warning, and the trips list. The deadline can be turned into a reminder,
which reuses the existing deadlines feature rather than adding a second
list of dates, or exported to the calendar. Six localization keys the old
dialog owned are gone; the new strings are in both languages.

Layout
------
Buttons placed directly in a Row now sit in Expanded. The app's button
theme asks for a full-width minimum size, and a Row hands its children an
unbounded width — together they assert, and the bottom sheet was rendering
with no height at all. Noted in the theme where the minimum size is set.
@ialakey
ialakey merged commit 00c7791 into master Sep 6, 2026
2 checks passed
@ialakey
ialakey deleted the feat/visa-run-calculator branch September 6, 2026 02:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant