Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3c527c4
New library UI: floating pill nav bar, Keep Reading panel, pill chips…
Feb 22, 2026
2aa6151
New library UI: immersive detail scaffold with cover behind status bar
Feb 24, 2026
40d0de3
New library UI: ImmersiveSeriesContent + card shadow
Feb 24, 2026
4b3324f
New library UI: ImmersiveBookContent + ImmersiveOneshotContent + scaf…
Feb 24, 2026
bd08341
feat(ui): implement shared transitions and gesture system fixes
Feb 26, 2026
940a7a0
feat(reader): implement kinetic paged sticky swipe with RTL support
Feb 26, 2026
ca5dd1e
feat(reader): implement full page context sequence for panel navigation
Feb 26, 2026
5668bb6
feat(reader): implement unified smooth pan and zoom for panel navigation
Feb 27, 2026
b16c247
feat(reader): implement mode-specific 'Tap to zoom' toggle
Feb 27, 2026
6a7e9d3
feat(android): upgrade panel detection model to rf-detr-med
Feb 27, 2026
e6e0ac6
feat(reader): smooth animated transitions for Paged and Panel modes
Feb 27, 2026
0ed5f03
feat(reader): implement density-aware spring physics for consistent t…
Feb 27, 2026
55e273c
fix(paged): resolve initial book loading hang
Feb 27, 2026
f3ecacd
reader: feature reader improvements and additional documentation
eserero Feb 27, 2026
70d0c49
fix(ui): improve immersive detail view layout and interaction
eserero Feb 27, 2026
995703b
refactor(ui): simplify immersive scaffold and fix state restoration
eserero Feb 27, 2026
0530e2b
style(ui): align immersive views with Material 3 typography and eleva…
eserero Feb 27, 2026
d4b5b39
style(ui): align core navigation, FABs, and menus with Material 3 spe…
eserero Feb 28, 2026
36672b8
style(ui): update chips to Material 3 squarish design and fix navigat…
eserero Feb 28, 2026
38de3e0
feat(ui): refine Library screen toolbar layout
eserero Feb 28, 2026
eb5f007
style(ui): improve Home and Library toolbars
eserero Feb 28, 2026
ea7453a
fix(ui): correct panel reader centering and rotation transitions
eserero Mar 1, 2026
c41708f
feat(reader): add adaptive edge-sampled gradient backgrounds
eserero Mar 1, 2026
928e323
fix(reader): correct inverted logic for adaptive background sampling
eserero Mar 1, 2026
9bdc9d9
feat(reader): implement image-relative adaptive background blooming
eserero Mar 1, 2026
0f0dc05
feat(reader): implement four-side sampling and corner blending for ad…
eserero Mar 1, 2026
97a8ec6
fix(reader): prevent image loading getting stuck on page change
eserero Mar 1, 2026
f1b5d9d
fix(ui): eliminate double animation and cover flash on immersive book…
eserero Mar 1, 2026
4548c2f
feat(ui): enhance immersive screens and navigation with adaptive colo…
eserero Mar 1, 2026
4d960b6
style(ui): strictly enforce 1-line-per-segment in 'Below' card layout
eserero Mar 1, 2026
3e58997
feat(ui): refine immersive oneshot screen and dropdown styling
eserero Mar 1, 2026
01e7fb9
docs: update README with fork improvements and new features
eserero Mar 1, 2026
9762d81
fix(ui): use stable window height for immersive collapsedOffset
eserero Mar 1, 2026
9ce318e
feat(reader): modernize reader UI with floating slider and settings FAB
eserero Mar 3, 2026
f4f30f9
feat(ui): refine shared transitions, animated menus, and appearance s…
eserero Mar 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,21 @@ build/
/output
/.kotlin
/cmake/build*

# Tooling / AI assistants
.github/
.gemini/
.claude/
agent-os/
.beans/
.beans.yml
CLAUDE.md

# Design / working files
New UI/

# Pre-existing vendored third-party repos
third_party/secret-service/

# Prebuilt native libraries
**/androidMain/jniLibs/**/*.so
114 changes: 114 additions & 0 deletions ADAPTIVE_READER_BACKGROUND_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Adaptive Reader Background (Edge-Sampled Gradients) Implementation Plan

This plan outlines the implementation of an "Adaptive Background" feature for the comic reader in Komelia. This feature improves visual immersion by replacing solid letterbox/pillarbox bars with a two-color gradient sampled from the current page's edges.

