Skip to content

Repository files navigation

Loopky

Loopky is a free, open source flashcards app with spaced repetition, and a full replacement for Anki for most learners. Import your Anki decks, study on your phone or tablet, and manage decks from your computer with the command line tool. It reads cards aloud, checks your pronunciation, and shares decks with your class through one link. Get it on Google Play · loopky.app

Features

  • Spaced repetition with Again, Hard, Good and Easy. Each button shows the interval it gives, and you can change the intervals.
  • Import Anki .apkg files, .txt and .csv, or paste a list straight in.
  • Listen, Speak and Type modes in 35 languages: hear the card, say the answer and have your pronunciation checked, or type it.
  • Reverse cards and images on either side of a card.
  • Two-pane tablet layouts, not a stretched phone screen.
  • Share decks by link. Publish a deck, follow and copy other people's, and find new ones in Discover.
  • A desktop command line tool to manage decks and cards in bulk, or to let an AI agent build them for you — see cli/README.md.
  • No ads and no analytics.

Guides

How it works

Loopky blends Duolingo TinyCards' playfulness, Anki's spaced repetition, and Pubky's decentralized identity and social graph. iOS + Android, built with Kotlin Multiplatform.

There is no Loopky account and no Loopky server. You sign in with a key held by Pubky Ring, and your decks and study progress are written to a Pubky homeserver you hold the key to.

  • Architecture: docs/Architecture.md — module layout, layering, homeserver data model, Pubky/Nexus integration, what is still open. Start here.
  • Import spec: docs/specs.md — the paste-to-import flow and the parser rules the test suite is written against.
  • Privacy: PRIVACY.md — what reaches a homeserver, and whose it is.

Screenshots

Captured against a live Pubky homeserver on both platforms. Full-resolution files live in screenshots/ and double as the store asset sets.

Android

Captured on the v0.6.0 debug build — a Pixel phone emulator (1080×2400) and the Pixel_Tablet emulator in landscape (2560×1600), the two width classes the adaptive layouts target.

Tablet

Home, deck detail and onboarding are real two-pane layouts at expanded width, with a navigation rail in place of the tab bar — not a stretched phone column.

Loopky on an Android tablet: today's queue beside the deck grid
Today — queue beside the deck grid
Loopky on an Android tablet: the deck library in four columns
Library — four columns at expanded width
Loopky on an Android tablet: deck details beside the full card list
Deck — metadata beside the full card list
Loopky on an Android tablet: studying a card, with Listen and Speak buttons
Study — the prompt side
Loopky on an Android tablet: the grade buttons beside the card
Grade — grades move beside the card
Loopky on an Android tablet: Discover, with trending tags and decks to follow
Discover — trending tags and follows
Loopky on an Android tablet: your profile and study totals
Profile — identity and totals
Loopky on an Android tablet: sign-in on one side, what Loopky is on the other
Sign in — split across two panes

Phone

Loopky on an Android phone: sign in with Pubky Ring, no account or password
Sign in
A key, not an account
Loopky on an Android phone: today's queue of cards to review
Today
The daily queue
Loopky on an Android phone: the deck library
Library
Your decks
Loopky on an Android phone: a deck with its cover, tags, study stats and cards
Deck
Stats, tags, cards
Loopky on an Android phone: studying a card, with Listen and Speak buttons
Study
Listen and Speak
Loopky on an Android phone: the answer side, with Again, Hard, Good and Easy buttons showing each interval
Grade
The button says the interval
Loopky on an Android phone: Discover, with trending tags and decks to follow
Discover
Decks and tags
Loopky on an Android phone: your profile and study totals
Profile
Your Pubky identity
Loopky on an Android phone: importing a deck by pasting a list of cards
Paste import
Separator auto-detected
Loopky on an Android phone: creating an account by SMS, Lightning or invite code
Signup
SMS, sats or invite

Settings and the recovery-phrase screens are FLAG_SECURE, so they cannot be screenshotted — that is deliberate, and why they are absent here.

iOS

Captured on the Release build (production Nexus + Homegate) against a live homeserver, on the iPhone 17 Pro Max simulator (1320×2868) and the iPad Pro 13-inch simulator (2064×2752) — the two sizes App Store Connect asks for. The SwiftUI screens are native, not Compose Multiplatform, and the iPad has its own regular-size-class layouts rather than a stretched phone column.

iPad

Loopky on an iPad: today's queue beside the deck grid
Today — queue beside the deck grid
Loopky on an iPad: the deck library in several columns
Library — multi-column at regular width
Loopky on an iPad: studying a card, with Listen and Speak buttons
Study — the prompt side
Loopky on an iPad: the grade buttons beside the card
Grade — grades move beside the card
Loopky on an iPad: Discover, with trending tags and decks to follow
Discover — four columns of decks and tags
Loopky on an iPad: your profile and study totals
Profile — identity and totals
Loopky on an iPad: sign in by scanning a QR code with Pubky Ring on your phone
Sign in — a simulator has no Pubky Ring, so the QR handoff is raised automatically

iPhone

Loopky on an iPhone: today's queue of cards to review
Today
The daily queue
Loopky on an iPhone: the deck library
Library
Your decks
Loopky on an iPhone: a deck with its cover, tags, study stats and cards
Deck
Stats, tags, cards
Loopky on an iPhone: studying a card, with Listen and Speak buttons
Study
The prompt side
Loopky on an iPhone: the answer side, with Again, Hard, Good and Easy buttons showing each interval
Grade
Listen, Speak, and the interval on the button
Loopky on an iPhone: Discover, with trending tags and decks to follow
Discover
Decks and tags from people you follow
Loopky on an iPhone: your profile and study totals
Profile
Your Pubky identity

Status

Android is feature-built end to end. Onboarding and Pubky Ring sign-in, homeserver signup (SMS / Lightning / invite code), the daily study queue, the SRS study loop with Listen / Speak / Type, deck library and editor, paste import and bulk file import (.txt / .csv / .apkg), publishing, discovery and tag browse, profiles and follows, and settings.

iOS is at feature parity. Browse without an account, then sign in with Pubky Ring — by deeplink, or by scanning a QR from the phone that holds your key — or create an account on a homeserver (SMS, Lightning or invite code), restore one from a recovery phrase or file, and back a key up three ways. Then the daily study queue, the SRS study loop with Listen / Speak / Type and reverse cards, the deck library and editor, paste import and bulk file import (.txt / .csv / .apkg), publishing, discovery and tag browse, profiles and follows, and settings including the synced study intervals. Driven against a real homeserver on the iPhone 17 simulator; see the iOS sections of journeys/RESULTS.md, which also record what could not be reached there and why.

iPad and every width but a phone is #173.

Roughly 1,300 shared tests run on every PR.


Architecture at a glance

Business logic is shared; UI is native per platform. That is the one rule to internalize.

  • shared/ — KMP module holding domain models, repositories (which own the business logic — there is no use-case layer), and the ViewModels both platforms consume. Platform glue (Pubky FFI, TTS, speech recognition, background work) is either expect/actual or a Koin-bound interface.
  • androidApp/ — the Android app. Jetpack Compose screens, Navigation Compose, Koin. Android-only, and a plain com.android.application module rather than a KMP one; Compose Multiplatform UI is not used for iOS.
  • iosApp/ — the iOS app. SwiftUI screens, NavigationStack, Koin bootstrap.

Pubky is reached through one interface, PubkyClient, over the UniFFI bindings generated by pubky-core-ffi-fork and checked in. Published decks live on the author's homeserver as a manifest plus chunked card records; the homeserver is the source of truth and the app is not offline-first. Global questions a single homeserver cannot answer — trending tags, search, "who else uses Loopky" — go to the Pubky Nexus indexer.

Module layout

loopky/
├── shared/
│   └── src/
│       ├── commonMain/kotlin/com/github/jvsena42/loopky/
│       │   ├── domain/        # models (pure Kotlin, no framework imports)
│       │   ├── data/          # repositories, PubkyClient, Nexus, storage
│       │   └── presentation/  # ViewModels (StateFlow + SharedFlow)
│       ├── commonTest/        # the whole automated suite
│       ├── androidMain/       # Pubky FFI, TTS, speech, WorkManager, Koin
│       └── iosMain/           # Pubky adapter, TTS, speech, BGTaskScheduler, Koin
│
├── androidApp/src/main/kotlin/com/github/jvsena42/loopky/
│   ├── ui/                    # Compose screens + navigation
│   ├── LoopkyApp.kt           # Application; starts Koin
│   └── MainActivity.kt
│
├── iosApp/iosApp/
│   ├── Views/                 # SwiftUI screens
│   ├── Navigation/            # NavigationStack
│   ├── DI/                    # Koin bootstrap + Flow→SwiftUI bridge
│   ├── Pubky/                 # IosPubkyClient + generated bindings
│   └── iOSApp.swift
│
├── cli/                       # `loopky`, the headless client — a GraalVM binary on :shared's
│                              # jvm() target, so an agent can drive Loopky without a screen
│
└── journeys/                  # scripted end-to-end journeys + results

Stack

Concern Choice
UI (Android) Jetpack Compose + Material 3 Expressive
UI (iOS) SwiftUI + NavigationStack
Shared logic Kotlin Multiplatform (commonMain)
DI Koin
Async Coroutines + Flow; hand-rolled Swift bridge (IosFlowWatcher / FlowObserver)
Persistence Pubky homeserver + in-memory session cache — no local database
Secrets Liftric KVault → Android Keystore / iOS Keychain
Identity / social Pubky (pubky-core-ffi-fork, UniFFI) + Nexus indexer for global reads
Navigation Per-platform native
Lint detekt (Kotlin) · SwiftLint (Swift)

Android minSdk 29, targetSdk 36.


Build and run

Android

./gradlew :androidApp:assembleDebug

Or use the run configuration from your IDE's toolbar.

iOS

Open /iosApp in Xcode and run. shared is consumed as a static framework.

Tests and checks

./gradlew :shared:allTests              # shared KMP tests (~1,300)
./gradlew :shared:compileKotlinMetadata # fast commonMain compile check
./gradlew detektAll                     # Kotlin lint (add --auto-correct to fix formatting)
./gradlew lintSwift                     # Swift lint (needs `brew install swiftlint`)

CI runs detekt, the unit tests, and an Android debug build on every PR.

End-to-end coverage is manual and scripted: journeys/ holds 25 numbered journeys driven on a device with android-cli, with results and dates in journeys/RESULTS.md. A green build says nothing about what the screen renders.


Learn more about Kotlin Multiplatform.

About

Free, open source flashcards app with spaced repetition. Imports Anki decks, reads cards aloud, checks pronunciation, shares decks by link. Phone, tablet, and a desktop CLI.

Topics

Resources

Stars

8 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages