diff --git a/app/src/main/java/com/allsoftdroid/audiobook/presentation/MainActivity.kt b/app/src/main/java/com/allsoftdroid/audiobook/presentation/MainActivity.kt
index 1bf326c9..aa1ce0cc 100644
--- a/app/src/main/java/com/allsoftdroid/audiobook/presentation/MainActivity.kt
+++ b/app/src/main/java/com/allsoftdroid/audiobook/presentation/MainActivity.kt
@@ -259,7 +259,6 @@ class MainActivity : BaseActivity() {
layoutParams = layout
}
}
-
}
private fun performAction(event: AudioPlayerEvent){
@@ -321,7 +320,6 @@ class MainActivity : BaseActivity() {
stopAudioService()
disposables.dispose()
downloader.Destroy()
- AppModule.unloadModule()
}
private fun stopAudioService(){
diff --git a/common/src/main/res/navigation/nav_graph.xml b/common/src/main/res/navigation/nav_graph.xml
index f8221fd6..dcd89cb9 100644
--- a/common/src/main/res/navigation/nav_graph.xml
+++ b/common/src/main/res/navigation/nav_graph.xml
@@ -38,8 +38,11 @@
+
+ @Query("SELECT * FROM metadata_table where metadata_id=:bookId")
+ fun getMetadataNonLive( bookId : String):DatabaseMetadataEntity
+
/**
* Get album details for the specified audio book
* @param metadata_id unique id given to audio book
@@ -50,6 +53,9 @@ interface MetadataDao{
@Query("SELECT * FROM MediaTrack_Table where track_album_id=:metadata_id and format like '%' || :formatContains || '%'")
fun getTrackDetails(metadata_id:String,formatContains:String):LiveData>
+ @Query("SELECT * FROM MediaTrack_Table where track_album_id=:metadata_id and format like '%' || :formatContains || '%'")
+ fun getTrackDetailsNonLive(metadata_id:String,formatContains:String):List
+
/**
* get list of media VBR track files for the given album id . here album id is same as metadata id so we will
* use complex sql queries to get VBR files
diff --git a/feature_book/src/main/java/com/allsoftdroid/feature_book/presentation/AudioBookListFragment.kt b/feature_book/src/main/java/com/allsoftdroid/feature_book/presentation/AudioBookListFragment.kt
index 05a2c654..a0615abf 100644
--- a/feature_book/src/main/java/com/allsoftdroid/feature_book/presentation/AudioBookListFragment.kt
+++ b/feature_book/src/main/java/com/allsoftdroid/feature_book/presentation/AudioBookListFragment.kt
@@ -274,9 +274,4 @@ class AudioBookListFragment : BaseUIFragment(){
}
}
}
-
- override fun onDestroy() {
- super.onDestroy()
- FeatureBookModule.unLoadModules()
- }
}
\ No newline at end of file
diff --git a/feature_book_details/src/main/java/com/allsoftdroid/feature/book_details/di/BookDetailsModule.kt b/feature_book_details/src/main/java/com/allsoftdroid/feature/book_details/di/BookDetailsModule.kt
index 91a85dc0..77caee32 100644
--- a/feature_book_details/src/main/java/com/allsoftdroid/feature/book_details/di/BookDetailsModule.kt
+++ b/feature_book_details/src/main/java/com/allsoftdroid/feature/book_details/di/BookDetailsModule.kt
@@ -115,7 +115,7 @@ object BookDetailsModule {
factory {
MetadataRepositoryImpl(
- metadataDao = get(),
+ metadataDao = get(named(name = METADATA_DAO)),
bookId = getProperty(PROPERTY_BOOK_ID),
metadataDataSource = get(),
saveInDatabase = get(named(name = METADATA_DATABASE))) as IMetadataRepository
@@ -123,7 +123,7 @@ object BookDetailsModule {
factory {
TrackListRepositoryImpl(
- metadataDao = get(),
+ metadataDao = get(named(name = METADATA_DAO)),
bookId = getProperty(PROPERTY_BOOK_ID)
) as ITrackListRepository
}
@@ -158,7 +158,7 @@ object BookDetailsModule {
AudioBookDatabase.getDatabase(get()).listenLaterDao()
}
- single {
+ single(named(name = METADATA_DAO)) {
AudioBookDatabase.getDatabase(get()).metadataDao()
}
@@ -167,7 +167,7 @@ object BookDetailsModule {
}
single(named(name = METADATA_DATABASE)) {
- SaveMetadataInDatabase.setup(metadataDao = get()) as SaveInDatabase
+ SaveMetadataInDatabase.setup(metadataDao = get(named(name = METADATA_DAO))) as SaveInDatabase
}
single {
@@ -185,4 +185,5 @@ object BookDetailsModule {
const val PROPERTY_BOOK_ID = "bookDetails_book_id"
private const val METADATA_DATABASE = "SaveMetadataInDatabase"
+ private const val METADATA_DAO ="MetadataDao_BookDetailsModule"
}
\ No newline at end of file
diff --git a/feature_book_details/src/test/java/com/allsoftdroid/feature/book_details/utils/FakeAudioDataSource.kt b/feature_book_details/src/test/java/com/allsoftdroid/feature/book_details/utils/FakeAudioDataSource.kt
index 0efd915f..fd6138a6 100644
--- a/feature_book_details/src/test/java/com/allsoftdroid/feature/book_details/utils/FakeAudioDataSource.kt
+++ b/feature_book_details/src/test/java/com/allsoftdroid/feature/book_details/utils/FakeAudioDataSource.kt
@@ -2,6 +2,7 @@ package com.allsoftdroid.feature.book_details.utils
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
+import com.allsoftdroid.common.test.getOrAwaitValue
import com.allsoftdroid.database.metadataCacheDB.MetadataDao
import com.allsoftdroid.database.metadataCacheDB.entity.DatabaseAlbumEntity
import com.allsoftdroid.database.metadataCacheDB.entity.DatabaseMetadataEntity
@@ -17,6 +18,10 @@ class FakeMetadataSource(private val _metadataLiveData: MutableLiveData {
return _albumEntity
@@ -34,6 +39,13 @@ class FakeMetadataSource(private val _metadataLiveData: MutableLiveData {
+ return _tracks.getOrAwaitValue()
+ }
+
override fun getTrackDetailsVBR(metadata_id: String): LiveData> {
return _tracks
}
diff --git a/feature_downloader/src/main/res/drawable/ic_close_circle.xml b/feature_downloader/src/main/res/drawable/ic_close_circle.xml
index 8c3303c8..6a920e0a 100644
--- a/feature_downloader/src/main/res/drawable/ic_close_circle.xml
+++ b/feature_downloader/src/main/res/drawable/ic_close_circle.xml
@@ -4,5 +4,5 @@
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
-
+
\ No newline at end of file
diff --git a/feature_downloader/src/main/res/drawable/ic_delete.xml b/feature_downloader/src/main/res/drawable/ic_delete.xml
index ef65fba8..c8b20470 100644
--- a/feature_downloader/src/main/res/drawable/ic_delete.xml
+++ b/feature_downloader/src/main/res/drawable/ic_delete.xml
@@ -4,5 +4,5 @@
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
-
+
\ No newline at end of file
diff --git a/feature_downloader/src/main/res/drawable/ic_file_music.xml b/feature_downloader/src/main/res/drawable/ic_file_music.xml
index 986cc4d5..a08cca0c 100644
--- a/feature_downloader/src/main/res/drawable/ic_file_music.xml
+++ b/feature_downloader/src/main/res/drawable/ic_file_music.xml
@@ -4,5 +4,5 @@
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
-
+
\ No newline at end of file
diff --git a/feature_downloader/src/main/res/layout/activity_download_management.xml b/feature_downloader/src/main/res/layout/activity_download_management.xml
index c5eb111e..50c390ac 100644
--- a/feature_downloader/src/main/res/layout/activity_download_management.xml
+++ b/feature_downloader/src/main/res/layout/activity_download_management.xml
@@ -2,7 +2,6 @@
diff --git a/feature_downloader/src/main/res/layout/layout_empty_content.xml b/feature_downloader/src/main/res/layout/layout_empty_content.xml
index 24a8ad8f..dcad6dbe 100644
--- a/feature_downloader/src/main/res/layout/layout_empty_content.xml
+++ b/feature_downloader/src/main/res/layout/layout_empty_content.xml
@@ -20,7 +20,7 @@
android:text="@string/emptyView_download_message"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:layout_width="wrap_content"
- android:textColor="@color/colorItem"
+ android:textColor="@color/colorPrimary"
android:layout_marginBottom="@dimen/margin_standard"
android:layout_height="wrap_content" />
\ No newline at end of file
diff --git a/feature_downloader/src/main/res/layout/recycler_download_item.xml b/feature_downloader/src/main/res/layout/recycler_download_item.xml
index e5964b91..eb37bdb1 100644
--- a/feature_downloader/src/main/res/layout/recycler_download_item.xml
+++ b/feature_downloader/src/main/res/layout/recycler_download_item.xml
@@ -15,7 +15,8 @@
android:id="@+id/textView_download_file_name"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:layout_width="0dp"
- android:textColor="@color/colorItem"
+ android:textColor="@color/colorPrimary"
+ android:textStyle="bold"
android:layout_weight="1"
android:layout_height="wrap_content" />
@@ -48,6 +49,7 @@
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="0dp"
android:paddingEnd="@dimen/padding_standard"
+ android:paddingStart="@dimen/standard_padding_min"
android:layout_weight="3"
android:layout_gravity="center"
android:layout_height="wrap_content" />
diff --git a/feature_listen_later_ui/src/main/java/com/allsoftdroid/audiobook/feature_listen_later_ui/utils/BindingUtils.kt b/feature_listen_later_ui/src/main/java/com/allsoftdroid/audiobook/feature_listen_later_ui/utils/BindingUtils.kt
index 24bfba9d..8edfbdd6 100644
--- a/feature_listen_later_ui/src/main/java/com/allsoftdroid/audiobook/feature_listen_later_ui/utils/BindingUtils.kt
+++ b/feature_listen_later_ui/src/main/java/com/allsoftdroid/audiobook/feature_listen_later_ui/utils/BindingUtils.kt
@@ -7,6 +7,7 @@ import com.allsoftdroid.audiobook.feature_listen_later_ui.R
import com.allsoftdroid.audiobook.feature_listen_later_ui.data.model.ListenLaterItemDomainModel
import com.allsoftdroid.common.base.extension.CreateImageOverlay
import com.allsoftdroid.common.base.network.ArchiveUtils
+import com.allsoftdroid.common.base.utils.BindingUtils.getNormalizedText
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
@@ -63,12 +64,4 @@ fun TextView.setBookDuration(item: ListenLaterItemDomainModel?){
item?.let {
text = it.duration
}
-}
-
-private fun getNormalizedText(text:String?,limit:Int):String{
- if(text?.length?:0>limit){
- return text?.substring(0,limit-3)+"..."
- }
-
- return text?:""
}
\ No newline at end of file
diff --git a/feature_listen_later_ui/src/main/res/layout/fragment_listen_later_layout.xml b/feature_listen_later_ui/src/main/res/layout/fragment_listen_later_layout.xml
index 52f02260..39e2b656 100644
--- a/feature_listen_later_ui/src/main/res/layout/fragment_listen_later_layout.xml
+++ b/feature_listen_later_ui/src/main/res/layout/fragment_listen_later_layout.xml
@@ -17,7 +17,7 @@
android:layout_height="?actionBarSize"
app:layout_constraintEnd_toEndOf="parent"
android:background="@color/black"
- android:padding="2dp"
+ android:padding="@dimen/padding_min"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:id="@+id/toolbar"
@@ -25,11 +25,11 @@
@@ -38,8 +38,9 @@
android:id="@+id/toolbar_title"
android:textColor="@color/white"
android:layout_width="0dp"
+ android:layout_marginStart="@dimen/margin_normal"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
- android:gravity="center"
+ android:gravity="start|center_vertical"
android:text="@string/toolbar_title_text"
android:layout_height="match_parent"
android:layout_weight="1"/>
@@ -47,7 +48,7 @@
diff --git a/feature_listen_later_ui/src/main/res/values/dimens.xml b/feature_listen_later_ui/src/main/res/values/dimens.xml
index 45d56c8b..ec0aea26 100644
--- a/feature_listen_later_ui/src/main/res/values/dimens.xml
+++ b/feature_listen_later_ui/src/main/res/values/dimens.xml
@@ -3,4 +3,8 @@
24dp
16dp
90dp
+
+ 2dp
+ 24dp
+ 16dp
\ No newline at end of file
diff --git a/feature_mini_player/src/main/java/com/allsoftdroid/audiobook/feature_mini_player/presentation/MiniPlayerFragment.kt b/feature_mini_player/src/main/java/com/allsoftdroid/audiobook/feature_mini_player/presentation/MiniPlayerFragment.kt
index 7b6dca27..e2ee040c 100644
--- a/feature_mini_player/src/main/java/com/allsoftdroid/audiobook/feature_mini_player/presentation/MiniPlayerFragment.kt
+++ b/feature_mini_player/src/main/java/com/allsoftdroid/audiobook/feature_mini_player/presentation/MiniPlayerFragment.kt
@@ -47,6 +47,5 @@ class MiniPlayerFragment : BaseContainerFragment() {
override fun onDestroy() {
super.onDestroy()
- FeatureMiniPlayerModule.unloadModule()
}
}
\ No newline at end of file
diff --git a/feature_mybooks/build.gradle b/feature_mybooks/build.gradle
index 11e0c6c6..460a1bed 100644
--- a/feature_mybooks/build.gradle
+++ b/feature_mybooks/build.gradle
@@ -43,8 +43,10 @@ android {
dependencies {
implementation(project(path: ModuleDependency.LIBRARY_COMMON))
+ implementation(project(path: ModuleDependency.DATABASE))
implementation(LibraryDependency.GLIDE)
kapt(LibraryDependency.GLIDE_COMPILER)
implementation(LibraryDependency.KOIN_X_VIEWMODEL)
+ implementation(LibraryDependency.LOTTIE)
}
diff --git a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/MyBooksFragment.kt b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/MyBooksFragment.kt
deleted file mode 100644
index ff57539c..00000000
--- a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/MyBooksFragment.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.allsoftdroid.audiobook.feature_mybooks
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.activity.OnBackPressedCallback
-import com.allsoftdroid.audiobook.feature_mybooks.databinding.FragmentMybooksLayoutBinding
-import com.allsoftdroid.common.base.fragment.BaseUIFragment
-
-class MyBooksFragment : BaseUIFragment() {
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- val dataBinding:FragmentMybooksLayoutBinding = inflateLayout(inflater,
- R.layout.fragment_mybooks_layout,container,false)
-
- return dataBinding.root
- }
-
- override fun handleBackPressEvent(callback: OnBackPressedCallback) {
- callback.isEnabled = false
- requireActivity().onBackPressed()
- }
-}
\ No newline at end of file
diff --git a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/data/model/BookMetadata.kt b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/data/model/BookMetadata.kt
new file mode 100644
index 00000000..89b7d779
--- /dev/null
+++ b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/data/model/BookMetadata.kt
@@ -0,0 +1,7 @@
+package com.allsoftdroid.audiobook.feature_mybooks.data.model
+
+data class BookMetadata(
+ val title:String,
+ val author:String,
+ val totalTracks:Int
+)
\ No newline at end of file
diff --git a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/data/model/LocalBookDomainModel.kt b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/data/model/LocalBookDomainModel.kt
new file mode 100644
index 00000000..fe482516
--- /dev/null
+++ b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/data/model/LocalBookDomainModel.kt
@@ -0,0 +1,10 @@
+package com.allsoftdroid.audiobook.feature_mybooks.data.model
+
+data class LocalBookDomainModel (
+ val bookTitle:String,
+ val bookIdentifier:String,
+ val bookAuthor:String,
+ val bookChaptersDownloaded:Int,
+ val totalChapters:Int,
+ val fileNames:List
+)
\ No newline at end of file
diff --git a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/data/model/LocalBookFiles.kt b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/data/model/LocalBookFiles.kt
new file mode 100644
index 00000000..4a7b4cd3
--- /dev/null
+++ b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/data/model/LocalBookFiles.kt
@@ -0,0 +1,6 @@
+package com.allsoftdroid.audiobook.feature_mybooks.data.model
+
+data class LocalBookFiles(
+ val identifier:String,
+ val filePath:List
+)
\ No newline at end of file
diff --git a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/data/repository/BookMetadataRepositoryImpl.kt b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/data/repository/BookMetadataRepositoryImpl.kt
new file mode 100644
index 00000000..44b58c3c
--- /dev/null
+++ b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/data/repository/BookMetadataRepositoryImpl.kt
@@ -0,0 +1,27 @@
+package com.allsoftdroid.audiobook.feature_mybooks.data.repository
+
+import com.allsoftdroid.audiobook.feature_mybooks.data.model.BookMetadata
+import com.allsoftdroid.audiobook.feature_mybooks.domain.IBookMetadataRepository
+import com.allsoftdroid.database.metadataCacheDB.MetadataDao
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+class BookMetadataRepositoryImpl(
+ private val metadataDao: MetadataDao,
+ private val dispatcher: CoroutineDispatcher = Dispatchers.IO
+):IBookMetadataRepository {
+ override suspend fun getBookMetadata(identifier: String): BookMetadata {
+
+ return withContext(dispatcher){
+ val metadata = metadataDao.getMetadataNonLive(identifier)
+ val tracks = metadataDao.getTrackDetailsNonLive(metadata_id = identifier,formatContains = "64")
+
+ if(metadata==null || tracks.isEmpty()) {
+ return@withContext BookMetadata(title ="",author = "",totalTracks =0)
+ }else{
+ return@withContext BookMetadata(title =metadata.title,author = metadata.creator,totalTracks = tracks.size)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/data/repository/LocalBooksRepositoryImpl.kt b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/data/repository/LocalBooksRepositoryImpl.kt
new file mode 100644
index 00000000..cd31d443
--- /dev/null
+++ b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/data/repository/LocalBooksRepositoryImpl.kt
@@ -0,0 +1,101 @@
+package com.allsoftdroid.audiobook.feature_mybooks.data.repository
+
+import android.app.Application
+import android.os.Environment
+import com.allsoftdroid.audiobook.feature_mybooks.data.model.LocalBookFiles
+import com.allsoftdroid.audiobook.feature_mybooks.domain.ILocalBooksRepository
+import com.allsoftdroid.common.base.network.ArchiveUtils
+import com.allsoftdroid.common.base.utils.LocalFilesForBook
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import timber.log.Timber
+import java.io.File
+
+class LocalBooksRepositoryImpl(
+ private val dispatcher: CoroutineDispatcher=Dispatchers.IO,
+ private val application: Application,
+ private val localFilesForBook:LocalFilesForBook
+) : ILocalBooksRepository {
+
+ override suspend fun getLocalBookFiles(): List =
+ withContext(dispatcher){
+ val localBookFiles = mutableListOf()
+
+ val rootFolder = ArchiveUtils.getDownloadsRootFolder(application)
+ Timber.d("Root Folder is $rootFolder")
+
+ val directory = Environment.getExternalStoragePublicDirectory("$rootFolder/AudioBooks/")
+ val bookIds = directory.listFiles()?.map {
+ it.absolutePath.split("/").last()
+ }
+
+ Timber.d("BookIds are $bookIds")
+
+ bookIds?.map {bookId->
+ val files = localFilesForBook.getDownloadedFilesList(bookId)
+ if(files.isNullOrEmpty()){
+ localBookFiles.add(LocalBookFiles(identifier = bookId,filePath = emptyList()))
+ }else{
+ Timber.i("$bookId : Found ${files.size} files")
+ localBookFiles.add(LocalBookFiles(identifier = bookId,filePath = files))
+ }
+ }
+
+ return@withContext localBookFiles
+ }
+
+ override suspend fun removeBook(identifier: String) {
+ withContext(dispatcher){
+ val rootFolder = ArchiveUtils.getDownloadsRootFolder(application)
+ Timber.d("Root Folder is $rootFolder")
+
+ val directory = Environment.getExternalStoragePublicDirectory("$rootFolder/AudioBooks/")
+ val books = directory.listFiles()?.filter {
+ it.absolutePath.split("/").last() == identifier
+ }
+
+ Timber.d("Books to be removed are : $books")
+
+ books?.let {
+ it.forEach { folder ->
+ try {
+ deleteRecursive(folder)
+ }catch (e:Exception){
+ Timber.e("Error: can't remove $folder")
+ }
+ }
+ }
+ }
+ }
+
+ override suspend fun deleteAllChapters(identifier: String) {
+ withContext(dispatcher){
+ val removeFiles = localFilesForBook.getDownloadedFilesList(identifier)
+
+ removeFiles?.let {filePaths ->
+ Timber.d("Files to be removed are: $filePaths")
+
+ filePaths.forEach {
+ try {
+ val file = File(it)
+ deleteRecursive(file)
+ }catch (e:Exception){
+ Timber.e("Error: can't remove $it")
+ }
+ }
+ }
+ }
+ }
+
+ private fun deleteRecursive(fileOrDirectory: File){
+
+ if(fileOrDirectory.isDirectory){
+ fileOrDirectory.listFiles()?.forEach {
+ deleteRecursive(it)
+ }
+ }
+
+ fileOrDirectory.delete()
+ }
+}
\ No newline at end of file
diff --git a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/di/LocalBooksModule.kt b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/di/LocalBooksModule.kt
new file mode 100644
index 00000000..1c05728d
--- /dev/null
+++ b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/di/LocalBooksModule.kt
@@ -0,0 +1,96 @@
+package com.allsoftdroid.audiobook.feature_mybooks.di
+
+import com.allsoftdroid.audiobook.feature_mybooks.data.repository.BookMetadataRepositoryImpl
+import com.allsoftdroid.audiobook.feature_mybooks.data.repository.LocalBooksRepositoryImpl
+import com.allsoftdroid.audiobook.feature_mybooks.domain.IBookMetadataRepository
+import com.allsoftdroid.audiobook.feature_mybooks.domain.ILocalBooksRepository
+import com.allsoftdroid.audiobook.feature_mybooks.domain.LocalBookListUsecase
+import com.allsoftdroid.audiobook.feature_mybooks.presentation.LocalBooksViewModel
+import com.allsoftdroid.database.common.AudioBookDatabase
+import com.allsoftdroid.database.metadataCacheDB.MetadataDao
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import org.koin.androidx.viewmodel.dsl.viewModel
+import org.koin.core.context.loadKoinModules
+import org.koin.core.context.unloadKoinModules
+import org.koin.core.module.Module
+import org.koin.core.qualifier.named
+import org.koin.dsl.module
+import kotlin.coroutines.CoroutineContext
+
+object LocalBooksModule {
+
+ fun injectFeature() =
+ loadFeature
+
+ fun unLoadModules(){
+ unloadKoinModules(
+ listOf(
+ localBooksViewModel,
+ dataModule,
+ jobModule
+ )
+ )
+ }
+
+ private val loadFeature by lazy {
+ loadKoinModules(listOf(
+ localBooksViewModel,
+ usecaseModule,
+ dataModule,
+ jobModule
+ ))
+ }
+
+ var localBooksViewModel : Module = module {
+ viewModel {
+ LocalBooksViewModel(
+ bookListUsecase = get()
+ )
+ }
+ }
+
+ var usecaseModule : Module = module {
+ factory {
+ LocalBookListUsecase(
+ localBooksRepository = get(),
+ bookMetadataRepository = get()
+ )
+ }
+ }
+
+ var dataModule : Module = module {
+ factory {
+ LocalBooksRepositoryImpl(
+ application = get(),
+ localFilesForBook = get()
+ ) as ILocalBooksRepository
+ }
+
+ factory {
+ BookMetadataRepositoryImpl(
+ metadataDao = get(named(name = BEAN_NAME))
+ ) as IBookMetadataRepository
+ }
+
+ single(named(name = BEAN_NAME)) {
+ AudioBookDatabase.getDatabase(get()).metadataDao() as MetadataDao
+ }
+ }
+
+ var jobModule : Module = module {
+
+ single(named(name = SUPER_VISOR_JOB)) {
+ SupervisorJob()
+ }
+
+ factory(named(name = VIEW_MODEL_SCOPE)) {
+ CoroutineScope(get(named(name = SUPER_VISOR_JOB)) as CoroutineContext + Dispatchers.Main)
+ }
+ }
+
+ const val SUPER_VISOR_JOB = "SuperVisorJob_LocalBooks"
+ const val VIEW_MODEL_SCOPE = "ViewModelScope_LocalBooks"
+ private const val BEAN_NAME = "LocalBooksFragment"
+}
\ No newline at end of file
diff --git a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/domain/IBookMetadataRepository.kt b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/domain/IBookMetadataRepository.kt
new file mode 100644
index 00000000..4c88d6d7
--- /dev/null
+++ b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/domain/IBookMetadataRepository.kt
@@ -0,0 +1,7 @@
+package com.allsoftdroid.audiobook.feature_mybooks.domain
+
+import com.allsoftdroid.audiobook.feature_mybooks.data.model.BookMetadata
+
+interface IBookMetadataRepository {
+ suspend fun getBookMetadata(identifier:String):BookMetadata
+}
\ No newline at end of file
diff --git a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/domain/ILocalBooksRepository.kt b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/domain/ILocalBooksRepository.kt
new file mode 100644
index 00000000..b5c50031
--- /dev/null
+++ b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/domain/ILocalBooksRepository.kt
@@ -0,0 +1,11 @@
+package com.allsoftdroid.audiobook.feature_mybooks.domain
+
+import com.allsoftdroid.audiobook.feature_mybooks.data.model.LocalBookFiles
+
+interface ILocalBooksRepository {
+ suspend fun getLocalBookFiles():List
+
+ suspend fun removeBook(identifier:String)
+
+ suspend fun deleteAllChapters(identifier: String)
+}
\ No newline at end of file
diff --git a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/domain/LocalBookListUsecase.kt b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/domain/LocalBookListUsecase.kt
new file mode 100644
index 00000000..4e447119
--- /dev/null
+++ b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/domain/LocalBookListUsecase.kt
@@ -0,0 +1,49 @@
+package com.allsoftdroid.audiobook.feature_mybooks.domain
+
+import com.allsoftdroid.audiobook.feature_mybooks.data.model.LocalBookDomainModel
+import timber.log.Timber
+
+class LocalBookListUsecase(
+ private val localBooksRepository: ILocalBooksRepository,
+ private val bookMetadataRepository: IBookMetadataRepository
+) {
+
+ suspend fun getBookList():List{
+
+ val localBooks = mutableListOf()
+
+ val localFiles = localBooksRepository.getLocalBookFiles()
+ Timber.d("local files size is ${localFiles.size}")
+
+ for (file in localFiles){
+
+ val metadata = bookMetadataRepository.getBookMetadata(file.identifier)
+
+ Timber.d("File Identifier is : ${file.identifier}")
+ Timber.d("Metadata is : ${metadata.title}")
+ Timber.d("File is : ${file.filePath}")
+
+ if (metadata.title.isNotEmpty()){
+ localBooks.add(
+ LocalBookDomainModel(
+ bookTitle = metadata.title,
+ bookIdentifier = file.identifier,
+ bookAuthor = metadata.author,
+ bookChaptersDownloaded = file.filePath.size,
+ totalChapters = metadata.totalTracks,
+ fileNames = file.filePath
+ ))
+ }
+ }
+
+ return localBooks
+ }
+
+ suspend fun removeBook(bookId:String){
+ localBooksRepository.removeBook(bookId)
+ }
+
+ suspend fun removeChapters(bookId: String){
+ localBooksRepository.deleteAllChapters(bookId)
+ }
+}
\ No newline at end of file
diff --git a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/presentation/LocalBooksViewModel.kt b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/presentation/LocalBooksViewModel.kt
new file mode 100644
index 00000000..a14d6e46
--- /dev/null
+++ b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/presentation/LocalBooksViewModel.kt
@@ -0,0 +1,89 @@
+package com.allsoftdroid.audiobook.feature_mybooks.presentation
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import com.allsoftdroid.audiobook.feature_mybooks.data.model.LocalBookDomainModel
+import com.allsoftdroid.audiobook.feature_mybooks.di.LocalBooksModule.SUPER_VISOR_JOB
+import com.allsoftdroid.audiobook.feature_mybooks.di.LocalBooksModule.VIEW_MODEL_SCOPE
+import com.allsoftdroid.audiobook.feature_mybooks.domain.LocalBookListUsecase
+import com.allsoftdroid.audiobook.feature_mybooks.utils.Empty
+import com.allsoftdroid.audiobook.feature_mybooks.utils.RequestStatus
+import com.allsoftdroid.audiobook.feature_mybooks.utils.Started
+import com.allsoftdroid.audiobook.feature_mybooks.utils.Success
+import com.allsoftdroid.common.base.extension.Event
+import kotlinx.coroutines.CompletableJob
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import org.koin.core.KoinComponent
+import org.koin.core.inject
+import org.koin.core.qualifier.named
+import timber.log.Timber
+
+class LocalBooksViewModel(
+ private val bookListUsecase: LocalBookListUsecase
+) : ViewModel(),KoinComponent {
+ /**
+ * cancelling this job cancels all the job started by this viewmodel
+ */
+ private val viewModelJob: CompletableJob by inject(named(name = SUPER_VISOR_JOB))
+
+ /**
+ * main scope for all coroutine launched by viewmodel
+ */
+ private val viewModelScope : CoroutineScope by inject(named(name = VIEW_MODEL_SCOPE))
+
+ private var _books:List? = null
+
+ private var _requestStatus = MutableLiveData>()
+ val requestStatus : LiveData> = _requestStatus
+
+
+ private fun loadBooks(){
+ viewModelScope.launch {
+ Timber.d("sending started response")
+ _requestStatus.value = Event(Started)
+
+ val books = bookListUsecase.getBookList()
+ if(books.isEmpty()){
+ Timber.d("books is empty sending empty response")
+ _requestStatus.value = Event(Empty)
+ }else{
+ Timber.d("books is not empty sending response")
+ _requestStatus.value = Event(Success(books))
+ _books = books
+ }
+ }
+ }
+
+ fun loadFromCacheOrReload(){
+
+ val books = _books
+
+ if (books.isNullOrEmpty()){
+ loadBooks()
+ }else{
+ _requestStatus.value = Event(Success(books))
+ }
+ }
+
+ fun removeBook(identifier:String){
+ viewModelScope.launch {
+ bookListUsecase.removeBook(identifier)
+ loadBooks()
+ }
+ }
+
+ fun removeAllChapters(identifier: String){
+ viewModelScope.launch {
+ bookListUsecase.removeChapters(identifier)
+ loadBooks()
+ }
+ }
+
+ //cancel the job when viewmodel is not longer in use
+ override fun onCleared() {
+ super.onCleared()
+ viewModelJob.cancel()
+ }
+}
\ No newline at end of file
diff --git a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/presentation/MyBooksFragment.kt b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/presentation/MyBooksFragment.kt
new file mode 100644
index 00000000..71b01d1b
--- /dev/null
+++ b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/presentation/MyBooksFragment.kt
@@ -0,0 +1,123 @@
+package com.allsoftdroid.audiobook.feature_mybooks.presentation
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.OnBackPressedCallback
+import androidx.core.os.bundleOf
+import androidx.lifecycle.Observer
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.allsoftdroid.audiobook.feature_mybooks.R
+import com.allsoftdroid.audiobook.feature_mybooks.databinding.FragmentMybooksLayoutBinding
+import com.allsoftdroid.audiobook.feature_mybooks.di.LocalBooksModule
+import com.allsoftdroid.audiobook.feature_mybooks.presentation.recyclerView.ItemClickedListener
+import com.allsoftdroid.audiobook.feature_mybooks.presentation.recyclerView.LocalBookAdapter
+import com.allsoftdroid.audiobook.feature_mybooks.presentation.recyclerView.OptionsClickedListener
+import com.allsoftdroid.audiobook.feature_mybooks.utils.Empty
+import com.allsoftdroid.audiobook.feature_mybooks.utils.Started
+import com.allsoftdroid.audiobook.feature_mybooks.utils.Success
+import com.allsoftdroid.common.base.fragment.BaseUIFragment
+import org.koin.core.KoinComponent
+import org.koin.core.inject
+import timber.log.Timber
+
+class MyBooksFragment : BaseUIFragment(),KoinComponent {
+
+ private val localBooksViewModel : LocalBooksViewModel by inject()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val dataBinding:FragmentMybooksLayoutBinding = inflateLayout(inflater,
+ R.layout.fragment_mybooks_layout,container,false)
+
+ LocalBooksModule.injectFeature()
+
+ dataBinding.lifecycleOwner = viewLifecycleOwner
+
+ val adapter = LocalBookAdapter(
+ this.requireActivity(),
+
+ ItemClickedListener {bookId->
+ //Navigate to display page
+ val bundle = bundleOf("bookId" to bookId)
+
+ this.findNavController()
+ .navigate(R.id.action_MyBooksFragment_to_AudioBookDetailsFragment,bundle)
+ },
+
+ OptionsClickedListener(
+ onDeleteBook = {
+ localBooksViewModel.removeBook(it.bookIdentifier)
+ },
+
+ onRemoveChapters = {
+ localBooksViewModel.removeAllChapters(it.bookIdentifier)
+ }
+ )
+ )
+
+ dataBinding.recyclerViewBooks.adapter = adapter
+
+ //recycler view layout manager
+ dataBinding.recyclerViewBooks.apply {
+ layoutManager = LinearLayoutManager(context)
+ }
+
+ dataBinding.toolbarBackArrow.setOnClickListener {
+ onBackPressed()
+ }
+
+ localBooksViewModel.requestStatus.observe(viewLifecycleOwner, Observer {
+ it.getContentIfNotHandled()?.let { status->
+ when(status){
+ is Empty -> {
+ Timber.d("Empty result")
+ dataBinding.loadingProgressbar.visibility = View.GONE
+ dataBinding.noLocalBooks.visibility = View.VISIBLE
+ dataBinding.bookCount.visibility = View.GONE
+ dataBinding.recyclerViewBooks.visibility = View.GONE
+ }
+
+ is Started -> {
+ Timber.d("Started the request")
+ dataBinding.loadingProgressbar.visibility = View.VISIBLE
+ dataBinding.noLocalBooks.visibility = View.GONE
+ dataBinding.bookCount.visibility = View.GONE
+ dataBinding.recyclerViewBooks.visibility = View.GONE
+ }
+
+ is Success -> {
+ Timber.d("Received result:${status.list}")
+ dataBinding.loadingProgressbar.visibility = View.GONE
+ dataBinding.noLocalBooks.visibility = View.GONE
+
+ adapter.submitList(status.list)
+ dataBinding.recyclerViewBooks.visibility = View.VISIBLE
+
+ dataBinding.bookCount.apply {
+ visibility = View.VISIBLE
+ text = requireActivity().getString(R.string.books_in_storage,status.list.size)
+ }
+ }
+ }
+ }
+ })
+
+ return dataBinding.root
+ }
+
+ override fun handleBackPressEvent(callback: OnBackPressedCallback) {
+ callback.isEnabled = false
+ requireActivity().onBackPressed()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ localBooksViewModel.loadFromCacheOrReload()
+ }
+}
\ No newline at end of file
diff --git a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/presentation/recyclerView/LocalBookAdapter.kt b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/presentation/recyclerView/LocalBookAdapter.kt
new file mode 100644
index 00000000..a7ba0198
--- /dev/null
+++ b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/presentation/recyclerView/LocalBookAdapter.kt
@@ -0,0 +1,108 @@
+package com.allsoftdroid.audiobook.feature_mybooks.presentation.recyclerView
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.widget.PopupMenu
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.allsoftdroid.audiobook.feature_mybooks.R
+import com.allsoftdroid.audiobook.feature_mybooks.data.model.LocalBookDomainModel
+import timber.log.Timber
+
+class LocalBookAdapter(
+ private val context: Context,
+ private val itemClickedListener: ItemClickedListener,
+ private val optionsClickedListener: OptionsClickedListener
+): ListAdapter(RandomBookDiffCallback()) {
+
+ /**
+ * Create view Holder of type BookViewHolder
+ */
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ return LocalBookItemViewHolder.from(parent)
+ }
+
+ /**
+ * Bind the ViewHolder with the data item
+ */
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ when(holder){
+ is LocalBookItemViewHolder ->{
+ val dataItem = getItem(position)
+ holder.bind(dataItem,itemClickedListener)
+
+ holder.buttonViewOptions.setOnClickListener {
+ showPopupMenu(dataItem,it)
+ }
+ }
+
+ else -> throw Exception("View Holder type is unknown:$holder")
+ }
+ }
+
+ private fun showPopupMenu(localBook: LocalBookDomainModel,view: View) {
+ val popUp = PopupMenu(context,view)
+ popUp.inflate(R.menu.local_books_option_menu)
+
+ popUp.setOnMenuItemClickListener {
+ when (it.itemId){
+ R.id.ItemOptions_removeAllChapters -> {
+ optionsClickedListener.onRemoveChaptersClicked(localBook)
+ }
+
+ R.id.ItemOptions_removeBook -> {
+
+ optionsClickedListener.onDeleteBookClicked(localBook)
+ Timber.d("remove clicked for ${localBook.bookTitle}")
+ }
+ }
+
+ return@setOnMenuItemClickListener false
+ }
+ popUp.show()
+ }
+}
+
+
+/**
+class to smartly check for difference in new loaded list and old list
+It enhance the performance of the recycler view
+ */
+class RandomBookDiffCallback : DiffUtil.ItemCallback(){
+ /**
+ * Compare items based on identifier fields
+ */
+ override fun areItemsTheSame(oldItem: LocalBookDomainModel, newItem: LocalBookDomainModel): Boolean {
+ return oldItem.bookIdentifier==newItem.bookIdentifier
+ }
+
+ /**
+ * Check every fields to verify for same content.
+ */
+ @SuppressLint("DiffUtilEquals")
+ override fun areContentsTheSame(oldItem: LocalBookDomainModel, newItem: LocalBookDomainModel): Boolean {
+ /*
+ Since book is data class so here all the fields are automatically checked
+ */
+ return oldItem == newItem
+ }
+
+}
+
+/*
+listener to check for the click event
+ */
+class ItemClickedListener(val clickListener : (identifier : String)->Unit){
+ fun onItemClicked(listenLater : LocalBookDomainModel) = clickListener(listenLater.bookIdentifier)
+}
+
+class OptionsClickedListener(
+ val onDeleteBook : (identifier : LocalBookDomainModel)->Unit,
+ val onRemoveChapters : (identifier : LocalBookDomainModel)->Unit)
+{
+ fun onDeleteBookClicked(listenLater : LocalBookDomainModel) = onDeleteBook(listenLater)
+ fun onRemoveChaptersClicked(listenLater : LocalBookDomainModel) = onRemoveChapters(listenLater)
+}
diff --git a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/presentation/recyclerView/LocalBookItemViewHolder.kt b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/presentation/recyclerView/LocalBookItemViewHolder.kt
new file mode 100644
index 00000000..a0b93525
--- /dev/null
+++ b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/presentation/recyclerView/LocalBookItemViewHolder.kt
@@ -0,0 +1,31 @@
+package com.allsoftdroid.audiobook.feature_mybooks.presentation.recyclerView
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.allsoftdroid.audiobook.feature_mybooks.data.model.LocalBookDomainModel
+import com.allsoftdroid.audiobook.feature_mybooks.databinding.MyBooksItemLayoutBinding
+
+class LocalBookItemViewHolder private constructor(private val binding : MyBooksItemLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
+
+ lateinit var buttonViewOptions: View
+
+ // bind the data to the view
+ fun bind(item: LocalBookDomainModel, itemClickedListener: ItemClickedListener) {
+ binding.book = item
+ binding.clickListener = itemClickedListener
+ buttonViewOptions = binding.ItemOptions
+ binding.executePendingBindings()
+ }
+
+ //construct the viewholder
+ companion object {
+ fun from(parent: ViewGroup): LocalBookItemViewHolder {
+ val layoutInflater = LayoutInflater.from(parent.context)
+ val binding = MyBooksItemLayoutBinding.inflate(layoutInflater, parent, false)
+
+ return LocalBookItemViewHolder(binding)
+ }
+ }
+}
\ No newline at end of file
diff --git a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/utils/BindingUtil.kt b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/utils/BindingUtil.kt
new file mode 100644
index 00000000..7a7ea5e5
--- /dev/null
+++ b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/utils/BindingUtil.kt
@@ -0,0 +1,65 @@
+package com.allsoftdroid.audiobook.feature_mybooks.utils
+
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.databinding.BindingAdapter
+import com.allsoftdroid.audiobook.feature_mybooks.R
+import com.allsoftdroid.audiobook.feature_mybooks.data.model.LocalBookDomainModel
+import com.allsoftdroid.common.base.extension.CreateImageOverlay
+import com.allsoftdroid.common.base.network.ArchiveUtils
+import com.allsoftdroid.common.base.utils.BindingUtils.getNormalizedText
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.engine.DiskCacheStrategy
+import com.bumptech.glide.request.RequestOptions
+
+@BindingAdapter("bookImage")
+fun setImageUrl(imageView: ImageView, item: LocalBookDomainModel?) {
+
+ item?.let {
+ val url = ArchiveUtils.getThumbnail(item.bookIdentifier)
+
+ Glide
+ .with(imageView.context)
+ .asBitmap()
+ .load(url)
+ .override(250,250)
+ .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
+ .dontAnimate()
+ .apply(
+ RequestOptions()
+ .placeholder(R.drawable.loading_animation)
+ .error(
+ CreateImageOverlay
+ .with(imageView.context)
+ .buildOverlay(front = R.drawable.ic_book_play,back = R.drawable.gradiant_background)
+ )
+ )
+ .into(imageView)
+ }
+}
+
+
+/*
+Binding adapter for updating the title in list items
+ */
+@BindingAdapter("bookTitle")
+fun TextView.setBookTitle(item: LocalBookDomainModel?){
+ item?.let {
+ text = getNormalizedText(item.bookTitle, 30)
+ }
+}
+
+@BindingAdapter("bookAuthor")
+fun TextView.setBookAuthor(item: LocalBookDomainModel?){
+ item?.let {
+ text =
+ getNormalizedText(item.bookAuthor, 30)
+ }
+}
+
+@BindingAdapter("bookChapterCount")
+fun TextView.setBookDuration(item: LocalBookDomainModel?){
+ item?.let {
+ text = context.getString(R.string.chapters_label,it.bookChaptersDownloaded,it.totalChapters)
+ }
+}
\ No newline at end of file
diff --git a/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/utils/RequestStatus.kt b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/utils/RequestStatus.kt
new file mode 100644
index 00000000..f8deb683
--- /dev/null
+++ b/feature_mybooks/src/main/java/com/allsoftdroid/audiobook/feature_mybooks/utils/RequestStatus.kt
@@ -0,0 +1,9 @@
+package com.allsoftdroid.audiobook.feature_mybooks.utils
+
+import com.allsoftdroid.audiobook.feature_mybooks.data.model.LocalBookDomainModel
+
+sealed class RequestStatus
+
+data class Success(val list : List) : RequestStatus()
+object Empty : RequestStatus()
+object Started : RequestStatus()
diff --git a/feature_mybooks/src/main/res/drawable/background_round_corner_dark_border.xml b/feature_mybooks/src/main/res/drawable/background_round_corner_dark_border.xml
new file mode 100644
index 00000000..e0826a52
--- /dev/null
+++ b/feature_mybooks/src/main/res/drawable/background_round_corner_dark_border.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature_mybooks/src/main/res/layout/fragment_mybooks_layout.xml b/feature_mybooks/src/main/res/layout/fragment_mybooks_layout.xml
index 73bd022a..6e627321 100644
--- a/feature_mybooks/src/main/res/layout/fragment_mybooks_layout.xml
+++ b/feature_mybooks/src/main/res/layout/fragment_mybooks_layout.xml
@@ -1,17 +1,94 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/bookCount"/>
+
+
+
+
+
+
-
\ No newline at end of file
diff --git a/feature_mybooks/src/main/res/layout/layout_no_books_found_local_storage.xml b/feature_mybooks/src/main/res/layout/layout_no_books_found_local_storage.xml
new file mode 100644
index 00000000..b8e1e814
--- /dev/null
+++ b/feature_mybooks/src/main/res/layout/layout_no_books_found_local_storage.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature_mybooks/src/main/res/layout/my_books_item_layout.xml b/feature_mybooks/src/main/res/layout/my_books_item_layout.xml
new file mode 100644
index 00000000..8a57d1c4
--- /dev/null
+++ b/feature_mybooks/src/main/res/layout/my_books_item_layout.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature_mybooks/src/main/res/menu/local_books_option_menu.xml b/feature_mybooks/src/main/res/menu/local_books_option_menu.xml
new file mode 100644
index 00000000..87a7ffe3
--- /dev/null
+++ b/feature_mybooks/src/main/res/menu/local_books_option_menu.xml
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/feature_mybooks/src/main/res/values/dimens.xml b/feature_mybooks/src/main/res/values/dimens.xml
new file mode 100644
index 00000000..8d3c3561
--- /dev/null
+++ b/feature_mybooks/src/main/res/values/dimens.xml
@@ -0,0 +1,18 @@
+
+
+ 4dp
+ 16dp
+ 24dp
+
+ 2dp
+ 8dp
+
+ 16dp
+ 16dp
+ 8dp
+
+ 90dp
+
+ 16dp
+ 24dp
+
\ No newline at end of file
diff --git a/feature_mybooks/src/main/res/values/strings.xml b/feature_mybooks/src/main/res/values/strings.xml
new file mode 100644
index 00000000..abf3be0c
--- /dev/null
+++ b/feature_mybooks/src/main/res/values/strings.xml
@@ -0,0 +1,13 @@
+
+
+ My Books
+ navigate to previous screen
+ No Books found in Storage
+ book thumbnail image
+ ⋮
+ Remove All Chapters
+ Delete Book
+ Number of chapters available locally
+ %1d Books in Storage
+ %d/%d Chapters
+
\ No newline at end of file