Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
42 changes: 42 additions & 0 deletions .agent/rules/project-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
trigger: always_on
---

# Project Context and Guidelines

## About Project

This is an offline-first Android client app for the [audiobookshelf](https://github.com/advplyr/audiobookshelf) server.

## Feature Requirements (Offline first behavior)

- As the app is an offline-first app, assume that the server is not always reachable.
- Playback progress should be saved in the local database first **immediately**.
- The app should watch the network changes, such as connecting to a new wifi network, or Ethernet LAN, or disconnecting from a network and connecting to a new network, disconnecting from wifi and connecting to celular network, etc., and try to ping or reach out to the audiobook server to check whether the server is reachable.
- If the server is reachable, the app should sync the local progress to the server and pull the latest progress updates from the server to the local database; it should merge the updates from both.
- If the server is reachable, and some chapters of any audiobook are downloaded, i.e., available offline, then the offline track should be given priority for playback.
- If the offline track is deleted/cleared while the book is being played, the player should attempt to fallback to the online URL if the server is reachable, otherwise pause playback and persist the last playback state (track ID, playback position/timestamp, current chapter/index, and playback status) plus a flag indicating offline content was removed; ensure the rule notes that these persisted fields are used to resume or report playback state and are written atomically to the player state store.
- The app must be fully functional for downloaded content when offline.

## Ensure Stability

- Ensure null-safety when converting data (e.g., check for division by zero in percentage calculations).
- Changes must be verified by building the app and ensuring logic holds (e.g., uninstall/reinstall for clean state tests).

## Overall Functionality

- When the app loads, it should load the offline available book content immediately, then in the background reach out to the server (if the server is reachable) and fetch the full list of the books, continue listening section books and update the UI seemlessly.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix spelling error: "seamlessly".

The word "seemlessly" is misspelled; it should be "seamlessly".

🔎 Proposed fix
-- When the app loads, it should load the offline available book content immediately, then in the background reach out to the server (if the server is reachable) and fetch the full list of the books, continue listening section books and update the UI seemlessly.
+- When the app loads, it should load the offline available book content immediately, then in the background reach out to the server (if the server is reachable) and fetch the full list of the books, continue listening section books and update the UI seamlessly.
🤖 Prompt for AI Agents
In .agent/rules/project-context.md around line 28, the word "seemlessly" is
misspelled; replace it with "seamlessly" so the sentence reads "...update the UI
seamlessly."

- The app should cache all the book's metadata in local database to optimise the app load time, Only the chapters / audio tracks should not be cached automatically / by default. Chapters should be downloaded and cached on demand by the user, using the download chapters/book functionality.
- When the app loads, if the server is not reachable, it shouldn't show long loading screen, trying to fetch the books from the server, it should load the book's list from the local database, however it should only show the books whos' chapters are downloaded and available offline can be played.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix grammar error: "whose" (no apostrophe).

The possessive form should be "whose", not "whos'".

🔎 Proposed fix
-- When the app loads, if the server is not reachable, it shouldn't show long loading screen, trying to fetch the books from the server, it should load the book's list from the local database, however it should only show the books whos' chapters are downloaded and available offline can be played.
+- When the app loads, if the server is not reachable, it shouldn't show long loading screen, trying to fetch the books from the server, it should load the book's list from the local database, however it should only show the books whose chapters are downloaded and available offline can be played.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- When the app loads, if the server is not reachable, it shouldn't show long loading screen, trying to fetch the books from the server, it should load the book's list from the local database, however it should only show the books whos' chapters are downloaded and available offline can be played.
- When the app loads, if the server is not reachable, it shouldn't show long loading screen, trying to fetch the books from the server, it should load the book's list from the local database, however it should only show the books whose chapters are downloaded and available offline can be played.
🤖 Prompt for AI Agents
In .agent/rules/project-context.md around line 30, fix the grammar by replacing
the incorrect possessive "whos'" with "whose" and adjust surrounding wording for
clarity; update the sentence to read something like: "When the app loads, if the
server is not reachable, it shouldn't show a long loading screen trying to fetch
books from the server; it should load the book list from the local database, and
only show books whose chapters are downloaded and available offline for
playback."

- When the server becomes reachable, it should update the books list, as now all the books can be played from local cache or online from the server.
- When the network is switched, the app should trigger checking whether the server is still reachable or not, if not reachable, it should update the UI to only show offline available ready to play books.

## General Guidelines and Standards

- **Colors**: Must be referenced from `Color.kt` / `Theme.kt`. Do not use raw hex values (e.g., `0xFF...`) in Composables.
- **Dimensions**: Must use `Spacing.kt` (e.g., `Spacing.md`, `Spacing.lg`) for padding, margins, and layout dimensions.
- **Design System**: Adhere effectively to the spacing system and color palette defined in the project.
- **ABSOLUTELY NO** hardcoded user-facing strings in UI code. All strings must be extracted to `strings.xml` and accessed via `stringResource`.
- Use `associateBy` or proper indexing for collection lookups (O(1)) instead of nested loops (O(N^2)) when synchronizing data.
- Avoid expensive operations on the main thread.
- No code duplication, keep the code clean and easy to maintain.
53 changes: 53 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Build and Release Kahani

on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:

jobs:
release:
name: Build Signed APK
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '21'
cache: 'gradle'

- name: Decode Keystore
run: |
echo "${{ secrets.RELEASE_KEYSTORE_BASE64 }}" | base64 --decode > app/kahani-release.jks

- name: Build Release APK
run: ./gradlew assembleRelease
env:
RELEASE_STORE_FILE: kahani-release.jks
RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_PASSWORD }}
RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}

- name: Create Release
uses: softprops/action-gh-release@v1
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update to latest action version.

The softprops/action-gh-release@v1 action is outdated. The v1 runner is deprecated on GitHub Actions.

⬆️ Proposed fix
       - name: Create Release
-        uses: softprops/action-gh-release@v1
+        uses: softprops/action-gh-release@v2
         with:
           files: app/build/outputs/apk/release/app-release.apk

For better security and stability, consider pinning to a specific commit SHA:

-        uses: softprops/action-gh-release@v1
+        uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.4

Based on static analysis hints.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
uses: softprops/action-gh-release@v1
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: app/build/outputs/apk/release/app-release.apk
🧰 Tools
🪛 actionlint (1.7.10)

42-42: the runner of "softprops/action-gh-release@v1" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🤖 Prompt for AI Agents
In @.github/workflows/release.yml at line 42, Replace the deprecated
softprops/action-gh-release@v1 reference with a pinned, supported ref: either
update to the current maintained major (e.g., softprops/action-gh-release@v2 if
available) or pin to a specific commit SHA by replacing uses:
softprops/action-gh-release@v1 with uses:
softprops/action-gh-release@<commit-sha> (fetch the latest stable commit from
the action repo), ensuring the workflow uses a fixed, non-deprecated ref for
security and stability.

with:
files: app/build/outputs/apk/release/app-release.apk
tag_name: v${{ github.run_number }}
name: Release v${{ github.run_number }}
body: |
Automated Release for Kahani.
Commit: ${{ github.sha }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ google-services.json

# Schemas
app/schemas
.DS_Store
21 changes: 21 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "gradle",
"id": "${workspaceFolder}app:installDebugapp",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix the malformed task ID.

The task ID appears to have a concatenation issue: "${workspaceFolder}app:installDebugapp" contains installDebugapp without proper separation. This is inconsistent with the script name app:installDebug on line 7.

🔎 Proposed fix
-      "id": "${workspaceFolder}app:installDebugapp",
+      "id": "${workspaceFolder}/app:installDebug:app",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"id": "${workspaceFolder}app:installDebugapp",
"id": "${workspaceFolder}/app:installDebug:app",
🤖 Prompt for AI Agents
In .vscode/tasks.json around line 6, the task ID is malformed as
"${workspaceFolder}app:installDebugapp" (it mistakenly concatenates "app"
twice); update the id to match the script name on line 7 by changing it to
"${workspaceFolder}app:installDebug" so the task ID exactly matches the actual
script/task name and removes the extra "app" suffix.

"script": "app:installDebug",
"description": "Installs the Debug build.",
"group": "install",
"project": "app",
"buildFile": "${workspaceFolder}/app/build.gradle.kts",
"rootProject": "Lissen",
"projectFolder": "${workspaceFolder}",
"workspaceFolder": "${workspaceFolder}",
"args": "",
"javaDebug": false,
"problemMatcher": ["$gradle"],
"label": "gradle: app:installDebug"
}
]
}
38 changes: 21 additions & 17 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ android {
defaultConfig {
val commitHash = gitCommitHash()

applicationId = "org.grakovne.lissen"
applicationId = "org.surjit.kahani"
minSdk = 28
targetSdk = 36
versionCode = 10800
versionName = "1.8.0-$commitHash"
versionCode = 10000
versionName = "1.0.0-${commitHash}"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: versionCode regression will break app updates.

The versionCode has been decreased from 10800 to 10000. Android requires versionCode to monotonically increase for app updates to work. Users with version 10800 installed will not be able to update to 10000.

Additionally, the applicationId change from org.grakovne.lissen to org.surjit.kahani means this will be treated as a completely new app by app stores, not an update to the existing app. Existing users will need to uninstall and reinstall.

🔧 Proposed fix
-    versionCode = 10000
+    versionCode = 10900

If this is intentional rebranding as a new app, please confirm that:

  1. This is not meant to be an update to an existing app in production
  2. Users understand they need to reinstall
  3. Data migration strategy is in place
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
applicationId = "org.surjit.kahani"
minSdk = 28
targetSdk = 36
versionCode = 10800
versionName = "1.8.0-$commitHash"
versionCode = 10000
versionName = "1.0.0-${commitHash}"
applicationId = "org.surjit.kahani"
minSdk = 28
targetSdk = 36
versionCode = 10900
versionName = "1.0.0-${commitHash}"
🤖 Prompt for AI Agents
In @app/build.gradle.kts around lines 53 - 57, The PR lowers versionCode and
changes applicationId, which will break updates; restore versionCode to a value
>= the previous production value (e.g., set versionCode to at least 10800 or
bump to a new higher integer) in the build script where versionCode is set, and
only change applicationId (org.surjit.kahani) if you intend this to be a new
app—if so, document that this is a deliberate rebranding and verify users must
reinstall and a data migration plan exists; otherwise revert applicationId to
the original (org.grakovne.lissen) to preserve update continuity.


buildConfigField("String", "GIT_HASH", "\"$commitHash\"")

Expand All @@ -66,27 +66,31 @@ android {
buildConfigField("String", "ACRA_REPORT_LOGIN", "\"$acraReportLogin\"")
buildConfigField("String", "ACRA_REPORT_PASSWORD", "\"$acraReportPassword\"")

if (project.hasProperty("RELEASE_STORE_FILE")) {
signingConfigs {
create("release") {
storeFile = file(project.property("RELEASE_STORE_FILE")!!)
storePassword = project.property("RELEASE_STORE_PASSWORD") as String?
keyAlias = project.property("RELEASE_KEY_ALIAS") as String?
keyPassword = project.property("RELEASE_KEY_PASSWORD") as String?
enableV1Signing = true
enableV2Signing = true
signingConfigs {
create("release") {
val envKeyStore = System.getenv("RELEASE_STORE_FILE")
val propKeyStore = localProperties.getProperty("RELEASE_STORE_FILE")

storeFile = when {
envKeyStore != null -> file(envKeyStore)
propKeyStore != null -> file(propKeyStore)
else -> null
}

storePassword = System.getenv("RELEASE_STORE_PASSWORD") ?: localProperties.getProperty("RELEASE_STORE_PASSWORD")
keyAlias = System.getenv("RELEASE_KEY_ALIAS") ?: localProperties.getProperty("RELEASE_KEY_ALIAS")
keyPassword = System.getenv("RELEASE_KEY_PASSWORD") ?: localProperties.getProperty("RELEASE_KEY_PASSWORD")

enableV1Signing = true
enableV2Signing = true
}
}
}


buildTypes {
release {
if (project.hasProperty("RELEASE_STORE_FILE")) {
signingConfig = signingConfigs.getByName("release")
}
isMinifyEnabled = false
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = true
isShrinkResources = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
Expand Down
4 changes: 2 additions & 2 deletions app/src/debug/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<resources>
<string name="app_name">Lissen (DEBUG)</string>
</resources>
<string name="app_name">Kahani (DEBUG)</string>
</resources>
6 changes: 3 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:largeHeap="true"
android:theme="@style/Theme.Lissen"
android:theme="@style/Theme.Kahani"
tools:ignore="DiscouragedApi,UnusedAttribute"
android:manageSpaceActivity="org.grakovne.lissen.content.LissenDataManagementActivity"
tools:targetApi="36">
Expand All @@ -41,7 +41,7 @@
android:name=".ui.activity.AppActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/Theme.Lissen"
android:theme="@style/Theme.Kahani"
tools:ignore="LockedOrientationActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand All @@ -68,7 +68,7 @@
<category android:name="android.intent.category.BROWSABLE" />

<data
android:scheme="lissen"
android:scheme="kahani"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for any remaining references to "lissen" OAuth scheme
rg -in 'lissen' --type xml --type kt --type java --type md --type gradle

Repository: SurjitSahoo/lissen-up

Length of output: 91


🏁 Script executed:

#!/bin/bash
# Check the context in AndroidManifest.xml around the kahani scheme
cat -n app/src/main/AndroidManifest.xml | grep -A 5 -B 5 'kahani'

Repository: SurjitSahoo/lissen-up

Length of output: 474


🏁 Script executed:

#!/bin/bash
# Look for OAuth-related configurations or documentation
find . -type f \( -name "*.md" -o -name "*.txt" -o -name "README*" \) | head -20 | xargs grep -l -i 'oauth\|redirect' 2>/dev/null | head -10

Repository: SurjitSahoo/lissen-up

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Search for any remaining references to "lissen" with correct file types
rg -in 'lissen' --type xml --type kotlin --type java --type md

Repository: SurjitSahoo/lissen-up

Length of output: 50378


🏁 Script executed:

#!/bin/bash
# Look for OAuth-related code and configuration
rg -in 'oauth|redirect.*uri' --type xml --type kotlin --type java --type md -A 2 -B 2

Repository: SurjitSahoo/lissen-up

Length of output: 23846


🏁 Script executed:

#!/bin/bash
# Check build.gradle files and other configuration for OAuth setup
find . -type f -name "*.gradle" -o -name "build.gradle.kts" | xargs grep -i 'oauth\|audiobookshelf' 2>/dev/null

Repository: SurjitSahoo/lissen-up

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Search for any migration-related code or version handling
rg -in 'migration|migrate|scheme.*change' --type kotlin --type java --type xml

Repository: SurjitSahoo/lissen-up

Length of output: 6395


🏁 Script executed:

#!/bin/bash
# Verify the AuthScheme constant value
cat -n app/src/main/kotlin/org/grakovne/lissen/channel/audiobookshelf/common/oauth/OAuthScheme.kt

Repository: SurjitSahoo/lissen-up

Length of output: 261


🏁 Script executed:

#!/bin/bash
# Check how AuthScheme is used in the OAuth flow
rg -A 2 -B 2 'AuthScheme.*://' app/src/main/kotlin/org/grakovne/lissen/channel/audiobookshelf/common/api/AudiobookshelfAuthService.kt

Repository: SurjitSahoo/lissen-up

Length of output: 384


Update AuthScheme constant to match the AndroidManifest.xml change.

The OAuth scheme was updated in AndroidManifest.xml to kahani://oauth, but the AuthScheme constant in OAuthScheme.kt remains set to "lissen". This causes the OAuth redirect_uri sent to the Audiobookshelf server to be lissen://oauth instead of kahani://oauth, breaking authentication if the server is configured to accept only the new scheme.

Update line 3 in app/src/main/kotlin/org/grakovne/lissen/channel/audiobookshelf/common/oauth/OAuthScheme.kt:

const val AuthScheme = "kahani"

Additionally, confirm with users that re-authentication is required after this change.

🤖 Prompt for AI Agents
In @app/src/main/AndroidManifest.xml at line 71, Update the AuthScheme constant
in OAuthScheme.kt from the old value to match the AndroidManifest change: change
the const val AuthScheme declaration to use "kahani" instead of "lissen" so
redirect_uri uses kahani://oauth; ensure any places that read AuthScheme (e.g.,
code that builds redirect_uri or registers intent filters) will pick up the new
value and note to inform users that they must re-authenticate after this scheme
change.

android:host="oauth" />
</intent-filter>
</activity>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import javax.inject.Singleton
class LibraryPageResponseConverter
@Inject
constructor() {
fun apply(response: LibraryItemsResponse): PagedItems<Book> =
fun apply(
response: LibraryItemsResponse,
libraryId: String,
): PagedItems<Book> =
response
.results
.mapNotNull {
Expand All @@ -22,6 +25,8 @@ class LibraryPageResponseConverter
series = it.media.metadata.seriesName,
subtitle = it.media.metadata.subtitle,
author = it.media.metadata.authorName,
duration = 0.0,
libraryId = libraryId,
)
}.let {
PagedItems(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class LibraryAudiobookshelfChannel
pageNumber = pageNumber,
sort = option,
direction = direction,
).map { libraryPageResponseConverter.apply(it) }
).map { libraryPageResponseConverter.apply(it, libraryId) }
}

override suspend fun searchBooks(
Expand All @@ -87,7 +87,7 @@ class LibraryAudiobookshelfChannel
searchResult
.map { it.book }
.map { it.map { response -> response.libraryItem } }
.map { librarySearchItemsConverter.apply(it) }
.map { librarySearchItemsConverter.apply(it, libraryId) }
}

val byAuthor =
Expand All @@ -106,7 +106,7 @@ class LibraryAudiobookshelfChannel
onFailure = { emptyList() },
)
}
}.map { librarySearchItemsConverter.apply(it) }
}.map { librarySearchItemsConverter.apply(it, libraryId) }
}

