Skip to content

feat: notif permission rationale + chat input icon animation - #71

Closed
alichherawalla wants to merge 7 commits into
mainfrom
fix/chat-input-icons-hide-on-focus-clean
Closed

alichherawalla wants to merge 7 commits into
mainfrom
fix/chat-input-icons-hide-on-focus-clean

Conversation

@alichherawalla

@alichherawalla alichherawalla commented Feb 23, 2026 •

Copy link
Copy Markdown
Collaborator

What

Two improvements shipped together:

1. Notification permission rationale modal (Android 13+)

On Android 13+ (API 33), instead of silently requesting POST_NOTIFICATIONS at app launch, an in-app rationale modal now shows the first time a user tries to download a model from the Models screen.

2. Chat input icon animation on focus

When the chat input gains focus the three right-side icons (paperclip, eye, image) slide off to the right and fade out, freeing up the full pill width for typing. On blur they slide back in.

Why

Rationale modal: The previous approach fired the OS permission dialog cold on startup with no context. This gives users a clear explanation before the OS dialog, improving transparency and reducing dismissal rates.

Icon animation: Maximises the input area when the user is actively typing without removing the icons entirely.

How

Rationale modal:

  • Removed startup requestNotificationPermission() call from App.tsx
  • Added maybeShowNotifRationale() in useModelsScreen that intercepts the first download attempt when: platform is Android 13+, no models downloaded yet, permission not already granted
  • Two paths: Allow (requests permission then proceeds) or No thanks (skips permission, still proceeds — download is never blocked)
  • 9 unit tests covering all gate conditions and handler paths

Icon animation:

  • Added useIconsAnimation hook driving three Reanimated shared values: iconsWidth, iconsTranslateX, iconsOpacity
  • Focus: collapses width 108→0, slides right, fades — 200ms
  • Blur: reverses — 250ms
  • Respects useReducedMotion() — instant when reduced motion is enabled

Test plan

  • Android 13+, first download attempt → rationale modal appears before download starts
  • Allow → OS permission dialog shown, download proceeds
  • No thanks → download proceeds without permission dialog
  • Second download (model already present) → no modal
  • iOS / Android < 13 → no modal ever shown
  • Tap into chat input → icons slide right and disappear, input widens
  • Tap away → icons slide back in
  • Reduced Motion enabled → no animation, icons simply hide/show instantly

github-actions Bot and others added 2 commits February 23, 2026 17:10
On Android 13+, intercept the first download attempt on the Models
screen to explain why notification permission is needed and that it is
optional. The OS dialog only appears if the user taps Allow; either
way the download proceeds unblocked.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the text input gains focus, the paperclip/eye/image icons slide
right and fade out (200ms) while their width collapses to 0 so the
input expands to fill the freed space. Reverses on blur. Respects
reduced-motion accessibility setting.
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @alichherawalla, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the user experience by introducing a dynamic animation for the chat input, allowing more space for typing when focused. Concurrently, it improves the handling of Android notification permissions for downloads, making the process more transparent and user-friendly by prompting the user with a clear rationale before requesting the permission.

Highlights

  • Chat Input Animation: Implemented a smooth slide and fade animation for the chat input's right-side icons (paperclip, eye, image) when the input gains focus. This frees up the full width of the input pill for text entry.
  • Accessibility for Animations: Ensured that the new chat input animation respects the user's useReducedMotion setting, providing instant hide/show behavior when reduced motion is enabled.
  • Android Notification Permission Flow: Refactored the Android 13+ POST_NOTIFICATIONS permission request for download progress. It is now presented as a user-facing alert on the first download attempt, giving users a clear choice to allow or deny.
Changelog
  • App.tsx
    • Removed the direct, unconditional request for POST_NOTIFICATIONS permission during app initialization.
  • src/components/ChatInput/index.tsx
    • Imported React Native Reanimated hooks and components (Animated, useSharedValue, useAnimatedStyle, withTiming, useReducedMotion).
    • Defined shared values (iconsWidth, iconsTranslateX, iconsOpacity) to control the animation of the icons.
    • Created animatedIconsStyle to apply dynamic width, transform, opacity, and overflow styles.
    • Implemented handleInputFocus and handleInputBlur functions to animate the icons based on input focus state, respecting useReducedMotion.
    • Applied onFocus and onBlur event handlers to the TextInput component.
    • Replaced the static View for pillIcons with Animated.View and applied the animatedIconsStyle.
  • src/components/ChatInput/styles.ts
    • Added overflow: hidden to the pillContainer style to ensure icons clip cleanly during animation.
  • src/screens/ModelsScreen/index.tsx
    • Added a new CustomAlert component to display the notification permission rationale to the user.
  • src/screens/ModelsScreen/useModelsScreen.ts
    • Imported useRef, useCallback, PermissionsAndroid, backgroundDownloadService, and logger.
    • Introduced showNotifRationale state and pendingDownload ref to manage the notification permission alert.
    • Implemented isFirstDownload logic to determine when to show the notification rationale.
    • Created maybeShowNotifRationale to conditionally present the notification permission alert on Android 13+ for the first download.
    • Wrapped handleDownload and handleDownloadImageModel with maybeShowNotifRationale to integrate the new permission flow.
    • Implemented handleNotifRationaleAllow to request permission and proceed with the pending download.
    • Implemented handleNotifRationaleDismiss to dismiss the alert and proceed with the pending download without requesting permission.
    • Exported showNotifRationale, handleNotifRationaleAllow, and handleNotifRationaleDismiss from the hook.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

Covers maybeShowNotifRationale platform/version/first-download/permission
gates, handleNotifRationaleAllow (with and without rejection), dismiss,
and the image model download path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request successfully implements a smooth slide-and-fade animation for the chat input icons, improving the user experience by maximizing space for text entry. It also refactors the Android notification permission request to a rationale-based flow in the Models screen. The implementation is generally solid, but there are opportunities to improve performance through stable hook references and to ensure the notification rationale logic doesn't inadvertently exclude existing users.

I am having trouble creating individual review comments. Click here to see my feedback.

App.tsx (78-80)

medium

Removing the notification permission request from the app startup is a good UX improvement. However, the new logic in ModelsScreen only prompts users on their 'first download'. This means existing users who already have models will never be prompted for this permission unless they delete their models or manually find the setting. Consider a more inclusive check for the rationale.

src/components/ChatInput/index.tsx (1)

medium

The useCallback hook is needed for the suggested performance improvements in the animation handlers.

import React, { useState, useRef, useCallback } from 'react';

src/components/ChatInput/index.tsx (56)

medium

The ICONS_WIDTH constant is currently defined inside the component. Since it is a static value derived from the icon dimensions (3 icons * 36px), it should ideally be defined outside the component to avoid recreation on every render and to serve as a single source of truth for the animation logic.

src/components/ChatInput/index.tsx (68-80)

medium

These animation handlers are recreated on every render of the ChatInput component (which happens on every keystroke). Wrapping them in useCallback will provide stable references to the TextInput props and prevent unnecessary overhead.

  const handleInputFocus = useCallback(() => {
    const duration = reducedMotion ? 0 : 200;
    iconsTranslateX.value = withTiming(ICONS_WIDTH, { duration });
    iconsOpacity.value = withTiming(0, { duration });
    iconsWidth.value = withTiming(0, { duration });
  }, [reducedMotion, iconsTranslateX, iconsOpacity, iconsWidth]);

  const handleInputBlur = useCallback(() => {
    const duration = reducedMotion ? 0 : 250;
    iconsTranslateX.value = withTiming(0, { duration });
    iconsOpacity.value = withTiming(1, { duration });
    iconsWidth.value = withTiming(ICONS_WIDTH, { duration });
  }, [reducedMotion, iconsTranslateX, iconsOpacity, iconsWidth]);

src/screens/ModelsScreen/useModelsScreen.ts (135-136)

medium

The isFirstDownload check restricts the notification rationale to only new users. Existing users who already have models will skip this logic and never be prompted for the permission that was removed from App.tsx. It would be better to check if the permission has been requested before or simply check the current permission status using PermissionsAndroid.check without the isFirstDownload constraint.

src/screens/ModelsScreen/useModelsScreen.ts (153-165)

medium

These callbacks are recreated on every render because they depend on the text and image objects, which are themselves recreated on every render of useModelsScreen. This can lead to unnecessary re-renders of the tab components. Consider stabilizing the objects returned by useTextModels and useImageModels using useMemo within those hooks.

