diff --git a/.github/workflows/build_app.yml b/.github/workflows/build_app.yml
new file mode 100644
index 00000000..49c83e99
--- /dev/null
+++ b/.github/workflows/build_app.yml
@@ -0,0 +1,42 @@
+name: Build Lissen App
+
+env:
+ # The name of the main module repository
+ main_project_module: app
+
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "main" ]
+
+ workflow_dispatch:
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ # Set Current Date As Env Variable
+ - name: Set current date as env variable
+ run: echo "date_today=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
+
+ # Set Repository Name As Env Variable
+ - name: Set repository name as env variable
+ run: echo "repository_name=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV
+
+ - name: Set Up JDK
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'zulu'
+ java-version: '21'
+
+ - name: Change wrapper permissions
+ run: chmod +x ./gradlew
+
+ # Run Build Project
+ - name: Build gradle project
+ run: ./gradlew build -Proom.schemaLocation=$GITHUB_WORKSPACE/app/schemas
diff --git a/README.md b/README.md
index 49670260..2820be5d 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,11 @@
# Lissen - Clean Audiobookshelf Player
+[![Build Lissen App](https://github.com/GrakovNe/lissen-android/actions/workflows/build_app.yml/badge.svg)](https://github.com/GrakovNe/lissen-android/actions/workflows/build_app.yml)
-
-
-
-
-
+
+
-
-
### Features
* Beautiful Interface: Intuitive design that makes browsing and listening to your audiobooks easy and enjoyable.
@@ -16,7 +13,16 @@
* Streaming Support: Stream your audiobooks directly from the cloud without needing to download them first.
* Offline Listening: Download audiobooks to listen offline, ideal for those who want to access their collection without an internet connection.
-### Installation
+### Screenshots
+
+
+
+
+
+
+
+
+### Building
1. Clone the repository:
```
@@ -35,5 +41,19 @@ nano local.properties
```
5. Build and run the app on an Android device or emulator.
+### Demo Environment
+
+
+You can connect to a demo [Audiobookshelf](https://github.com/advplyr/audiobookshelf) instance through the Lissen app:
+
+```
+URL: https://demo.lissenapp.org
+
+Username: demo
+Password: demo
+```
+
+This instance contains only Public Domain audiobooks from [LibriVox](https://librivox.org/) and is intended solely for demonstrating the client’s functionality.
+
## License
Lissen is open-source and licensed under the MIT License. See the LICENSE file for more details.
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 961ad5bb..49052dfe 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -98,8 +98,6 @@ dependencies {
implementation(libs.androidx.media3.session)
kapt(libs.hilt.android.compiler)
- implementation(libs.icons.lucide)
-
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
@@ -121,7 +119,6 @@ dependencies {
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
- annotationProcessor(libs.androidx.room.compiler)
ksp(libs.androidx.room.compiler)
testImplementation(libs.junit)
diff --git a/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/AudiobookshelfChannel.kt b/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/AudiobookshelfChannel.kt
index fe04d014..1ba41b43 100644
--- a/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/AudiobookshelfChannel.kt
+++ b/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/AudiobookshelfChannel.kt
@@ -4,6 +4,7 @@ import android.net.Uri
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
+import org.grakovne.lissen.BuildConfig
import org.grakovne.lissen.channel.audiobookshelf.api.AudioBookshelfDataRepository
import org.grakovne.lissen.channel.audiobookshelf.api.AudioBookshelfMediaRepository
import org.grakovne.lissen.channel.audiobookshelf.api.AudioBookshelfSyncService
@@ -14,7 +15,9 @@ import org.grakovne.lissen.channel.audiobookshelf.converter.LibraryResponseConve
import org.grakovne.lissen.channel.audiobookshelf.converter.LibrarySearchItemsConverter
import org.grakovne.lissen.channel.audiobookshelf.converter.PlaybackSessionResponseConverter
import org.grakovne.lissen.channel.audiobookshelf.converter.RecentBookResponseConverter
+import org.grakovne.lissen.channel.audiobookshelf.model.DeviceInfo
import org.grakovne.lissen.channel.audiobookshelf.model.LibraryResponse
+import org.grakovne.lissen.channel.audiobookshelf.model.StartPlaybackRequest
import org.grakovne.lissen.channel.common.ApiResult
import org.grakovne.lissen.channel.common.ApiResult.Success
import org.grakovne.lissen.channel.common.ChannelCode
@@ -146,9 +149,9 @@ class AudiobookshelfChannel @Inject constructor(
supportedMimeTypes: List,
deviceId: String
): ApiResult {
- val request = org.grakovne.lissen.channel.audiobookshelf.model.StartPlaybackRequest(
+ val request = StartPlaybackRequest(
supportedMimeTypes = supportedMimeTypes,
- deviceInfo = org.grakovne.lissen.channel.audiobookshelf.model.DeviceInfo(
+ deviceInfo = DeviceInfo(
clientName = getClientName(),
deviceId = deviceId,
deviceName = getClientName()
@@ -196,7 +199,7 @@ class AudiobookshelfChannel @Inject constructor(
password: String
): ApiResult = dataRepository.authorize(host, username, password)
- private fun getClientName() = "Lissen App Android"
+ private fun getClientName() = "Lissen App ${BuildConfig.VERSION_NAME}"
private val supportedLibraryTypes = listOf("book")
}
diff --git a/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/converter/LibraryItemIdResponseConverter.kt b/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/converter/LibraryItemIdResponseConverter.kt
index 598133dc..8c7f49d2 100644
--- a/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/converter/LibraryItemIdResponseConverter.kt
+++ b/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/converter/LibraryItemIdResponseConverter.kt
@@ -32,19 +32,23 @@ class LibraryItemIdResponseConverter @Inject constructor() {
}
val filesAsChapters: () -> List = {
- item.media.audioFiles.fold(0.0 to mutableListOf()) { (accDuration, chapters), file ->
- chapters.add(
- BookChapter(
- start = accDuration,
- end = accDuration + file.duration,
- title = file.metaTags?.tagTitle
- ?: file.metadata.filename.removeSuffix(file.metadata.ext),
- duration = file.duration,
- id = file.ino
+ item
+ .media
+ .audioFiles
+ .sortedBy { it.index }
+ .fold(0.0 to mutableListOf()) { (accDuration, chapters), file ->
+ chapters.add(
+ BookChapter(
+ start = accDuration,
+ end = accDuration + file.duration,
+ title = file.metaTags?.tagTitle
+ ?: file.metadata.filename.removeSuffix(file.metadata.ext),
+ duration = file.duration,
+ id = file.ino
+ )
)
- )
- accDuration + file.duration to chapters
- }.second
+ accDuration + file.duration to chapters
+ }.second
}
return DetailedBook(
@@ -54,6 +58,7 @@ class LibraryItemIdResponseConverter @Inject constructor() {
files = item
.media
.audioFiles
+ .sortedBy { it.index }
.map {
BookFile(
id = it.ino,
diff --git a/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/converter/LibraryItemResponseConverter.kt b/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/converter/LibraryItemResponseConverter.kt
index b2a33ca3..119dea9b 100644
--- a/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/converter/LibraryItemResponseConverter.kt
+++ b/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/converter/LibraryItemResponseConverter.kt
@@ -12,10 +12,12 @@ class LibraryItemResponseConverter @Inject constructor() {
fun apply(response: LibraryItemsResponse): PagedItems = response
.results
- .map {
+ .mapNotNull {
+ val title = it.media.metadata.title ?: return@mapNotNull null
+
Book(
id = it.id,
- title = it.media.metadata.title,
+ title = title,
author = it.media.metadata.authorName,
cachedState = BookCachedState.ABLE_TO_CACHE,
duration = it.media.duration.toInt()
diff --git a/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/converter/LibrarySearchItemsConverter.kt b/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/converter/LibrarySearchItemsConverter.kt
index da6341c5..ac7946c3 100644
--- a/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/converter/LibrarySearchItemsConverter.kt
+++ b/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/converter/LibrarySearchItemsConverter.kt
@@ -10,10 +10,12 @@ import javax.inject.Singleton
class LibrarySearchItemsConverter @Inject constructor() {
fun apply(response: List): List {
return response
- .map {
+ .mapNotNull {
+ val title = it.media.metadata.title ?: return@mapNotNull null
+
Book(
id = it.id,
- title = it.media.metadata.title,
+ title = title,
author = it.media.metadata.authorName,
cachedState = BookCachedState.ABLE_TO_CACHE,
duration = it.media.duration.toInt()
diff --git a/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/model/LibraryItemsResponse.kt b/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/model/LibraryItemsResponse.kt
index cbcbd8e3..a4657230 100644
--- a/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/model/LibraryItemsResponse.kt
+++ b/app/src/main/java/org/grakovne/lissen/channel/audiobookshelf/model/LibraryItemsResponse.kt
@@ -2,57 +2,20 @@ package org.grakovne.lissen.channel.audiobookshelf.model
data class LibraryItemsResponse(
val results: List,
- val total: Int,
- val limit: Int,
- val page: Int,
- val sortBy: String,
- val sortDesc: Boolean,
- val filterBy: String,
- val mediaType: String,
- val minified: Boolean,
- val collapseseries: Boolean,
- val include: String
+ val page: Int
)
data class LibraryItem(
val id: String,
- val libraryId: String,
- val folderId: String,
- val path: String,
- val relPath: String,
- val isFile: Boolean,
- val mtimeMs: Long,
- val ctimeMs: Long,
- val birthtimeMs: Long,
- val addedAt: Long,
- val updatedAt: Long,
- val isMissing: Boolean,
- val isInvalid: Boolean,
- val mediaType: String,
val media: Media
)
data class Media(
- val numTracks: Int,
- val numAudioFiles: Int,
- val numChapters: Int,
val duration: Double,
- val metadata: Metadata,
- val size: Long
+ val metadata: Metadata
)
data class Metadata(
- val title: String,
- val titleIgnorePrefix: String,
- val subtitle: String?,
- val authorName: String?,
- val genres: List,
- val publishedYear: String?,
- val publishedDate: String?,
- val publisher: String?,
- val description: String?,
- val isbn: String?,
- val asin: String?,
- val language: String?,
- val explicit: Boolean
+ val title: String?,
+ val authorName: String?
)
diff --git a/app/src/main/java/org/grakovne/lissen/channel/common/UserAgent.kt b/app/src/main/java/org/grakovne/lissen/channel/common/UserAgent.kt
index 6cf6c0fc..798a01a6 100644
--- a/app/src/main/java/org/grakovne/lissen/channel/common/UserAgent.kt
+++ b/app/src/main/java/org/grakovne/lissen/channel/common/UserAgent.kt
@@ -3,4 +3,4 @@ package org.grakovne.lissen.channel.common
import android.os.Build
import org.grakovne.lissen.BuildConfig
-val USER_AGENT = "Lissen/${BuildConfig.VERSION_NAME} (Linux; Android ${Build.VERSION.RELEASE}; ${Build.MODEL}) ExoPlayer/1.4.1"
\ No newline at end of file
+val USER_AGENT = "Lissen/${BuildConfig.VERSION_NAME} (Linux; Android ${Build.VERSION.RELEASE}; ${Build.MODEL}) ExoPlayer/1.4.1"
diff --git a/app/src/main/java/org/grakovne/lissen/content/cache/dao/CachedBookDao.kt b/app/src/main/java/org/grakovne/lissen/content/cache/dao/CachedBookDao.kt
index 51e06df5..8da90f76 100644
--- a/app/src/main/java/org/grakovne/lissen/content/cache/dao/CachedBookDao.kt
+++ b/app/src/main/java/org/grakovne/lissen/content/cache/dao/CachedBookDao.kt
@@ -5,6 +5,7 @@ import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
+import androidx.room.RewriteQueriesToDropUnusedColumns
import androidx.room.Transaction
import androidx.room.Update
import org.grakovne.lissen.content.cache.entity.BookChapterEntity
@@ -82,6 +83,7 @@ interface CachedBookDao {
): List
@Transaction
+ @RewriteQueriesToDropUnusedColumns
@Query(
"""
SELECT * FROM detailed_books
diff --git a/app/src/main/java/org/grakovne/lissen/playback/MediaRepository.kt b/app/src/main/java/org/grakovne/lissen/playback/MediaRepository.kt
index 8c5ff7c6..32aa8b1c 100644
--- a/app/src/main/java/org/grakovne/lissen/playback/MediaRepository.kt
+++ b/app/src/main/java/org/grakovne/lissen/playback/MediaRepository.kt
@@ -7,7 +7,6 @@ import android.content.Intent
import android.content.IntentFilter
import android.os.Handler
import android.os.Looper
-import androidx.core.content.ContextCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.localbroadcastmanager.content.LocalBroadcastManager
@@ -130,7 +129,7 @@ class MediaRepository @Inject constructor(
val intent = Intent(context, PlaybackService::class.java).apply {
action = PlaybackService.ACTION_PLAY
}
- ContextCompat.startForegroundService(context, intent)
+ context.startForegroundService(intent)
}
fun pauseAudio() {
diff --git a/app/src/main/java/org/grakovne/lissen/ui/components/AsyncShimmeringImage.kt b/app/src/main/java/org/grakovne/lissen/ui/components/AsyncShimmeringImage.kt
index cf79cd4d..a7f976e3 100644
--- a/app/src/main/java/org/grakovne/lissen/ui/components/AsyncShimmeringImage.kt
+++ b/app/src/main/java/org/grakovne/lissen/ui/components/AsyncShimmeringImage.kt
@@ -16,7 +16,6 @@ import androidx.compose.ui.layout.ContentScale
import coil.ImageLoader
import coil.compose.AsyncImage
import coil.request.ImageRequest
-import com.valentinilk.shimmer.shimmer
@Composable
fun AsyncShimmeringImage(
@@ -37,7 +36,6 @@ fun AsyncShimmeringImage(
Box(
modifier = Modifier
.fillMaxSize()
- .shimmer()
.background(Color.Gray)
)
}
diff --git a/app/src/main/java/org/grakovne/lissen/ui/components/BookCoverFetcher.kt b/app/src/main/java/org/grakovne/lissen/ui/components/BookCoverFetcher.kt
index f5ef9f1a..ecba3bc1 100644
--- a/app/src/main/java/org/grakovne/lissen/ui/components/BookCoverFetcher.kt
+++ b/app/src/main/java/org/grakovne/lissen/ui/components/BookCoverFetcher.kt
@@ -4,11 +4,9 @@ import android.content.Context
import android.net.Uri
import coil.ImageLoader
import coil.decode.ImageSource
-import coil.disk.DiskCache
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.fetch.SourceResult
-import coil.memory.MemoryCache
import coil.request.Options
import dagger.Module
import dagger.Provides
@@ -72,17 +70,7 @@ object ImageLoaderModule {
): ImageLoader {
return ImageLoader
.Builder(context)
- .components {
- add(bookCoverFetcherFactory)
- }
- .memoryCache {
- MemoryCache.Builder(context).build()
- }
- .diskCache {
- DiskCache.Builder()
- .directory(context.cacheDir.resolve("сover_cache"))
- .build()
- }
+ .components { add(bookCoverFetcherFactory) }
.build()
}
}
diff --git a/app/src/main/java/org/grakovne/lissen/ui/extensions/TimeExtensions.kt b/app/src/main/java/org/grakovne/lissen/ui/extensions/TimeExtensions.kt
index 4abcaa59..4714ac35 100644
--- a/app/src/main/java/org/grakovne/lissen/ui/extensions/TimeExtensions.kt
+++ b/app/src/main/java/org/grakovne/lissen/ui/extensions/TimeExtensions.kt
@@ -1,31 +1,28 @@
package org.grakovne.lissen.ui.extensions
-import android.annotation.SuppressLint
+import java.util.Locale
-@SuppressLint("DefaultLocale")
fun Int.formatFully(): String {
val hours = this / 3600
val minutes = (this % 3600) / 60
- val remainingSeconds = this % 60
+ val seconds = this % 60
return if (hours > 0) {
- String.format("%02d:%02d:%02d", hours, minutes, remainingSeconds)
+ String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds)
} else {
- String.format("%02d:%02d", minutes, remainingSeconds)
+ String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds)
}
}
-@SuppressLint("DefaultLocale")
fun Int.formatShortly(): String {
val hours = this / 3600
val minutes = (this % 3600) / 60
- return String.format("%02dh %02dm", hours, minutes)
+ return String.format(Locale.getDefault(), "%02dh %02dm", hours, minutes)
}
-@SuppressLint("DefaultLocale")
fun Int.formatLeadingMinutes(): String {
val minutes = this / 60
- val remainingSeconds = this % 60
+ val seconds = this % 60
- return String.format("%02d:%02d", minutes, remainingSeconds)
+ return String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds)
}
diff --git a/app/src/main/java/org/grakovne/lissen/ui/icons/Search.kt b/app/src/main/java/org/grakovne/lissen/ui/icons/Search.kt
new file mode 100644
index 00000000..79c9b06a
--- /dev/null
+++ b/app/src/main/java/org/grakovne/lissen/ui/icons/Search.kt
@@ -0,0 +1,76 @@
+/**
+ * Copyright @alexstyl
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * File has been copied from https://composeicons.com/icons/lucide/search-check under ISC Licence
+ */
+package org.grakovne.lissen.ui.icons
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.PathFillType
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.StrokeJoin
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.path
+import androidx.compose.ui.unit.dp
+
+val Search: ImageVector
+ get() {
+ if (_Search != null) {
+ return _Search!!
+ }
+ _Search = ImageVector.Builder(
+ name = "Search",
+ defaultWidth = 24.dp,
+ defaultHeight = 24.dp,
+ viewportWidth = 24f,
+ viewportHeight = 24f
+ ).apply {
+ path(
+ fill = null,
+ fillAlpha = 1.0f,
+ stroke = SolidColor(Color(0xFF000000)),
+ strokeAlpha = 1.0f,
+ strokeLineWidth = 2f,
+ strokeLineCap = StrokeCap.Round,
+ strokeLineJoin = StrokeJoin.Round,
+ strokeLineMiter = 1.0f,
+ pathFillType = PathFillType.NonZero
+ ) {
+ moveTo(19f, 11f)
+ arcTo(8f, 8f, 0f, isMoreThanHalf = false, isPositiveArc = true, 11f, 19f)
+ arcTo(8f, 8f, 0f, isMoreThanHalf = false, isPositiveArc = true, 3f, 11f)
+ arcTo(8f, 8f, 0f, isMoreThanHalf = false, isPositiveArc = true, 19f, 11f)
+ close()
+ }
+ path(
+ fill = null,
+ fillAlpha = 1.0f,
+ stroke = SolidColor(Color(0xFF000000)),
+ strokeAlpha = 1.0f,
+ strokeLineWidth = 2f,
+ strokeLineCap = StrokeCap.Round,
+ strokeLineJoin = StrokeJoin.Round,
+ strokeLineMiter = 1.0f,
+ pathFillType = PathFillType.NonZero
+ ) {
+ moveTo(21f, 21f)
+ lineToRelative(-4.3f, -4.3f)
+ }
+ }.build()
+ return _Search!!
+ }
+
+private var _Search: ImageVector? = null
diff --git a/app/src/main/java/org/grakovne/lissen/ui/screens/library/LibraryScreen.kt b/app/src/main/java/org/grakovne/lissen/ui/screens/library/LibraryScreen.kt
index 131ed7c2..9f778872 100644
--- a/app/src/main/java/org/grakovne/lissen/ui/screens/library/LibraryScreen.kt
+++ b/app/src/main/java/org/grakovne/lissen/ui/screens/library/LibraryScreen.kt
@@ -312,7 +312,7 @@ fun LibraryScreen(
}
}
- item { Spacer(modifier = Modifier.height(8.dp)) }
+ item(key = "library_spacer") { Spacer(modifier = Modifier.height(8.dp)) }
when {
isPlaceholderRequired -> item { LibraryPlaceholderComposable() }
@@ -326,7 +326,7 @@ fun LibraryScreen(
}
}
- else -> items(count = library.itemCount) {
+ else -> items(count = library.itemCount, key = { "library_item_$it" }) {
val book = library[it] ?: return@items
val isVisible = remember(hiddenBooks, book.id) {
derivedStateOf { libraryViewModel.isVisible(book.id) }
diff --git a/app/src/main/java/org/grakovne/lissen/ui/screens/library/composables/DefaultActionComposable.kt b/app/src/main/java/org/grakovne/lissen/ui/screens/library/composables/DefaultActionComposable.kt
index b6ff39ea..e1355da0 100644
--- a/app/src/main/java/org/grakovne/lissen/ui/screens/library/composables/DefaultActionComposable.kt
+++ b/app/src/main/java/org/grakovne/lissen/ui/screens/library/composables/DefaultActionComposable.kt
@@ -27,12 +27,11 @@ import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import com.composables.icons.lucide.Lucide
-import com.composables.icons.lucide.Search
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.grakovne.lissen.R
+import org.grakovne.lissen.ui.icons.Search
import org.grakovne.lissen.ui.navigation.AppNavigationService
import org.grakovne.lissen.viewmodel.CachingModelView
import org.grakovne.lissen.viewmodel.LibraryViewModel
@@ -54,7 +53,7 @@ fun DefaultActionComposable(
modifier = Modifier.offset(x = 4.dp)
) {
Icon(
- imageVector = Lucide.Search,
+ imageVector = Search,
contentDescription = null
)
}
diff --git a/app/src/main/java/org/grakovne/lissen/ui/screens/player/composable/TrackControlComposable.kt b/app/src/main/java/org/grakovne/lissen/ui/screens/player/composable/TrackControlComposable.kt
index 380d80ff..fe5e3f42 100644
--- a/app/src/main/java/org/grakovne/lissen/ui/screens/player/composable/TrackControlComposable.kt
+++ b/app/src/main/java/org/grakovne/lissen/ui/screens/player/composable/TrackControlComposable.kt
@@ -123,19 +123,14 @@ fun TrackControlComposable(
) {
IconButton(
onClick = {
- if (currentTrackIndex > 0) {
- viewModel.previousTrack()
- }
+ viewModel.previousTrack()
},
- enabled = currentTrackIndex > 0
+ enabled = true
) {
Icon(
imageVector = Icons.Rounded.SkipPrevious,
contentDescription = "Previous Track",
- tint = when (currentTrackIndex > 0) {
- true -> colorScheme.onBackground
- else -> colorScheme.onBackground.copy(alpha = 0.3f)
- },
+ tint = colorScheme.onBackground,
modifier = Modifier.size(36.dp)
)
}
diff --git a/app/src/main/java/org/grakovne/lissen/ui/screens/settings/composable/GeneralSettingsItemComposable.kt b/app/src/main/java/org/grakovne/lissen/ui/screens/settings/composable/GeneralSettingsItemComposable.kt
index 90a2ce32..fd0499bc 100644
--- a/app/src/main/java/org/grakovne/lissen/ui/screens/settings/composable/GeneralSettingsItemComposable.kt
+++ b/app/src/main/java/org/grakovne/lissen/ui/screens/settings/composable/GeneralSettingsItemComposable.kt
@@ -10,8 +10,8 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Check
-import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
@@ -65,7 +65,7 @@ fun GeneralSettingsItemComposable(
onDismissRequest()
}
)
- Divider()
+ HorizontalDivider()
}
}
}
diff --git a/app/src/main/java/org/grakovne/lissen/viewmodel/PlayerViewModel.kt b/app/src/main/java/org/grakovne/lissen/viewmodel/PlayerViewModel.kt
index e2939f7e..80224301 100644
--- a/app/src/main/java/org/grakovne/lissen/viewmodel/PlayerViewModel.kt
+++ b/app/src/main/java/org/grakovne/lissen/viewmodel/PlayerViewModel.kt
@@ -135,8 +135,15 @@ class PlayerViewModel @Inject constructor(
}
fun previousTrack() {
- val previousChapterIndex = currentChapterIndex.value?.let { it - 1 } ?: return
- setChapter(previousChapterIndex)
+ val position = currentChapterPosition.value ?: return
+ val index = currentChapterIndex.value ?: return
+
+ val currentIndexReplay = (position > CURRENT_TRACK_REPLAY_THRESHOLD || index == 0)
+
+ when {
+ currentIndexReplay -> setChapter(index)
+ index > 0 -> setChapter(index - 1)
+ }
}
fun togglePlayPause() {
@@ -182,4 +189,9 @@ class PlayerViewModel @Inject constructor(
return 0.0
}
+
+ companion object {
+
+ private const val CURRENT_TRACK_REPLAY_THRESHOLD = 5
+ }
}
diff --git a/gradle.properties b/gradle.properties
index 132244e5..f0a2e55f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,23 +1,4 @@
-# Project-wide Gradle settings.
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. For more details, visit
-# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
-# org.gradle.parallel=true
-# AndroidX package structure to make it clearer which packages are bundled with the
-# Android operating system, and which are packaged with your app's APK
-# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
-# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
-# Enables namespacing of each library's R class so that its R class includes only the
-# resources declared in the library itself and none from the library's dependencies,
-# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ed8db1e2..0c3b3a5a 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -7,7 +7,6 @@ composeShimmerAndroid = "1.3.1"
converterGson = "2.9.0"
hiltAndroid = "2.52"
hiltNavigationCompose = "1.2.0"
-iconsLucide = "1.0.0"
kotlin = "1.9.24"
coreKtx = "1.15.0"
junit = "4.13.2"
@@ -58,7 +57,6 @@ compose-shimmer-android = { module = "com.valentinilk.shimmer:compose-shimmer-an
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltAndroid" }
-icons-lucide = { module = "com.composables:icons-lucide", version.ref = "iconsLucide" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }