A progress bar is easy to draw. The more useful question is what should be allowed to change it.
This small PythonIDE AppUI example tracks three manually completed focus sessions. It has four states: Ready, Focusing, Break, and Finished. Starting a session does not earn progress. Finishing a break does not earn progress. Only marking a focus session complete increases the count.
There is no timer, alarm, or background work. The screen helps you record a decision, not measure elapsed time.
1. Describe the transitions before building the screen
The first cycle is Ready → Focusing → Break → Ready. The count increases only when leaving Focusing. On the third completed session, Focusing goes directly to Finished instead of Break.
That gives us two pieces of state: phase and completed. The goal is fixed at three. Keeping these values separate makes it possible to show both the current activity and overall progress.
The early return in advance prevents the count from exceeding the goal, even if the final button is pressed again. Reset deliberately clears both values. Clearing only the count could leave the screen in an inconsistent Finished state.
2. Let callbacks change state and the view describe it
advance and reset contain the transition rules. body reads the current state to build the screen. The labels dictionary gives the primary button an action-specific label in each phase.
The progress view uses completed as its value and goal as its total. It does not have a separate progress variable to synchronize. The text and progress bar therefore derive from the same source.
The final button remains visible and says “All sessions complete.” Its callback safely returns without changing anything. A production design might disable that button if the current API supports it; the behavior here stays explicit in Python rather than depending only on appearance.
Run the complete example
Save this as focus_checkin.py in PythonIDE. AppUI is the app-specific UI module used here, not a Python standard-library module.
import appui
# Manual session state, not a background timer.
state = appui.State(phase="Ready", completed=0)
goal = 3
def advance():
if state.completed >= goal:
return
if state.phase == "Ready":
state.phase = "Focusing"
elif state.phase == "Focusing":
state.completed += 1
state.phase = "Finished" if state.completed == goal else "Break"
elif state.phase == "Break":
state.phase = "Ready"
def reset():
state.phase = "Ready"
state.completed = 0
def body():
labels = {"Ready": "Start a session",
"Focusing": "Mark session complete",
"Break": "Finish break",
"Finished": "All sessions complete"}
return appui.NavigationStack(
appui.ScrollView([
appui.VStack([
appui.Text("A small focus goal")
.font("title2").bold(),
appui.Text("Manual check-ins. No timer or alarm.")
.font("subheadline"),
appui.Text(f"Status: {state.phase}")
.font("headline"),
appui.ProgressView(value=state.completed, total=goal),
appui.Text(f"{state.completed} of {goal} sessions complete"),
appui.Button(labels[state.phase], action=advance)
.button_style("bordered_prominent"),
appui.Button("Reset this session plan", action=reset)
.button_style("bordered"),
], spacing=18, alignment="leading").padding(24)
]).navigation_title("Focus Check-in")
)
appui.run(body, state=state, presentation="fullscreen_with_close")
What the actual interaction check showed
The PythonIDE run was checked through all three sessions:
- Ready at 0 → Focusing → Break at 1, with about 33% progress.
- Finish break → Ready at 1 → Focusing → Break at 2, with about 67% progress.
- Finish break → Ready at 2 → Focusing → Finished at 3, with 100% progress.
- Pressing the final button again kept the count at 3.
- Reset returned the screen to Ready with a count of 0.
The screenshots show real Ready, Break, and Finished states. Testing the transitions matters more than seeing a correct first screen: a UI can render beautifully while its callbacks count the wrong events.
Know what this example does not measure
A manual tap is not proof that a timed session occurred. The app does not wait a specified number of minutes, schedule a notification, or continue a clock in the background. This example also makes no promise that state survives closing or relaunching it.
The goal is positive and fixed in the source. If you make it editable, define how lowering the goal below completed should work. The transition function assumes phases are one of the four known strings; a larger application should keep all state updates behind well-defined functions.
Try one change
Add a “Cancel current session” action that returns Focusing to Ready without increasing completed. Decide whether it should be available during Break or Finished, and explain why.
Then test these cases: cancel the first session, complete one session, cancel the next, and reset from Break. The count should change only when a session is explicitly marked complete.
I build PythonIDE, a Python coding app for iPhone and iPad. This example uses its AppUI module to make the state transitions visible.
AI assisted with drafting and translation. The interface and transitions were checked in PythonIDE before this review draft was prepared.






Top comments (0)