@alichherawalla alichherawalla changed the title fix: hide chat input icons with slide animation on focus feat: notif permission rationale + chat input icon animation Feb 23, 2026
…hatInput

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@codecov

codecov Bot commented Feb 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.39535% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.89%. Comparing base (16b4994) to head (1752fde).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/components/ChatInput/index.tsx 50.00% 8 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main      #71      +/-   ##
==========================================
- Coverage   87.95%   87.89%   -0.06%     
==========================================
  Files         149      149              
  Lines        6436     6479      +43     
  Branches     1784     1789       +5     
==========================================
+ Hits         5661     5695      +34     
- Misses        411      419       +8     
- Partials      364      365       +1     
Files with missing lines Coverage Δ
src/components/ChatInput/styles.ts 33.33% <ø> (ø)
src/screens/ModelsScreen/index.tsx 91.30% <ø> (ø)
src/screens/ModelsScreen/useModelsScreen.ts 49.54% <100.00%> (+16.61%) ⬆️
src/components/ChatInput/index.tsx 86.07% <50.00%> (-9.17%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

github-actions Bot and others added 2 commits February 23, 2026 22:21
…liable icon hide on focus

- overflow: 'hidden' is not an animatable property and caused the
  animated style block to not apply on some RN versions
- translateX on the same view as the overflow clip does not work
  correctly (transform is applied after layout clipping)
- Move overflow: 'hidden' to the static pillIcons style
- Animate only width (collapse) + opacity (fade) which are reliable
  in Reanimated flex layouts
- Add 4 unit tests covering focus/blur animation with and without
  reduced motion

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- App.tsx: remove unused backgroundDownloadService import (fixes lint error)
- ChatInput: move ICONS_WIDTH outside component; add useCallback to
  handleInputFocus/handleInputBlur with stable deps
- useModelsScreen: drop isFirstDownload guard from maybeShowNotifRationale
  so existing users who revoke permission are also prompted; reference
  specific handler fns (textHandleDownload/imageHandleDownload) in
  useCallback deps instead of entire hook return objects

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@alichherawalla

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request introduces two main features: a notification permission rationale modal for Android 13+ and an animation for chat input icons on focus/blur. The changes are well-implemented, with appropriate use of React hooks and reanimated for the animation. The notification rationale logic correctly handles platform versions and permission states, and the associated unit tests provide good coverage. The animation logic also correctly respects reduced motion settings. Overall, the code maintains good quality and clarity.

… test

- ChatInput: restore iconsTranslateX shared value; on focus slides icons
  right (ICONS_WIDTH) + fades + collapses width; on blur reverses all three.
  pillIcons overflow set to 'visible' so the pill's overflow:hidden does the
  clipping as icons translate past the right edge.
- notifRationale test: update 'not first download' case to reflect the
  new behaviour — existing users with permission not granted now see the
  rationale modal instead of being silently passed through.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@alichherawalla

Copy link
Copy Markdown
Collaborator Author

Addressed all Gemini review comments — summary of changes across the two fix commits (a3f759a, d3a61ec):

App.tsx — removed unused backgroundDownloadService import (also fixed the CI lint failure).

ChatInput — moved ICONS_WIDTH constant outside the component; imported useCallback; wrapped handleInputFocus and handleInputBlur in useCallback with explicit stable deps (reducedMotion, iconsTranslateX, iconsOpacity, iconsWidth). Also restored the iconsTranslateX slide animation that had been dropped in an earlier fix — set pillIcons overflow to 'visible' so the static style (not useAnimatedStyle) owns it, and let the pill's existing overflow: 'hidden' clip the icons as they translate past the right edge.

useModelsScreen — removed the isFirstDownload guard from maybeShowNotifRationale. The PermissionsAndroid.check call is now the sole gate, so existing users who revoke the notification permission will also be shown the rationale modal. The useCallback deps now reference the specific handler functions (textHandleDownload, imageHandleDownload) rather than the entire text/image hook-return objects.

notifRationale test — updated the "not first download" test case to reflect the new intended behaviour: models already downloaded + permission not granted → modal still shown.

@sonarqubecloud

Copy link
Copy Markdown

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