## 1. Feature Overview
When a page does not perfectly fill the screen (due to "Fit to Screen" settings), the empty space (letterbox or pillarbox) will be filled with a gradient.
- **Top/Bottom gaps (Letterbox):** Vertical gradient from Top Edge Color to Bottom Edge Color.
- **Left/Right gaps (Pillarbox):** Horizontal gradient from Left Edge Color to Right Edge Color.
- **Panel Mode:** Uses the edge colors of the *full page* even when zoomed into a panel.
- **Configurability:** Independent toggles for Paged Mode and Panel Mode in Reader Settings.

## 2. Technical Strategy

### A. Color Sampling (Domain/Infra)
We need an efficient way to extract the average color of image edges using the `KomeliaImage` (libvips) abstraction.

1. **Utility Function:** Create `getEdgeColors(image: KomeliaImage): Pair<Color, Color>` (or similar) in `komelia-infra/image-decoder`.
2. **Implementation:**
- To get Top/Bottom colors:
- Extract a small horizontal strip from the top (e.g., full width, 10px height).
- Shrink the strip to 1x1.
- Repeat for the bottom.
- To get Left/Right colors:
- Extract a vertical strip from the left (e.g., 10px width, full height).
- Shrink to 1x1.
- Repeat for the right.
3. **Efficiency:** Libvips is optimized for these operations; it will avoid full decompression where possible and perform the resize/averaging in a streaming fashion.

### B. State Management
1. **Settings:**
- Add `pagedReaderAdaptiveBackground` and `panelReaderAdaptiveBackground` to `ImageReaderSettingsRepository`.
- Update `PagedReaderState` and `PanelsReaderState` to collect these settings.
2. **Page State:**
- Add `edgeColors: Pair<Color, Color>?` to the `Page` data class in `PagedReaderState`.
- When a page is loaded, trigger the background sampling task asynchronously.
- `PanelsReaderState` will track the edge colors of the *current page* it is showing panels for.

### C. UI Implementation (Compose)
1. **AdaptiveBackground Composable:**
- Location: `komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/common/`
- Parameters: `topColor: Color`, `bottomColor: Color`, `orientation: Orientation`.
- Use `animateColorAsState` for smooth transitions between pages.
- Use `Brush.linearGradient` to draw the background.
2. **Integration:**
- Wrap `ReaderImageContent` in `PagedReaderContent` and `PanelsReaderContent` with the new `AdaptiveBackground` component.
- Pass the colors based on whether the feature is enabled in the current mode's settings.

### D. Settings UI
1. **Reader Settings:**
- Add two new toggles in `SettingsContent.kt` (used by `BottomSheetSettingsOverlay` and `SettingsSideMenu`).
- Labels: "Adaptive Background (Paged)" and "Adaptive Background (Panels)".
- Position them near "Double tap to zoom" for consistency.

## 3. Implementation Steps

1. **Infra:** Implement the color sampling logic in `komelia-infra/image-decoder`.
2. **Domain:** Update `ImageReaderSettingsRepository` interface and its implementation (e.g., `AndroidImageReaderSettingsRepository`).
3. **State:**
- Update `PagedReaderState` to perform color sampling when images are loaded.
- Update `PanelsReaderState` to share this logic for its current page.
4. **UI:**
- Create the `AdaptiveBackground` composable.
- Update the settings screens to include the toggles.
- Connect the state to the UI to render the gradients.

## 4. Edge Cases & Considerations
- **Transparent Images:** Sampled colors should consider the background (likely white) if the image has transparency.
- **Very Thin Margins:** If the "Fit to Screen" fills the entire screen, the background won't be visible (current behavior preserved).
- **Performance:** Ensure sampling happens on a background thread and doesn't block the UI or delay page rendering.
- **Color Consistency:** Sampled colors can be slightly desaturated or darkened if they are too bright and distracting.

## Phase 2: Per-Pixel Edge Gradients (Blooming Effect)
This phase improves the background by preserving the color variation along the image edges and fading them into the theme's background.

### 1. Technical Strategy
- **Sampling:** Instead of a single 1x1 average, sample a 1D line of colors.
- Top/Bottom: Extract 10px strip, resize to `width x 1`.
- Left/Right: Extract 10px strip, resize to `1 x height`.
- **Rendering:**
- Draw the 1D sampled line stretched across the gap (creating color bars).
- Apply a gradient overlay from `Transparent` (image side) to `ThemeBackground` (screen edge side).

## Phase 2.5: Image-Relative Bloom Gradients
This fix ensures the background "bloom" starts exactly at the image edges rather than the center of the screen.

### 1. Technical Strategy
- **Image Bounds:** Retrieve the actual display dimensions and position of the image within the container.
- **Top Bloom:** Start at the `image.top` with `Color.Transparent` (showing full colors) and fade to `MaterialTheme.colorScheme.background` at the screen `top (0)`.
- **Bottom Bloom:** Start at the `image.bottom` with `Color.Transparent` and fade to `MaterialTheme.colorScheme.background` at the screen `bottom (height)`.
- **Logic:** The "colorful" part of the background should "leak" from the image outward to the screen edges.

### 2. Implementation Steps
1. **UI:** Update `AdaptiveBackground` to accept the `imageSize` or `imageBounds`.
2. **Rendering:** Recalculate the `drawRect` and `Brush` coordinates to align with these bounds.

## Phase 3: Four-Side Sampling & Corner Blending (Panel Mode Optimization)
This phase addresses scenarios where gaps exist on all four sides of the image, which is common in Panel Mode.

### 1. Technical Strategy
- **Sampling:** Always sample all four edges (Top, Bottom, Left, Right).
- **Rendering:**
- Create four independent gradient zones.
- **Corner Miter:** Use a 45-degree clipping or alpha-blending in the corners so that adjacent edge colors (e.g., Top and Left) meet seamlessly.
- **Panel Padding:** Ensure the background fills the additional "safety margin" or padding added around panels, providing a consistent immersive feel even when the panel is much smaller than the screen.

### 2. Implementation Steps
1. **Infra:** Update sampling to return all four edge lines.
2. **UI:** Update `AdaptiveBackground` to render four zones with corner blending logic.
3. **Panel Mode:** Verify integration with panel zooming and padding logic.

### 2. Implementation Steps
1. **Infra:** Add `getEdgeColorLines()` to `KomeliaImage` / `ReaderImageUtils`.
2. **UI:** Create a version of `AdaptiveBackground` that handles `ImageBitmap` buffers and applies the "bloom" fade.
3. **Switching:** Maintain both Phase 1 and Phase 2 logic for easy comparison/toggling during development.
67 changes: 67 additions & 0 deletions CARD_LAYOUT_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Proposal: Adaptive Library Card Layout

## Objective
Introduce a new layout option for library items (Books, Series, Collections, Read Lists) that places text metadata below the thumbnail in a structured Material 3 Filled Card, improving readability and providing a more traditional "bookshelf" aesthetic.

## 1. User Interface Changes

### Appearance Settings
- **New Toggle**: `Card Layout`
- **Options**:
- `Overlay` (Current default): Text appears on top of the thumbnail with a gradient overlay.
- `Below`: Text appears in a dedicated area below the thumbnail.
- **Description**: "Show title and metadata below the thumbnail instead of on top."
- **Preview**: The card size slider preview in the settings will adapt to show the selected layout.

### Card Design (`Below` Layout)
- **Dimensions**:
- **Width**: Strictly aligned to the `cardWidth` setting (same as `Overlay` layout).
- **Height**: Calculated dynamically based on the thumbnail's aspect ratio plus the fixed height of the text area.
- **Container**:
- **Type**: Material 3 Filled Card.
- **Colors**: `surfaceContainerHighest` for the container, following Material 3 guidelines for both Light and Dark themes.
- **Corners**: The card container will have rounded corners on all four sides (M3 standard, typically 12dp).
- **Thumbnail**:
- Fills the top, left, and right edges of the card.
- Maintains the existing 0.703 aspect ratio.
- **Corners**: Rounded corners **only on the top** to match the card's top profile; bottom of the thumbnail remains square where it meets the text area.
- **Text Area**:
- Located directly beneath the thumbnail.
- **Title**: Maximum of 2 lines, ellipsis on overflow.
- **Padding**: 8dp to 10dp padding around text elements to ensure M3 spacing standards.

## 2. Technical Implementation

### Persistence (`komelia-domain` & `komelia-infra`)
- Add `cardLayoutBelow` to `AppSettings` data class in `komelia-infra/database/shared/src/commonMain/kotlin/snd/komelia/db/AppSettings.kt`.
- **Database Migrations**:
- Add `V19__card_layout_below.sql` migration for SQLite (Android/Desktop) in `komelia-infra/database/sqlite/src/commonMain/composeResources/files/migrations/app/`.
- Update `AppSettingsTable.kt` in `komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/tables/` to include the new column.
- Update `ExposedSettingsRepository.kt` in `komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/settings/` to map the new column to the `AppSettings` object.
- Update `CommonSettingsRepository` and `SettingsRepositoryWrapper` to handle this new preference.

### UI State (`komelia-ui`)
- Define `LocalCardLayoutBelow` in `CompositionLocals.kt`.
- Update `MainView.kt` to collect the setting and provide it to the composition.
- Enhance `ItemCard` in `ItemCard.kt` to act as the layout engine:
- If `Overlay`: Use existing `Box` structure.
- If `Below`: Use `Column` with thumbnail followed by a content area.

### Component Updates
- **SeriesItemCard.kt**:
- Extract `SeriesImageOverlay` logic.
- Pass title and unread count to `ItemCard`'s content slot when in `Below` mode.
- **BookItemCard.kt**:
- Ensure the read progress bar and "unread" indicators are correctly positioned.
- Handle book titles and series titles (if enabled) in the text area.
- **Other Cards**: Apply similar changes to `CollectionItemCard.kt` and `ReadListItemCard.kt`.

## 3. Implementation Phases
1. **Phase 1: Domain & Infrastructure**: Update settings storage and repository layers.
2. **Phase 2: Settings UI**: Add the toggle to the Appearance settings screen and ViewModel.
3. **Phase 3: Base Component Refactoring**: Update `ItemCard` to support dual-layout switching.
4. **Phase 4: Content Implementation**: Update Series, Book, Collection, and Read List cards to fill the "Below" layout content slot.
5. **Phase 5: Visual Polish**: Finalize padding, M3 colors, and layout constraints (max 2 lines).

## 4. Default Behavior
- The default will remain as the `Overlay` layout to preserve the current user experience until explicitly changed by the user.
70 changes: 70 additions & 0 deletions DISABLE_DOUBLE_TAP_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Implementation Plan: Disable Double-Tap to Zoom

## Goal
Allow users to disable the double-tap gesture to zoom. This eliminates the system's wait-time for a second tap, making the single-tap (for page/panel turns) feel instantaneous.

---

## 1. Database Migration
**File**: `komelia-infra/database/sqlite/src/commonMain/composeResources/files/migrations/app/V17__reader_tap_settings.sql`
```sql
ALTER TABLE ImageReaderSettings ADD COLUMN tap_to_zoom BOOLEAN NOT NULL DEFAULT 1;
```

**File**: `komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/migrations/AppMigrations.kt`
- Add `"V17__reader_tap_settings.sql"` to the `migrations` list.

---

## 2. Persistence Layer Updates

**File**: `komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/tables/ImageReaderSettingsTable.kt`
```kotlin
val tapToZoom = bool("tap_to_zoom").default(true)
```

**File**: `komelia-infra/database/shared/src/commonMain/kotlin/snd/komelia/db/ImageReaderSettings.kt`
```kotlin
val tapToZoom: Boolean = true,
```

**File**: `komelia-domain/core/src/commonMain/kotlin/snd/komelia/settings/ImageReaderSettingsRepository.kt`
```kotlin
fun getTapToZoom(): Flow<Boolean>
suspend fun putTapToZoom(enabled: Boolean)
```

**File**: `komelia-infra/database/shared/src/commonMain/kotlin/snd/komelia/db/repository/ReaderSettingsRepositoryWrapper.kt`
- Implement `getTapToZoom` and `putTapToZoom`.

**File**: `komelia-infra/database/sqlite/src/commonMain/kotlin/snd/komelia/db/settings/ExposedImageReaderSettingsRepository.kt`
- Map `tapToZoom` in `get()` and `save()`.

---

## 3. State Management
**File**: `komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/ReaderState.kt` (since it applies to multiple modes)
- Load `tapToZoom` from repository.
- Provide a `onTapToZoomChange(Boolean)` handler.

---

## 4. UI Layer (Settings)
Add a "Tap to zoom" toggle to the reading mode settings for both **Paged** and **Panels** modes.

**Files**:
- `komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/settings/BottomSheetSettingsOverlay.kt` (Mobile)
- `komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/settings/SettingsSideMenu.kt` (Desktop)

---

## 5. Gesture Integration
**File**: `komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/common/ReaderContent.kt`
- Modify `ReaderControlsOverlay` to accept a `tapToZoom: Boolean` parameter.
- Update `detectTapGestures`:
```kotlin
detectTapGestures(
onTap = { ... },
onDoubleTap = if (tapToZoom) { offset -> ... } else null
)
```
74 changes: 74 additions & 0 deletions GESTURE_SYSTEM_FIX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Gesture System: Simultaneous Pan/Zoom & Velocity Fix

This document records the implementation of the verified solution for the comic reader's gesture handling system.

## Problems Addressed

