Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 13 additions & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
java-version: '17.0.13'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
Expand All @@ -56,3 +56,15 @@ jobs:

- name: Run Tests
run: ./gradlew :app:testDebugUnitTestAll

- name: Upload JVM crash logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: jvm-crash-logs
path: |
**/hs_err_pid*.log
**/replay_pid*.log
**/core.*
if-no-files-found: ignore
retention-days: 14
90 changes: 90 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,102 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.13.0] - 2026-05-18

### Added

- Highlights: select and save text highlights while reading articles, with support for adding notes to each highlight
- Global highlights list in the navigation drawer: browse, search, filter, and sort all highlights across your bookmarks
- Swipe actions on bookmark cards for quick access to favorite, archive, and delete

### Changed

- Reader now uses native WebView scrolling for smoother, more reliable article navigation
- Picture bookmark reading view layout updated to match Readeck's presentation

### Fixed

- Foreground service crash on Android 14+ devices
- Reader top bar overlay behavior corrected
- Sync cancellation and resilience improvements

## [0.12.6] - 2026-05-05

### Fixed

- Resolved a critical OutOfMemoryError that caused crashes during offline content synchronization

### Changed

- Cleaned up obsolete code and reduced app footprint

## [0.12.0] - 2026-04-11

### Added

- In-page anchor link support in reader content: table-of-contents and fragment links now navigate correctly within articles
- Long-press context menu for in-page links to open or copy anchor targets
- Content download status icon on reading-view bookmark cards
- Reading progress icon in Compact list view

### Changed

- Sync architecture updated to multipart sync for improved reliability and consistency when refreshing bookmark metadata and content
- Sync Settings revised with automatic content sync for offline reading using volume, item-count, or date-range policies

### Fixed

- Reader text reflow regression: resolved cases where article text could disappear after layout/font reflow updates
- Server URL validation now allows http:// endpoints (in addition to https://) for self-hosted/local Readeck setups
- Offline status indicator now tracks network transitions more accurately and avoids incorrect offline icon states

## [0.11.1] - 2026-03-19

### Added

- Special thanks to Stefan (@Alanon202) in About screen and README for app functionality feedback and testing support

### Changed

- Background sync indicator removed for cleaner UI
- Filter chip behavior: dismissing synthetic chips restores preset defaults, literal chip removal remains unchanged

### Fixed

- Server error flag propagation: bookmarks with server errors now correctly appear in "With errors" filter after refresh/create operations
- Text autosizing in reader: fixed 8-12% text size increase when switching from Medium to Wide reader width
- Filter UI: synthetic filter chips now appear when preset constraints are broadened (e.g., "Is archived: N/A", "Is favorite: N/A", "Type: Any")
- Sync performance: reduced blocking spinner on app open by showing cached bookmarks immediately during background sync
- Delete operation: fixed race condition with rapid successive delete actions
- Layout stability: eliminated theme switching reflow and fixed layout shift in sync indicator
- Video controls: improved fullscreen discoverability and auto-rotation behavior
- Missing translations: added localized "Copy to clipboard" text for all languages

## [0.11.0] - 2026-03-14

### Added

- Image gallery lightbox in reading view: tap any article image to open a full-screen gallery with swipe navigation, pinch-to-zoom, double-tap to zoom, and a thumbnail strip
- Long-press context menus for images and links in reader view and bookmark list (copy, download, share, open in browser)
- Highlights and annotations: view, create, and edit Readeck highlights directly in the reading view
- "Keep screen on while reading" toggle in Settings → User Interface
- Reader appearance settings: curated themes, font size, line spacing, content width, and fullscreen mode
- Fullscreen reading mode: hides top bar while reading; swipe up to reveal controls
- Typography and Find in Page now available for Video and Picture bookmark types
- About screen shows app and server info in collapsible cards (version, build, server name, URL)
- 15-minute option added to auto-sync schedule

### Changed

- Favorite and Archive actions moved from top bar to overflow menu and inline buttons at end of article content
- Long-press context menus replaced with centered dialog popups showing a preview header
- "View original" renamed to "View web page" throughout
- Menu items and filter labels now use sentence case
- Bookmark deletion: card stays visible but greyed-out until snackbar is dismissed or undo is pressed
- Delta sync re-enabled for Readeck 0.22+; deleted bookmarks now detected immediately on pull-to-refresh
- Navigation drawer and settings screen typography refined for better visual hierarchy
- User guide gains a "Contents" button for one-tap navigation back to the table of contents

## [0.10.0] - 2026-02-26

### Added
Expand Down
76 changes: 0 additions & 76 deletions _notes/reader-top-bar-scroll-behavior-spec.md

This file was deleted.