val bySeries: Deferred<OperationResult<List<Book>>> =
Expand All @@ -125,7 +125,7 @@ class LibraryAudiobookshelfChannel
)
}
}.map { result -> result.map { it.libraryItem } }
.map { result -> result.let { librarySearchItemsConverter.apply(it) } }
.map { result -> result.let { librarySearchItemsConverter.apply(it, libraryId) } }
}

mergeBooks(byTitle, byAuthor, bySeries)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import org.grakovne.lissen.lib.domain.BookSeries
import org.grakovne.lissen.lib.domain.DetailedItem
import org.grakovne.lissen.lib.domain.MediaProgress
import org.grakovne.lissen.lib.domain.PlayingChapter
import java.time.LocalDate
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import javax.inject.Inject
import javax.inject.Singleton

Expand Down Expand Up @@ -88,7 +91,7 @@ class BookResponseConverter
chapters = maybeChapters ?: filesAsChapters(),
libraryId = item.libraryId,
localProvided = false,
year = item.media.metadata.publishedYear,
year = extractYear(item.media.metadata.publishedYear),
abstract = item.media.metadata.description,
publisher = item.media.metadata.publisher,
series =
Expand All @@ -115,4 +118,28 @@ class BookResponseConverter
},
)
}

private fun extractYear(rawYear: String?): String? {
if (rawYear.isNullOrBlank()) {
return null
}

// 1. If it's explicitly 4 digits, assume it's a year
if (rawYear.matches(Regex("^\\d{4}$"))) {
return rawYear
}

return try {
// 2. Try parsing as ZonedDateTime (ISO 8601 with timezone, e.g. 2010-10-07T07:13:01Z)
ZonedDateTime.parse(rawYear).year.toString()
} catch (e: Exception) {
try {
// 3. Try parsing as LocalDate (yyyy-MM-dd)
LocalDate.parse(rawYear).year.toString()
} catch (e: Exception) {
// 4. Fallback: If it starts with 4 digits, take them
Regex("^(\\d{4})").find(rawYear)?.groupValues?.get(1) ?: rawYear
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@ import javax.inject.Singleton
class LibrarySearchItemsConverter
@Inject
constructor() {
fun apply(response: List<LibraryItem>) =
response
.mapNotNull {
val title = it.media.metadata.title ?: return@mapNotNull null
fun apply(
response: List<LibraryItem>,
libraryId: String,
) = response
.mapNotNull {
val title = it.media.metadata.title ?: return@mapNotNull null

Book(
id = it.id,
title = title,
series = it.media.metadata.seriesName,
subtitle = it.media.metadata.subtitle,
author = it.media.metadata.authorName,
)
}
Book(
id = it.id,
title = title,
series = it.media.metadata.seriesName,
subtitle = it.media.metadata.subtitle,
author = it.media.metadata.authorName,
duration = 0.0,
libraryId = libraryId,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class PodcastAudiobookshelfChannel
pageNumber = pageNumber,
sort = option,
direction = direction,
).map { podcastPageResponseConverter.apply(it) }
).map { podcastPageResponseConverter.apply(it, libraryId) }
}

override suspend fun searchBooks(
Expand All @@ -83,7 +83,7 @@ class PodcastAudiobookshelfChannel
.searchPodcasts(libraryId, query, limit)
.map { it.podcast }
.map { it.map { response -> response.libraryItem } }
.map { podcastSearchItemsConverter.apply(it) }
.map { podcastSearchItemsConverter.apply(it, libraryId) }
}

byTitle.await()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import javax.inject.Singleton
class PodcastPageResponseConverter
@Inject
constructor() {
fun apply(response: PodcastItemsResponse): PagedItems<Book> =
fun apply(
response: PodcastItemsResponse,
libraryId: String,
): PagedItems<Book> =
response
.results
.mapNotNull {
Expand All @@ -22,6 +25,9 @@ class PodcastPageResponseConverter
subtitle = null,
series = null,
author = it.media.metadata.author,
// Duration is unavailable from the API
duration = 0.0,
libraryId = libraryId,
)
}.let {
PagedItems(
Expand Down
Loading
Loading