1. **Pan-Zoom Lock**: The gesture detector prevented panning whenever a zoom change was detected, making the UI feel stiff and unresponsive during multi-touch gestures.
2. **Velocity Jumps (The "Leap" Bug)**: When lifting one finger during a two-finger gesture, the "centroid" (the center point between fingers) would suddenly jump from the center of two fingers to the position of the remaining finger. This one-frame jump was interpreted as extreme velocity, causing the image to fly violently off-screen.
3. **Continuous Mode Regression**: Previous attempts to fix these issues often broke the native kinetic scrolling of the `LazyColumn` in continuous mode by either consuming events prematurely or introducing asynchronous timing mismatches.

## The Solution: "Surgical Frame Filtering"

The implementation uses a non-invasive approach that filters input data before it reaches the movement logic, ensuring the output behavior remains compatible with native scrolling.

### 1. Pointer Count Stability Tracking
We added tracking for the number of active fingers (`lastIterationPointerCount`) inside the `detectTransformGestures` loop in `ScalableContainer.kt`.

- **Mechanism**: We only apply zoom and pan changes if the pointer count is **stable** (exactly the same as the previous frame).
- **Result**: This automatically ignores the single "jump frame" that occurs at the exact millisecond a finger is added or removed.

### 2. Synchronized Velocity Reset
We added a `resetVelocity()` method to `ScreenScaleState.kt` to clear the `VelocityTracker`'s history.

- **Mechanism**: This is called whenever the finger count changes.
- **Result**: It ensures that the velocity for the "new" gesture (e.g., transitioning from two-finger zoom to one-finger pan) is calculated from a clean slate, preventing the jump from being factored into the momentum.

### 3. Native Scroll Preservation
Crucially, the implementation continues to **not consume** the pointer events.

- **Result**: Because the events are not consumed, the `LazyColumn` in continuous mode can still see the vertical movement and handle it using its own internal, highly-optimized physics. This preserves the smooth, kinetic feel of the vertical scroll while allowing simultaneous pan/zoom.

## Implementation Details

### Files Modified:
- **`komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/ScreenScaleState.kt`**
- Added `resetVelocity()` helper.
- **`komelia-ui/src/commonMain/kotlin/snd/komelia/ui/reader/image/common/ScalableContainer.kt`**
- Implemented `lastIterationPointerCount` tracking.
- Removed the `if/else` block that prevented simultaneous pan/zoom.
- Integrated `resetVelocity()` calls on pointer count changes.

---

## Smooth Mode-Aware Double Tap to Zoom (Implemented)

### Problem: The "Fit Height" Jump
Previously, double-tapping to zoom out would always return the image to a zoom level of 1.0 (Fit Height). If a user was reading in "Fit Width" or a padded webtoon mode, this behavior was jarring as it forced them out of their preferred layout.

### The Solution: "Layout Base Zoom"
We implemented a system where the reader remembers the "base" zoom level intended by the current reading mode and uses it as the target for zooming out.

#### 1. Base Zoom Tracking
In `ScreenScaleState.kt`, we added a `baseZoom` property.
- **Mechanism**: The layout engines (Paged, Continuous, Panels) now flag their initial zoom calculations as "Base Zoom" using `setZoom(..., updateBase = true)`.
- **Result**: `ScreenScaleState` always knows what the "correct" zoom level is for the current mode (e.g., 1.2x for Fit Width).

#### 2. Animated Mode Toggle
We implemented a `toggleZoom(focus)` function that provides a smooth, kinetic transition.
- **Animation**: Uses a `SpringSpec` (`StiffnessLow`) for a natural, decelerating feel.
- **Logic**:
- If current zoom > base: Zoom out to `baseZoom`.
- If current zoom <= base: Zoom in to `max(base * 2.5, 2.5)`.
- **Focus Preservation**: The tapped point (`focus`) remains stationary under the finger as the image expands or contracts around it.

#### 3. Reader Mode Integration
The solution is integrated across all reader modes to ensure consistent behavior:
- **Paged Mode**: Returns to "Fit Width", "Fit Height", or "Original" as defined by the user settings.
- **Continuous Mode**: Returns to the padded column width (Webtoon style).
- **Panels Mode**: Returns to the specific fit level of the current panel.

### Files Modified:
- **`ScreenScaleState.kt`**: Implemented `baseZoom` tracking and the smooth `toggleZoom` animation.
- **`ReaderContent.kt`**: Integrated `onDoubleTap` into the `ReaderControlsOverlay` gesture detector.
- **`PagedReaderState.kt`, `ContinuousReaderState.kt`, `PanelsReaderState.kt`**: Updated layout logic to set the `baseZoom`.
Loading