1 change: 1 addition & 0 deletions app/src/main/java/com/mydeck/app/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ abstract class AppModule {
abstract fun bindHighlightsRepository(highlightsRepositoryImpl: com.mydeck.app.domain.HighlightsRepositoryImpl): com.mydeck.app.domain.HighlightsRepository

@Binds
@Singleton
abstract fun bindBookmarkRepository(bookmarkRepositoryImpl: BookmarkRepositoryImpl): BookmarkRepository

@Binds
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/java/com/mydeck/app/domain/BookmarkRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.mydeck.app.domain.model.BookmarkListItem
import com.mydeck.app.domain.model.BookmarkMetadataUpdate
import com.mydeck.app.domain.model.ProgressFilter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow

interface BookmarkRepository {
fun observeBookmarks(
Expand Down Expand Up @@ -40,6 +41,8 @@ interface BookmarkRepository {
suspend fun updateBookmark(bookmarkId: String, isFavorite: Boolean?, isArchived: Boolean?, isRead: Boolean?): UpdateResult
suspend fun updateReadProgress(bookmarkId: String, progress: Int): UpdateResult
suspend fun updateLabels(bookmarkId: String, labels: List<String>): UpdateResult
val syncProgress: StateFlow<BookmarkSyncProgress>

suspend fun performFullSync(): SyncResult
suspend fun performDeltaSync(since: kotlinx.datetime.Instant?): SyncResult
suspend fun syncPendingActions(): UpdateResult
Expand Down Expand Up @@ -83,6 +86,11 @@ interface BookmarkRepository {
suspend fun refreshBookmarkMetadata(bookmarkId: String)
suspend fun fetchExtractionLog(bookmarkId: String): ExtractionLogResult

sealed interface BookmarkSyncProgress {
data object Idle : BookmarkSyncProgress
data class Running(val page: Int, val totalPages: Int) : BookmarkSyncProgress
}

sealed class ExtractionLogResult {
data class Success(val text: String) : ExtractionLogResult()
data class HttpError(val code: Int) : ExtractionLogResult()
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/java/com/mydeck/app/domain/BookmarkRepositoryImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.NonCancellable
Expand Down Expand Up @@ -66,6 +69,10 @@ class BookmarkRepositoryImpl @Inject constructor(
@IoDispatcher
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) : BookmarkRepository {

private val _syncProgress = MutableStateFlow<BookmarkRepository.BookmarkSyncProgress>(BookmarkRepository.BookmarkSyncProgress.Idle)
override val syncProgress: StateFlow<BookmarkRepository.BookmarkSyncProgress> = _syncProgress.asStateFlow()

override fun observeBookmarks(
type: Bookmark.Type?,
unread: Boolean?,
Expand Down Expand Up @@ -755,6 +762,8 @@ class BookmarkRepositoryImpl @Inject constructor(
totalInserted += bookmarks.size
}

_syncProgress.value = BookmarkRepository.BookmarkSyncProgress.Running(currentPage, totalPages)

if (currentPage < totalPages) {
offset += pageSize
} else {
Expand Down Expand Up @@ -816,6 +825,7 @@ class BookmarkRepositoryImpl @Inject constructor(
Timber.e(e, "Full sync failed")
BookmarkRepository.SyncResult.NetworkError(errorMessage = "Network error during full sync", ex = e)
} finally {
_syncProgress.value = BookmarkRepository.BookmarkSyncProgress.Idle
// NonCancellable ensures cleanup runs even when the coroutine is cancelled.
withContext(NonCancellable) {
try {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/mydeck/app/io/rest/NetworkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ object NetworkModule {
Timber.tag("OkHttp").d(message)
}
val loggingInterceptor = HttpLoggingInterceptor(timberLogger).apply {
level = HttpLoggingInterceptor.Level.BODY
level = HttpLoggingInterceptor.Level.BASIC
redactHeader("Authorization")
redactHeader("Cookie")
redactHeader("Set-Cookie")
Expand Down
15 changes: 4 additions & 11 deletions app/src/main/java/com/mydeck/app/ui/highlights/HighlightsScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ fun HighlightsContent(
if (uiState.cachePartial && uiState.groups.isNotEmpty()) {
HighlightsPartialCacheBanner()
}
if (uiState.isRefreshing && !uiState.isUserRefreshing && !uiState.isInitialLocalLoad) {
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
}
}
}
) { paddingValues ->
Expand Down Expand Up @@ -246,16 +249,6 @@ fun HighlightsContent(
MaterialTheme.colorScheme.onSurfaceVariant
}
)
if (uiState.isRefreshing) {
Spacer(Modifier.height(12.dp))
CircularProgressIndicator(modifier = Modifier.size(24.dp))
Spacer(Modifier.height(8.dp))
Text(
text = stringResource(R.string.highlights_refreshing),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
if (uiState.refreshFailed) {
Spacer(Modifier.height(8.dp))
Button(onClick = onRetry) {
Expand All @@ -274,7 +267,7 @@ fun HighlightsContent(
} else {
val pullToRefreshState = rememberPullToRefreshState()
PullToRefreshBox(
isRefreshing = uiState.isRefreshing,
isRefreshing = uiState.isUserRefreshing,
onRefresh = onRetry,
state = pullToRefreshState,
modifier = Modifier.fillMaxSize()
Expand Down
Loading
Loading