A screen-time app for the only part of your phone that actually eats hours:
Instagram Reels and YouTube Shorts.
It times how long you spend in those two feeds, shows the number while you are in them, and blocks the feeds once you pass a daily limit you set. Android only. No account, and nothing leaves your device.
Your phone's built-in screen-time report tells you what you did yesterday. That is too late to act on, and it measures the wrong thing: "3 hours on Instagram" cannot tell the difference between messaging a friend and thumbing through Reels at 1am.
Unhook measures only the short-form feeds. It keeps a live timer on screen while you scroll, interrupts you when you are spiraling, and takes the feeds away entirely once you have spent your daily allowance. Days you stay under the limit earn points, and points unlock cat pictures, so there is something on the other side of stopping.
Guilt counts. A floating timer runs while you scroll and the clock reddens as your budget drains. The feeds keep working right up to your daily limit, then get blocked for the rest of the day, with at most three one-minute reprieves.
Block refuses outright. Every reel and short is walled off from the moment you open the app, all day. Leaving Block mode makes you confirm it in writing.
Lowering your limit takes effect instantly. Raising it waits until tomorrow's reset, so you cannot talk your way out of a block mid-scroll.
A floating pill sits over the feed with the time so far, and a cat that looks progressively worse as the budget drains.
Separately, a card interrupts when the timing is bad rather than when the budget is gone. There are nudges for the late-night scroll, the first-thing-in-the-morning scroll, the mid-workday scroll, every three minutes of continuous reel time, and the moment you pass yesterday's total.
"Keep scrolling" is a real button. A wall you can never pass just gets uninstalled.
The inline feed reels get blanked and replaced with a cat, and the overlay eats touches, so scrolling through it does nothing. The cat and the line rotate each time the block reappears, drawn only from the cats you have already unlocked.
Each day pays out the share of your limit you did not spend. Points unlock thirteen cats, frequent at first and further apart later. The first is free, so the gallery is never empty.
Unlocking only moves one way. Today's points shrink in real time as you scroll, so the gallery is gated on your high-water mark, and a cat you earned cannot re-lock at four in the afternoon.
Seven days, thirty, or everything, by time or by count. History starts the day you install, fills in one day at a time, and never leaves the device. The widget carries the same number to your home screen.
The Accessibility API is the only mechanism on Android that can tell an app a Reels or Shorts player is on screen inside another app. Usage-stats APIs report only which app is in the foreground, which cannot separate watching Reels from reading DMs.
The service is scoped in its configuration to exactly two packages, com.instagram.android and com.google.android.youtube. It reads the view tree in the moment to answer one question, "is a short-form player on screen right now", increments a timer, and at the limit navigates away from the feed. Screen content is evaluated and discarded.
Nothing is recorded, logged, or transmitted. There is no code anywhere in the app that makes a network request, in JS or in the native service. (INTERNET does appear in the merged manifest, because React Native's template declares it, not because anything uses it.) Everything, history included, lives in a single SharedPreferences file on the device.
So it needs two permissions, both explained before either is requested:
Download the APK from the latest release.
arm64 only, which covers any phone from roughly 2017 onward. Older 32-bit devices will refuse to install it. Release APKs are signed with a development key rather than a Play Store key, so if you later move to a Play build you will need to uninstall first.
| Node | 20 or newer |
| JDK | 21 (java -version) |
| Android SDK | platform-tools on PATH, ANDROID_HOME set |
| Device | a physical Android phone with Instagram or YouTube installed |
A physical device matters. The whole app keys off two real packages and needs an accessibility service plus an overlay permission, none of which is meaningful on a bare emulator.
git clone https://github.com/rogerantony-dev/unhook.git
cd unhook
npm install
npx expo prebuild --platform android # generates android/ from app.json + plugin/
npx expo run:android # debug build, installs, starts Metronpx expo run:android produces a dev-client build that loads JS from Metro, so you get fast refresh on everything in App.tsx and components/.
Then enable both permissions on the device, which the app's onboarding walks you through:
- Draw over other apps → allow for Unhook.
- Settings → Accessibility → Unhook Reel Counter → on.
Open Instagram Reels and the pill should appear within a second or two.
This is what the GitHub releases ship: a standalone APK with the JS bundled in, no Metro.
npx expo prebuild --platform android
cd android
./gradlew assembleRelease -PreactNativeArchitectures=arm64-v8a
adb install -r app/build/outputs/apk/release/app-release.apkDrop the -PreactNativeArchitectures flag to build all four ABIs, which roughly doubles the APK size.
npm testJest covers the pure logic, which is deliberately kept free of React and native calls: points and streaks (components/progress.ts), history bucketing (history.ts), nudge selection (nudges.ts), and the clock's transform matrices (catmatrix.ts).
android/ is generated and gitignored. Editing it directly works until the next prebuild silently overwrites your changes. The real sources are:
| Path | What it is |
|---|---|
plugin/native/ReelAccessibilityService.kt |
Detection, timing, blocking, the pill, the cover, the nudges |
plugin/native/UnhookWidgetProvider.kt |
The home-screen widget |
plugin/native/res/ |
Widget layouts, cat drawables |
plugin/withReelCounter.js |
Config plugin that copies all of the above into android/ at prebuild, and patches the manifest |
modules/unhooknative/ |
Expo module exposing the service's state to JS |
Edit those, re-run npx expo prebuild --platform android, rebuild.
To add a cat: append to CATS in components/cats.ts, drop the image in assets/cats/, drop a copy in plugin/native/res/drawable/ as unhook_cat_<n>.png, and bump catCount in the service. The list order is the unlock order, and the native side indexes by position, so the two must stay aligned.
adb shell am force-stopkills the accessibility service, and Android leaves it switched off. The app then drops back to onboarding until you re-enable it in Settings. Useadb shell input keyevent KEYCODE_HOMEinstead when you just want the app backgrounded.- Raising the daily limit does nothing today. It is deferred to the next daily reset on purpose. Lowering it is instant.
expo prebuildrewritesandroid/gradle.properties, so pin the ABI with the-Pflag on the Gradle command rather than editing that file.- The daily counters roll over at local midnight, when the service next sees a new date.
No analytics, no crash reporting, no network calls, no account. The privacy policy is the long version of that sentence.
MIT.










