fix: repair broken UX flows and dead ends across the app - #25
Open
FlazeIGuess wants to merge 3 commits into
Open
fix: repair broken UX flows and dead ends across the app#25FlazeIGuess wants to merge 3 commits into
FlazeIGuess wants to merge 3 commits into
Conversation
Sharing / processing - Fix the endless spinner: setting an error no longer leaves _isLoading true, so failures actually render an error state instead of hanging forever. - Add "Try Again" and a Cancel action; make the result area scrollable so the alternative-service list cannot overflow small screens. - Loop the loading pulse with a repeating controller instead of a TweenAnimationBuilder that fired once and froze. - Honour the preferred music service on the metadata fast path; it previously opened the sender's platform link verbatim. Playlists - Resolve per-platform links lazily for received playlists so tracks open in the recipient's own music app, which is the point of the feature. - Import screen: wrap in try/catch, add timeouts, and give the error state a retry plus a way back; re-opening a link now reuses the existing import instead of creating a duplicate. - Warn when a playlist could not be published instead of silently dropping the user on a share screen whose share will fail. - Confirm before discarding an in-progress playlist (close button and system back). - Enforce the title/description limits that were already shown as errors. - Fetch converted links for tracks added from history. - Use pushReplacement/pop instead of go so Back no longer exits the app. - Replace 24dp icon-only buttons with labelled 48dp targets; add pull to refresh. History - Add per-entry delete with undo; previously only "Clear All" existed. - Block re-convert actions behind a progress dialog so repeated taps cannot fire parallel requests. - Include the nickname when re-sharing and record the re-share so statistics stop under-reporting. - Show a filter-aware empty state with a "Clear filter" action. Home / navigation / settings - Back now returns to the Home tab before leaving the app. - Open the tutorial as a pushed flow with a close button so it is no longer a one-way trip. - Stop the 3s rebuild timer once real data is available. - Link field: keyboard submit, clear button, reset after sharing. - Own the nickname controller in a StatefulWidget (was rebuilt and leaked on every build) and debounce writes. - Read the version from package metadata instead of a hardcoded string. - Explain the "Open by default" steps before jumping to system settings. - Correct the support copy that claimed the app is ad-free. Platform / plumbing - Declare custom-scheme queries in the manifest; without them canLaunchUrl reported installed apps as missing (SMS in particular). - Route link-encoder logging through debugPrint so URLs and nicknames stay out of release logs. - Add timeouts and null-safe parsing to the playlist backend calls. - Add an on-demand debug-APK workflow.
The debug build type now carries applicationIdSuffix ".dev", so a test APK installs next to an existing release install instead of trying to replace it. Both release and CI builds are debug-signed with keystores generated per machine, so their signing certificates differ. An in-place update is therefore rejected with INSTALL_FAILED_UPDATE_INCOMPATIBLE and forces an uninstall, which wipes history, playlists and settings — all app state lives in SharedPreferences and there is no export path. - applicationIdSuffix ".dev" and versionNameSuffix "-dev" for debug builds. - App label moved to a manifest placeholder: "UniTune" vs "UniTune Dev", so the two installs are distinguishable in the launcher, the share sheet and the link chooser, where both register the same intent filters. - AdMob app id moved to a manifest placeholder as well. The ".dev" application id is not registered with AdMob, so debug builds point at Google's public test app id; USE_TEST_ADS switches the ad units to match. Production builds are unaffected: both placeholders default to the existing values. - The dev workflow prints the APK's signer certificate, so its signing identity can be compared against an installed build without downloading it first.
The bar had no sliding indicator at all, despite the doc comment claiming one. Each tab painted its own background when selected, so the highlight appeared to jump because one background faded in while another faded out. Item widths were also non-uniform and changed mid-transition, since only the selected tab revealed its label via AnimatedSize. There was no stable geometry a drag could follow. Rebuilt around a single pill positioned by a continuous position value: - Drag anywhere on the bar to move the pill; release snaps to the nearest item, and a flick carries to the neighbour. - Tapping is unchanged and still wins the gesture arena when the finger does not move. - Haptic detent each time a new item becomes the nearest, plus the existing impact on commit. - Every item expands to the same width, derived from the widest measured label, and the expansion budget is split between the two items the pill straddles. The total width is therefore constant and nothing reflows during the drag. - Labels reveal horizontally via a width factor rather than reflowing, so text never wraps or jumps. - The bar shrinks the expansion budget to fit narrow screens instead of overflowing. - External index changes (system back returning to Home, a screen jumping to the Playlists tab) animate the pill, guarded so the callback-triggered rebuild cannot restart the settle animation mid-flight. Selection is committed on release, not during the drag, so the content transition in MainShell is not thrashed while the finger moves. Adds 7 widget tests covering tap, drag-to-next, snap-back below the threshold, multi-slot drags, clamping at both ends, external index changes, and the constant-width invariant.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes flow bugs and gaps found while auditing the app end to end. No release is created by this PR — the APK is uploaded as a workflow artifact under the Actions run only.
Sharing / processing
_errorwithout clearing_isLoading, sobuildkept rendering the loading state. Hitting "song not available on your service" left the user stuck forever with no way out.TweenAnimationBuilderwith a fixed tween and anonEndthat calledsetState; the tween never changed so it pulsed once and froze. Replaced with a repeating controller.Playlists
try/catcharound a bare HTTP call and an error state with no actions — a failed import was an infinite spinner or a dead end. Now: timeouts, retry, and a way back.convertedLinks, making them unplayable for anyone on another service.context.goin the create/detail/post-create flow wiped the navigation stack, so Back exited the app. Replaced withpushReplacement/pop.History
onEntryDeletewas wired up but never reachable — only "Clear All" existed. Added a delete action with undo.Home / navigation / settings
context.go, replacing the stack with an onboarding flow that had no exit — users were trapped until they walked every remaining step. It is now a pushed flow with a close button.Timer.periodicrebuilt the whole Home screen every 3s forever, including once the rotating placeholder was no longer displayed.TextEditingControllerwas constructed insidebuild— leaked on every rebuild and reset the caret mid-typing. Now owned by aStatefulWidget, with debounced writes.Platform / plumbing
<queries>declared no custom-scheme intents, socanLaunchUrlreported installed apps as missing on Android 11+. SMS had no<package>entry at all, so that messenger choice always silently fell back to the system share sheet.link_encoder.dartusedprintin 19 places, leaking user URLs and nicknames into release logcat.returninstead ofcontinueabandoned a whole multi-item share if the first item was a deep link.'/p/'substring test matched non-UniTune URLs; now host-scoped.Verification
flutter analyze— no errors, no warnings (21 remaining infos are pre-existing, mostlyavoid_printin unused*_example.dartfiles).flutter test— 6/6 pass, including two new cases covering import deduplication and persisted link conversion.Not addressed (flagged for follow-up)
processing_screen_v2.dart,share_intent_handler.dart,track_search_screen.dart,batch_conversion_service.dart,cover_collage_generator.dartand the*_example.dartfiles are referenced from nowhere.deleteTokenis stored but never used.AdHelper.defaultRequestalways setsnonPersonalizedAds: true, so the UMP consent flow's outcome is never applied.Generated by Claude Code