{ 4 | 5 | var requestValues : Q? = null 6 | var useCaseCallback : UseCaseCallback? = null 7 | 8 | 9 | internal suspend fun run(){ 10 | executeUseCase(requestValues) 11 | } 12 | 13 | protected abstract suspend fun executeUseCase(requestValues : Q?) 14 | 15 | /** 16 | * Data passed to a request 17 | */ 18 | interface RequestValues 19 | 20 | /** 21 | * Data received from a request 22 | */ 23 | interface ResponseValues 24 | 25 | 26 | interface UseCaseCallback
{ 27 | suspend fun onSuccess(response : R) 28 | suspend fun onError(t : Throwable) 29 | } 30 | } -------------------------------------------------------------------------------- /database/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /services/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /database/src/main/java/com/allsoftdroid/database/metadataCacheDB/entity/DatabaseMetadataEntity.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.database.metadataCacheDB.entity 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | 7 | 8 | @Entity(tableName = "Metadata_Table") 9 | data class DatabaseMetadataEntity( 10 | 11 | @PrimaryKey 12 | @ColumnInfo(name = "metadata_id") 13 | var identifier : String, 14 | 15 | @ColumnInfo(name = "uploader") 16 | var creator : String, 17 | 18 | @ColumnInfo(name = "upload_date") 19 | var date : String, 20 | 21 | var description : String, 22 | 23 | var licenseUrl : String, 24 | 25 | @ColumnInfo(name = "category") 26 | var tag : String, 27 | 28 | var title : String, 29 | 30 | var release_year : String, 31 | 32 | var runtime: String 33 | ) -------------------------------------------------------------------------------- /feature_mybooks/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /feature_settings/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /common/src/main/java/com/allsoftdroid/common/base/fragment/BaseUIFragment.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.common.base.fragment 2 | 3 | import android.os.Bundle 4 | import androidx.activity.OnBackPressedCallback 5 | import androidx.activity.addCallback 6 | 7 | abstract class BaseUIFragment : BaseContainerFragment() { 8 | 9 | private lateinit var callback: OnBackPressedCallback 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | 14 | callback = requireActivity().onBackPressedDispatcher.addCallback(this){ 15 | handleBackPressEvent(callback) 16 | } 17 | 18 | callback.isEnabled = true 19 | } 20 | 21 | abstract fun handleBackPressEvent(callback: OnBackPressedCallback) 22 | 23 | fun onBackPressed(){ 24 | handleBackPressEvent(callback) 25 | } 26 | } -------------------------------------------------------------------------------- /feature_mini_player/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /feature_listen_later_ui/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /feature_playerfullscreen/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /feature_audiobook_enhance_details/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /common/src/main/java/com/allsoftdroid/common/base/extension/ContextExtension.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.common.base.extension 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.widget.Toast 6 | import androidx.annotation.StringRes 7 | import androidx.appcompat.app.AppCompatActivity 8 | import androidx.core.os.bundleOf 9 | import kotlin.reflect.KProperty1 10 | 11 | fun Context.toast(@StringRes resId: Int, length: Int = Toast.LENGTH_SHORT) { 12 | Toast.makeText(this, getString(resId), length).show() 13 | } 14 | 15 | inline fun Context.startActivity( 16 | vararg params: Pair , Any?> 17 | ) { 18 | val extras = params.map { it.first.name to it.second }.toTypedArray() 19 | val intent = Intent(this, T::class.java) 20 | intent.putExtras(bundleOf(*extras)) 21 | startActivity(intent) 22 | } -------------------------------------------------------------------------------- /common/src/main/java/com/allsoftdroid/common/test/EspressoIdlingResource.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.common.test 2 | 3 | import androidx.test.espresso.idling.CountingIdlingResource 4 | 5 | object EspressoIdlingResource { 6 | 7 | private const val RESOURCE = "GLOBAL" 8 | 9 | @JvmField 10 | val countingIdlingResource = CountingIdlingResource(RESOURCE) 11 | 12 | fun increment() { 13 | countingIdlingResource.increment() 14 | } 15 | 16 | fun decrement() { 17 | if (!countingIdlingResource.isIdleNow) { 18 | countingIdlingResource.decrement() 19 | } 20 | } 21 | } 22 | 23 | inline fun wrapEspressoIdlingResource(function: () -> T): T { 24 | EspressoIdlingResource.increment() // Set app as busy. 25 | return try { 26 | function() 27 | } finally { 28 | EspressoIdlingResource.decrement() // Set app as idle. 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/allsoftdroid/audiobook/presentation/utils/TakeScreenshotUtils.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.audiobook.presentation.utils 2 | 3 | import androidx.test.runner.screenshot.Screenshot 4 | import timber.log.Timber 5 | import java.io.IOException 6 | 7 | object TakeScreenshotUtils { 8 | fun takeScreenshot(parentFolderPath: String = "", screenShotName: String) { 9 | Timber.d("Taking screenshot of '$screenShotName'") 10 | val screenCapture = Screenshot.capture() 11 | val processors = setOf(ScreenCaptureProcessor(parentFolderPath)) 12 | try { 13 | screenCapture.apply { 14 | name = screenShotName 15 | process(processors) 16 | } 17 | Timber.d("Screenshot taken") 18 | } catch (ex: IOException) { 19 | Timber.d("Could not take the screenshot: $ex") 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/allsoftdroid/audiobook/utility/MoveUpBehavior.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.audiobook.utility 2 | 3 | import android.view.View 4 | import androidx.coordinatorlayout.widget.CoordinatorLayout 5 | import com.google.android.material.snackbar.Snackbar.SnackbarLayout 6 | 7 | 8 | class MoveUpBehavior:CoordinatorLayout.Behavior () { 9 | 10 | override fun layoutDependsOn( 11 | parent: CoordinatorLayout, 12 | child: View, 13 | dependency: View 14 | ): Boolean { 15 | return dependency is SnackbarLayout 16 | } 17 | 18 | override fun onDependentViewChanged( 19 | parent: CoordinatorLayout, 20 | child: View, 21 | dependency: View 22 | ): Boolean { 23 | val translationY = 24 | Math.min(0f, dependency.translationY - dependency.height) 25 | child.translationY = translationY+1 26 | return true 27 | } 28 | } -------------------------------------------------------------------------------- /.github/workflows/Create-Release-CI.yml: -------------------------------------------------------------------------------- 1 | name: "Release Deploy CI" 2 | 3 | on: 4 | push: 5 | branches: # array of glob patterns matching against refs/heads. Optional; defaults to all 6 | - release # triggers on pushes that contain changes in release 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: Create Release Folder 14 | run: rsync -arv --exclude='.git/' --exclude='.github/' --exclude='.idea/' --exclude='.gitignore' . ./release 15 | - name: Switch to Release folder 16 | run: | 17 | cd release 18 | ls -la 19 | - name: Bump Version and Push tag 20 | uses: anothrNick/github-tag-action@master 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | REPO_OWNER: pravinyo 24 | WITH_V: true 25 | RELEASE_BRANCHES: release -------------------------------------------------------------------------------- /database/src/main/java/com/allsoftdroid/database/networkCacheDB/NetworkCacheDao.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.database.networkCacheDB 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | 10 | @Dao 11 | interface NetworkCacheDao { 12 | @Query("SELECT response FROM Network_Cache_Table where networkRequestId=:networkRequestId") 13 | fun getNetworkResponse(networkRequestId: String): Flow 14 | 15 | @Insert(onConflict = OnConflictStrategy.REPLACE) 16 | suspend fun insertResponse(networkResponseEntity: DatabaseNetworkResponseEntity) 17 | 18 | @Query("DELETE FROM Network_Cache_Table WHERE networkRequestId=:networkRequestId") 19 | suspend fun removeResponse(networkRequestId: String) 20 | 21 | @Query("DELETE FROM Network_Cache_Table") 22 | suspend fun removeAll() 23 | } -------------------------------------------------------------------------------- /feature_book_details/src/test/java/com/allsoftdroid/feature/book_details/utils/FakeListenLaterRepository.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.feature.book_details.utils 2 | 3 | import com.allsoftdroid.feature.book_details.data.model.ListenLaterDomainModel 4 | import com.allsoftdroid.feature.book_details.domain.repository.IListenLaterRepository 5 | 6 | internal class FakeListenLaterRepository : IListenLaterRepository { 7 | 8 | var listenLater:ListenLaterDomainModel?=null 9 | 10 | override suspend fun isAddedToListenLater(bookId: String): Boolean { 11 | if(listenLater!=null){ 12 | return listenLater!!.bookId == bookId 13 | } 14 | return false 15 | } 16 | 17 | override suspend fun addToListenLater(listenLater: ListenLaterDomainModel) { 18 | this.listenLater = listenLater 19 | } 20 | 21 | override suspend fun removeListenLater(bookId: String) { 22 | this.listenLater = null 23 | } 24 | } -------------------------------------------------------------------------------- /feature_book_details/src/main/java/com/allsoftdroid/feature/book_details/domain/repository/BookDetailsSharedPreferenceRepository.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.feature.book_details.domain.repository 2 | 3 | /** 4 | * Interface for sharePreference for saving current state of the track Playing 5 | */ 6 | 7 | interface BookDetailsSharedPreferenceRepository { 8 | 9 | fun saveTrackPosition(pos : Int) 10 | fun trackPosition():Int 11 | 12 | fun saveTrackTitle(title : String) 13 | fun trackTitle():String 14 | 15 | fun saveBookId(bookId:String) 16 | fun bookId():String 17 | 18 | fun saveBookName(name:String) 19 | fun bookName():String 20 | 21 | fun saveTrackFormatIndex(formatIndex:Int) 22 | fun trackFormatIndex():Int 23 | 24 | fun clear() 25 | 26 | fun saveIsPlaying(isPlaying: Boolean) 27 | fun isPlaying(): Boolean 28 | 29 | fun isToolTipShown():Boolean 30 | fun setToolTipShown(shouldSkip:Boolean) 31 | } -------------------------------------------------------------------------------- /feature_downloader/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /feature_listen_later_ui/src/main/java/com/allsoftdroid/audiobook/feature_listen_later_ui/data/model/BookMarkDataItem.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.audiobook.feature_listen_later_ui.data.model 2 | 3 | import com.allsoftdroid.database.listenLaterDB.entity.DatabaseListenLaterEntity 4 | 5 | data class BookMarkDataItem( 6 | val bookId:String, 7 | val bookName:String, 8 | val bookAuthor:String, 9 | val duration:String?, 10 | val timeStamp:String 11 | ) 12 | 13 | fun DatabaseListenLaterEntity.toExternalModel() = BookMarkDataItem( 14 | bookId = this.identifier, 15 | bookName = this.title, 16 | bookAuthor = this.author, 17 | duration = this.duration, 18 | timeStamp = this.timeStamp 19 | ) 20 | 21 | fun BookMarkDataItem.toDatabaseModel() = DatabaseListenLaterEntity( 22 | identifier = this.bookId, 23 | title = this.bookName, 24 | author = this.bookAuthor, 25 | duration = this.duration?:"", 26 | timeStamp = this.timeStamp 27 | ) -------------------------------------------------------------------------------- /feature_book_details/src/main/res/drawable/ic_information_variant.xml: -------------------------------------------------------------------------------- 1 | 2 |multiple download 4 |happy icons 5 |No Downloads… 6 |clear all downloaded 7 |file icon 8 |waiting… 9 |cancel download 10 |delete 11 |DELETE 12 |CANCEL 13 |Downloading… 14 |Don\'t Show this 15 |Download Status 16 |7 | -------------------------------------------------------------------------------- /feature_listen_later_ui/src/main/java/com/allsoftdroid/audiobook/feature_listen_later_ui/domain/contracts/ImportFileContract.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.audiobook.feature_listen_later_ui.domain.contracts 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.net.Uri 7 | import androidx.activity.result.contract.ActivityResultContract 8 | 9 | class ImportFileContract : ActivityResultContract12 | () { 10 | override fun createIntent(context: Context, input: Int?): Intent { 11 | val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) 12 | intent.addCategory(Intent.CATEGORY_OPENABLE) 13 | intent.type = "text/plain" 14 | return intent 15 | } 16 | 17 | override fun parseResult(resultCode: Int, intent: Intent?): Uri? = when{ 18 | resultCode != Activity.RESULT_OK -> null // Return null, if action is cancelled 19 | else -> intent?.data // Return the data 20 | } 21 | } -------------------------------------------------------------------------------- /feature_book/src/main/res/drawable/ic_gauge.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /feature_listen_later_ui/src/main/java/com/allsoftdroid/audiobook/feature_listen_later_ui/domain/repository/IExportUserDataRepository.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.audiobook.feature_listen_later_ui.domain.repository 2 | 3 | import android.os.ParcelFileDescriptor 4 | import com.allsoftdroid.audiobook.feature_listen_later_ui.data.model.BookMarkDataItem 5 | 6 | /** 7 | * Interface for saving user data to specified path 8 | */ 9 | interface IExportUserDataRepository { 10 | /** 11 | * This method allow data to be saved at path. on separate thread 12 | * @param path to where this content be saved 13 | * @param data which need to be saved 14 | */ 15 | suspend fun toFile(path:String,data: List8 | ) 16 | 17 | /** 18 | * This method allow data to be saved at path. on separate thread 19 | * @param pfd to where this content be saved 20 | * @param data which need to be saved 21 | */ 22 | suspend fun toFile(pfd: ParcelFileDescriptor, data: List ) 23 | } -------------------------------------------------------------------------------- /feature_listen_later_ui/src/main/java/com/allsoftdroid/audiobook/feature_listen_later_ui/domain/repository/IImportUserDataRepository.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.audiobook.feature_listen_later_ui.domain.repository 2 | 3 | import android.os.ParcelFileDescriptor 4 | import com.allsoftdroid.audiobook.feature_listen_later_ui.data.model.BookMarkDataItem 5 | 6 | /** 7 | * Interface for importing user content to App Listen later DB 8 | */ 9 | interface IImportUserDataRepository { 10 | /** 11 | * It load the data from the file at Path and returns list of data. 12 | * @param path of the file for data import 13 | * @return list of @[BookMarkDataItem] 14 | */ 15 | suspend fun fromFile(path:String):List 16 | 17 | /** 18 | * It load the data from the file at Path and returns list of data. 19 | * @param pfd of the file for data import 20 | * @return list of @[BookMarkDataItem] 21 | */ 22 | suspend fun fromFile(pfd: ParcelFileDescriptor):List 23 | } -------------------------------------------------------------------------------- /common/src/main/res/drawable/loading_animation.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 24 | -------------------------------------------------------------------------------- /feature_book/src/main/java/com/allsoftdroid/feature_book/data/databaseExtension/DatabaseEntities.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.feature_book.data.databaseExtension 2 | 3 | import com.allsoftdroid.database.bookListDB.DatabaseAudioBook 4 | import com.allsoftdroid.feature_book.domain.model.AudioBookDomainModel 5 | 6 | /** 7 | * Convert database instance into domain model instance 8 | */ 9 | 10 | fun List .asBookDomainModel():List { 11 | return map { 12 | AudioBookDomainModel( 13 | mId = it.identifier, 14 | title = it.title, 15 | creator = it.creator, 16 | date = it.date, 17 | addeddate = it.addeddate?:"" 18 | ) 19 | } 20 | } 21 | 22 | fun DatabaseAudioBook.toBookDomainModel():AudioBookDomainModel{ 23 | return AudioBookDomainModel( 24 | mId = identifier, 25 | title = title, 26 | creator = creator, 27 | date = date, 28 | addeddate = addeddate?:"" 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /feature_downloader/src/main/java/com/allsoftdroid/audiobook/feature_downloader/domain/IDownloader.java: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.audiobook.feature_downloader.domain; 2 | 3 | import android.content.Context; 4 | 5 | import com.allsoftdroid.common.base.store.downloader.Download; 6 | 7 | import java.util.List; 8 | 9 | public interface IDownloader extends IDownloaderCore { 10 | 11 | void updateDownloaded(String mUrl,String mBookId,int mChapterIndex); 12 | void updateProgress(String mUrl,String mBookId,int mChapterIndex,long progress); 13 | 14 | long[] getProgress(long downloadId); 15 | void bulkDownload(List downloads); 16 | 17 | void removeFromDownloadDatabase(long downloadId); 18 | 19 | void LogAllLocalData(); 20 | void clearAllDownloadedEntry(); 21 | 22 | String findURLbyDownloadId(long downloadId); 23 | long getDownloadIdByURL(String url); 24 | 25 | byte getStatusByDownloadId(long downloadId); 26 | 27 | void openDownloadedFile(Context context, long downloadId); 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 16 | 17 | 18 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /feature_book_details/src/main/java/com/allsoftdroid/feature/book_details/domain/usecase/ListenLaterUsecase.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.feature.book_details.domain.usecase 2 | 3 | import com.allsoftdroid.feature.book_details.data.model.ListenLaterDomainModel 4 | import com.allsoftdroid.feature.book_details.domain.repository.IListenLaterRepository 5 | 6 | internal class ListenLaterUsecase( 7 | private val listenLaterRepository: IListenLaterRepository 8 | ) { 9 | 10 | suspend fun addToListenLater(bookId:String,title:String,author:String,duration:String){ 11 | val listenLaterDomainModel = ListenLaterDomainModel(bookId = bookId, 12 | bookName = title, 13 | bookAuthor = author, 14 | bookDuration = duration) 15 | 16 | listenLaterRepository.addToListenLater(listenLaterDomainModel) 17 | } 18 | 19 | suspend fun isAdded(bookId: String) = listenLaterRepository.isAddedToListenLater(bookId) 20 | 21 | suspend fun remove(bookId: String) = listenLaterRepository.removeListenLater(bookId) 22 | } -------------------------------------------------------------------------------- /.github/workflows/Build-InstrumentationTest.yml: -------------------------------------------------------------------------------- 1 | name: AudioBook InstrumentationTest 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'ci_instrumentation_test' 7 | 8 | jobs: 9 | instrumentationJob: 10 | name: Run Instrumentation Tests 11 | runs-on: ubuntu-18.04 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: set up JDK 1.8 16 | uses: actions/setup-java@v1 17 | with: 18 | java-version: 1.8 19 | 20 | - uses: malinskiy/action-android/install-sdk@release/0.0.6 21 | - run: adb devices 22 | - run: echo $ANDROID_HOME 23 | 24 | - uses: malinskiy/action-android/emulator-run-cmd@release/0.0.6 25 | with: 26 | cmd: ./gradlew connectedDebugAndroidTest 27 | api: 25 28 | tag: default 29 | abi: x86 30 | - name: Save logcat output 31 | uses: actions/upload-artifact@master 32 | if: failure() 33 | with: 34 | name: logcat 35 | path: artifacts/logcat.log 36 | -------------------------------------------------------------------------------- /.github/workflows/lock-thread-ci.yml: -------------------------------------------------------------------------------- 1 | name: 'Lock threads Bot' 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | jobs: 8 | lock: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: dessant/lock-threads@v2 12 | with: 13 | github-token: ${{ github.token }} 14 | issue-lock-inactive-days: '10' 15 | issue-lock-reason: 'resolved' 16 | issue-lock-labels: 'outdated' 17 | issue-lock-comment: > 18 | This issue has been automatically locked since there 19 | has not been any recent activity after it was closed. 20 | Please open a new issue for related bugs. 21 | pr-lock-inactive-days: '10' 22 | issue-exclude-labels: 'bug, help-wanted' 23 | pr-lock-reason: 'resolved' 24 | pr-lock-comment: > 25 | This pull request has been automatically locked since there 26 | has not been any recent activity after it was closed. 27 | Please open a new issue for related bugs. 28 | -------------------------------------------------------------------------------- /database/src/main/java/com/allsoftdroid/database/metadataCacheDB/entity/DatabaseTrackEntity.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.database.metadataCacheDB.entity 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.ForeignKey 6 | import androidx.room.PrimaryKey 7 | 8 | @Entity(tableName = "MediaTrack_Table") 9 | data class DatabaseTrackEntity( 10 | 11 | @PrimaryKey 12 | var track_id : String, 13 | 14 | @ForeignKey( 15 | entity = DatabaseAlbumEntity::class, 16 | parentColumns = ["album_metadata_id"], 17 | childColumns = ["track_album_id"], 18 | onDelete = ForeignKey.RESTRICT) 19 | @ColumnInfo(name = "track_album_id") 20 | var trackAlbum_id : String, 21 | 22 | @ColumnInfo(name = "remote_filename") 23 | val filename : String, 24 | 25 | @ColumnInfo(name = "title") 26 | var trackTitle : String?, 27 | 28 | var trackNumber : Int?, 29 | 30 | var length: String?, 31 | 32 | var format: String?, 33 | 34 | var size : String? 35 | ) -------------------------------------------------------------------------------- /feature_mini_player/src/main/java/com/allsoftdroid/audiobook/feature_mini_player/di/FeatureMiniPlayerModule.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.audiobook.feature_mini_player.di 2 | 3 | import com.allsoftdroid.audiobook.feature_mini_player.presentation.viewModel.MiniPlayerViewModel 4 | import org.koin.androidx.viewmodel.dsl.viewModel 5 | import org.koin.core.context.loadKoinModules 6 | import org.koin.core.context.unloadKoinModules 7 | import org.koin.core.module.Module 8 | import org.koin.dsl.module 9 | 10 | object FeatureMiniPlayerModule { 11 | fun injectFeature() = loadFeature 12 | 13 | fun unloadModule() = unloadKoinModules(listOf( 14 | miniPlayerViewModelModule 15 | )) 16 | 17 | private val loadFeature by lazy { 18 | loadKoinModules(listOf( 19 | miniPlayerViewModelModule 20 | )) 21 | } 22 | 23 | private val miniPlayerViewModelModule: Module = module{ 24 | viewModel { 25 | MiniPlayerViewModel(eventStore = get(),userActionEventStore = get()) 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /.github/workflows/Closes-Issue-Checker-Bot.yml: -------------------------------------------------------------------------------- 1 | name: 'Lock threads Bot' 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | jobs: 8 | lock: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: dessant/lock-threads@v2 12 | with: 13 | github-token: ${{ github.token }} 14 | issue-lock-inactive-days: '10' 15 | issue-lock-reason: 'resolved' 16 | issue-lock-labels: 'outdated' 17 | issue-lock-comment: > 18 | This issue has been automatically locked since there 19 | has not been any recent activity after it was closed. 20 | Please open a new issue for related bugs. 21 | pr-lock-inactive-days: '10' 22 | issue-exclude-labels: 'bug, help-wanted' 23 | pr-lock-reason: 'resolved' 24 | pr-lock-comment: > 25 | This pull request has been automatically locked since there 26 | has not been any recent activity after it was closed. 27 | Please open a new issue for related bugs. 28 | -------------------------------------------------------------------------------- /common/src/main/java/com/allsoftdroid/common/test/MainCoroutineRule.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.common.test 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.ExperimentalCoroutinesApi 5 | import kotlinx.coroutines.test.TestCoroutineDispatcher 6 | import kotlinx.coroutines.test.TestCoroutineScope 7 | import kotlinx.coroutines.test.resetMain 8 | import kotlinx.coroutines.test.setMain 9 | import org.junit.rules.TestWatcher 10 | import org.junit.runner.Description 11 | 12 | @ExperimentalCoroutinesApi 13 | class MainCoroutineRule(val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()): 14 | TestWatcher(), 15 | TestCoroutineScope by TestCoroutineScope(dispatcher) { 16 | override fun starting(description: Description?) { 17 | super.starting(description) 18 | Dispatchers.setMain(dispatcher) 19 | } 20 | 21 | override fun finished(description: Description?) { 22 | super.finished(description) 23 | cleanupTestCoroutines() 24 | Dispatchers.resetMain() 25 | } 26 | } -------------------------------------------------------------------------------- /feature_listen_later_ui/src/main/java/com/allsoftdroid/audiobook/feature_listen_later_ui/domain/contracts/ExportFileContract.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.audiobook.feature_listen_later_ui.domain.contracts 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.net.Uri 7 | import androidx.activity.result.contract.ActivityResultContract 8 | 9 | class ExportFileContract: ActivityResultContract() { 10 | override fun createIntent(context: Context, documentName: String): Intent { 11 | val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) 12 | intent.addCategory(Intent.CATEGORY_OPENABLE) 13 | intent.type = "text/plain" 14 | intent.putExtra(Intent.EXTRA_TITLE, documentName) 15 | return intent 16 | } 17 | 18 | override fun parseResult(resultCode: Int, intent: Intent?): Uri? = when{ 19 | resultCode != Activity.RESULT_OK -> null // Return null, if action is cancelled 20 | else -> intent?.data // Return the data 21 | } 22 | } -------------------------------------------------------------------------------- /common/src/main/java/com/allsoftdroid/common/base/utils/ImageUtils.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.common.base.utils 2 | 3 | import android.graphics.* 4 | 5 | 6 | class ImageUtils { 7 | 8 | companion object{ 9 | fun getCircleBitmap(bitmap: Bitmap): Bitmap { 10 | val output = Bitmap.createBitmap( 11 | bitmap.width, 12 | bitmap.height, Bitmap.Config.ARGB_8888 13 | ) 14 | val canvas = Canvas(output) 15 | 16 | val color = Color.RED 17 | val paint = Paint() 18 | val rect = Rect(0, 0, bitmap.width, bitmap.height) 19 | val rectF = RectF(rect) 20 | 21 | paint.isAntiAlias = true 22 | canvas.drawARGB(0, 0, 0, 0) 23 | paint.color = color 24 | canvas.drawOval(rectF, paint) 25 | 26 | paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) 27 | canvas.drawBitmap(bitmap, rect, rect, paint) 28 | 29 | bitmap.recycle() 30 | 31 | return output 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /common/src/main/res/layout/layout_no_connection.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /feature_book_details/src/main/res/layout/layout_server_error.xml: -------------------------------------------------------------------------------- 1 | 2 |15 | 16 | 24 | 7 | 8 | -------------------------------------------------------------------------------- /feature_book_details/src/test/java/com/allsoftdroid/feature/book_details/utils/FakeNetworkCacheDao.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.feature.book_details.utils 2 | 3 | import com.allsoftdroid.database.networkCacheDB.DatabaseNetworkResponseEntity 4 | import com.allsoftdroid.database.networkCacheDB.NetworkCacheDao 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.flowOf 7 | 8 | class FakeNetworkCacheDao : NetworkCacheDao { 9 | 10 | private var hashMap = HashMap15 | 16 | 24 | () 11 | 12 | override fun getNetworkResponse(networkRequestId: String): Flow { 13 | return flowOf(hashMap[networkRequestId]?:"") 14 | } 15 | 16 | override suspend fun insertResponse(networkResponseEntity: DatabaseNetworkResponseEntity) { 17 | hashMap[networkResponseEntity.identifier] = networkResponseEntity.networkResponse 18 | } 19 | 20 | override suspend fun removeResponse(networkRequestId: String) { 21 | hashMap.remove(networkRequestId) 22 | } 23 | 24 | override suspend fun removeAll() { 25 | hashMap.clear() 26 | } 27 | } -------------------------------------------------------------------------------- /.github/workflows/Build-UnitTest.yml: -------------------------------------------------------------------------------- 1 | name: AudioBook Feature UnitTest 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | - '!master' 8 | - '!release*' 9 | 10 | jobs: 11 | testJob: 12 | name: Run Unit Tests 13 | runs-on: ubuntu-18.04 14 | 15 | steps: 16 | - uses: actions/checkout@v1 17 | - name: set up JDK 1.8 18 | uses: actions/setup-java@v1 19 | with: 20 | java-version: 1.8 21 | - name: Unit tests 22 | run: bash ./gradlew testDebugUnitTest --stacktrace 23 | 24 | apk: 25 | name: Generate APK 26 | needs: testJob 27 | runs-on: ubuntu-18.04 28 | 29 | steps: 30 | - uses: actions/checkout@v1 31 | - name: set up JDK 1.8 32 | uses: actions/setup-java@v1 33 | with: 34 | java-version: 1.8 35 | - name: Build debug APK 36 | run: bash ./gradlew assembleDebug --stacktrace 37 | - name: Upload APK 38 | uses: actions/upload-artifact@v1 39 | with: 40 | name: app 41 | path: app/build/outputs/apk/debug/app-debug.apk -------------------------------------------------------------------------------- /feature_book_details/src/androidTest/java/com/allsoftdroid/feature/book_details/presentation/utils/FakeNetworkCacheDao.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.feature.book_details.presentation.utils 2 | 3 | import com.allsoftdroid.database.networkCacheDB.DatabaseNetworkResponseEntity 4 | import com.allsoftdroid.database.networkCacheDB.NetworkCacheDao 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.flowOf 7 | 8 | class FakeNetworkCacheDao : NetworkCacheDao { 9 | 10 | private var hashMap = HashMap () 11 | 12 | override fun getNetworkResponse(networkRequestId: String): Flow { 13 | return flowOf(hashMap[networkRequestId]?:"") 14 | } 15 | 16 | override suspend fun insertResponse(networkResponseEntity: DatabaseNetworkResponseEntity) { 17 | hashMap[networkResponseEntity.identifier] = networkResponseEntity.networkResponse 18 | } 19 | 20 | override suspend fun removeResponse(networkRequestId: String) { 21 | hashMap.remove(networkRequestId) 22 | } 23 | 24 | override suspend fun removeAll() { 25 | hashMap.clear() 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | -------------------------------------------------------------------------------- /feature_book/src/main/java/com/allsoftdroid/feature_book/data/model/AudioBookDataModel.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.feature_book.data.model 2 | 3 | import com.allsoftdroid.database.bookListDB.DatabaseAudioBook 4 | import com.allsoftdroid.feature_book.domain.model.AudioBookDomainModel 5 | 6 | internal data class AudioBookDataModel( 7 | val identifier: String, 8 | val title: String, 9 | val creator: Any?, 10 | val date: String, 11 | val addeddate:String? 12 | ) 13 | 14 | internal fun AudioBookDataModel.toDomainModel(): AudioBookDomainModel { 15 | 16 | return AudioBookDomainModel( 17 | mId = this.identifier, 18 | title = this.title, 19 | creator = this.creator?.toString()?:"N/A", 20 | date = this.date, 21 | addeddate = this.addeddate 22 | ) 23 | } 24 | 25 | 26 | internal fun AudioBookDataModel.toDatabaseModel(): DatabaseAudioBook { 27 | 28 | return DatabaseAudioBook( 29 | identifier = this.identifier, 30 | title = this.title, 31 | creator = this.creator?.toString()?:"N/A", 32 | date = this.date, 33 | addeddate = this.addeddate 34 | ) 35 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Pravin Tripathi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /feature_listen_later_ui/src/main/java/com/allsoftdroid/audiobook/feature_listen_later_ui/domain/repository/IListenLaterRepository.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.audiobook.feature_listen_later_ui.domain.repository 2 | 3 | import com.allsoftdroid.audiobook.feature_listen_later_ui.data.model.ListenLaterItemDomainModel 4 | 5 | interface IListenLaterRepository { 6 | /** 7 | * Remove [ListenLaterItemDomainModel] from listen later DB using identifier 8 | * @param identifier 9 | * Unique id assigned to each book 10 | */ 11 | suspend fun removeBookById(identifier:String) 12 | 13 | /** 14 | * Return list of [ListenLaterItemDomainModel] in last in first out fashion from DB 15 | */ 16 | suspend fun getBooksInLIFO(): ListAudioBook 3 |Feature Book 4 |Download Feature 5 |Swipe up to open player 6 |Third-party Licenses 7 |Thanks for granting permission 8 |This Feature need Storage Permission 9 |Dismiss 10 |Please connect to internet 11 |Playing 12 |continue %s from where you left, 13 |Chapter : %s 14 |Listen 15 |You are offline 16 |Can\'t navigate to Player 17 |17 | 18 | /** 19 | * Return list of [ListenLaterItemDomainModel] in fist in first out fashion from DB 20 | */ 21 | suspend fun getBooksInFIFO():List 22 | 23 | /** 24 | * Return list of [ListenLaterItemDomainModel] in short playtime first 25 | */ 26 | suspend fun getBooksInOrderOfLength():List 27 | } -------------------------------------------------------------------------------- /feature_book_details/src/test/java/com/allsoftdroid/feature/book_details/utils/FakeSaveInDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.feature.book_details.utils 2 | 3 | import com.allsoftdroid.database.common.SaveInDatabase 4 | import com.allsoftdroid.database.metadataCacheDB.MetadataDao 5 | import com.allsoftdroid.feature.book_details.data.model.AudioBookMetadataDataModel 6 | import timber.log.Timber 7 | 8 | class FakeSaveInDatabase(private val dao : MetadataDao) : SaveInDatabase { 9 | override var mDao: MetadataDao = dao 10 | 11 | private lateinit var mBookList: List 12 | 13 | override fun addData(data: Any): FakeSaveInDatabase { 14 | mBookList = (data as List<*>).filterIsInstance () 15 | 16 | return this 17 | } 18 | 19 | override suspend fun execute() { 20 | 21 | //scan the list and build the new to be inserted in the database 22 | for(element in mBookList){ 23 | val book: AudioBookMetadataDataModel = element 24 | 25 | Timber.i(book.identifier) 26 | Timber.i(book.title) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /feature_book_details/src/androidTest/java/com/allsoftdroid/feature/book_details/presentation/utils/FakeSaveInDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.feature.book_details.presentation.utils 2 | 3 | import com.allsoftdroid.database.common.SaveInDatabase 4 | import com.allsoftdroid.database.metadataCacheDB.MetadataDao 5 | import com.allsoftdroid.feature.book_details.data.model.AudioBookMetadataDataModel 6 | import timber.log.Timber 7 | 8 | class FakeSaveInDatabase(private val dao : MetadataDao) : SaveInDatabase { 9 | override var mDao: MetadataDao = dao 10 | 11 | private lateinit var mBookList: List 12 | 13 | override fun addData(data: Any): FakeSaveInDatabase { 14 | mBookList = (data as List<*>).filterIsInstance () 15 | 16 | return this 17 | } 18 | 19 | override suspend fun execute() { 20 | 21 | //scan the list and build the new to be inserted in the database 22 | for(element in mBookList){ 23 | val book: AudioBookMetadataDataModel = element 24 | 25 | Timber.i(book.identifier) 26 | Timber.i(book.title) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /feature_book_details/src/main/java/com/allsoftdroid/feature/book_details/data/network/service/ArchiveAudioBookMetadataServiceApi.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.feature.book_details.data.network.service 2 | 3 | import com.allsoftdroid.feature.book_details.data.network.Utils 4 | import retrofit2.Call 5 | import retrofit2.Retrofit 6 | import retrofit2.converter.scalars.ScalarsConverterFactory 7 | import retrofit2.http.GET 8 | import retrofit2.http.Path 9 | 10 | /** 11 | * create retrofit instance 12 | */ 13 | private val retrofit = Retrofit.Builder() 14 | .addConverterFactory(ScalarsConverterFactory.create()) 15 | .baseUrl(Utils.MetaData.getBaseURL()) 16 | .build() 17 | 18 | 19 | /** 20 | * API to allow access of the following content type as a json string 21 | */ 22 | interface ArchiveMetadataService{ 23 | 24 | @GET("${Utils.MetaData.PATH}{book_id}") 25 | fun getMetadata(@Path("book_id") bookId:String): Call 26 | } 27 | 28 | 29 | 30 | /** 31 | * API is accessible via this object 32 | */ 33 | internal object ArchiveMetadataApi{ 34 | val RETROFIT_SERVICE: ArchiveMetadataService by lazy { 35 | retrofit.create(ArchiveMetadataService::class.java) 36 | } 37 | } -------------------------------------------------------------------------------- /feature_downloader/src/main/res/layout/layout_empty_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /feature_book_details/src/test/java/com/allsoftdroid/feature/book_details/utils/FakeTrackListRepository.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.feature.book_details.utils 2 | 3 | import com.allsoftdroid.feature.book_details.data.model.TrackFormat 4 | import com.allsoftdroid.feature.book_details.domain.model.AudioBookTrackDomainModel 5 | import com.allsoftdroid.feature.book_details.domain.repository.ITrackListRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.flow 8 | 9 | class FakeTrackListRepository : ITrackListRepository { 10 | 11 | override suspend fun loadTrackListData(format: TrackFormat): Flow18 | 19 | 26 | > { 12 | val tracks = mutableListOf
() 13 | 14 | tracks.add(AudioBookTrackDomainModel(filename = "sample.mp3",title = "sample track",trackNumber = 0,trackAlbum = "Intro", 15 | length = "1",format = "64KB",size = "2MB",trackId = "1")) 16 | 17 | tracks.add(AudioBookTrackDomainModel(filename = "sample2.mp3",title = "sample2 track",trackNumber = 1,trackAlbum = "Intro", 18 | length = "1",format = "64KB",size = "2MB",trackId = "2")) 19 | 20 | return flow { emit(tracks) } 21 | } 22 | } -------------------------------------------------------------------------------- /database/src/main/java/com/allsoftdroid/database/listenLaterDB/ListenLaterDao.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.database.listenLaterDB 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.allsoftdroid.database.listenLaterDB.entity.DatabaseListenLaterEntity 8 | 9 | @Dao 10 | interface ListenLaterDao { 11 | 12 | @Insert(onConflict = OnConflictStrategy.REPLACE) 13 | fun insertForLater(listenLater : DatabaseListenLaterEntity) 14 | 15 | @Query("DELETE FROM ListenLater_Table where book_id = :identifier") 16 | fun removeById(identifier:String):Int 17 | 18 | @Query("SELECT * FROM ListenLater_Table order by timestamp desc") 19 | fun getBooksInLIFO():List 20 | 21 | @Query("SELECT * FROM ListenLater_Table order by timestamp asc") 22 | fun getBooksInFIFO():List 23 | 24 | @Query("SELECT * FROM ListenLater_Table order by play_time") 25 | fun getBooksInOrderOfLength():List 26 | 27 | @Query("SELECT count(*) FROM ListenLater_Table where book_id = :bookId") 28 | fun getListenLaterStatusFor(bookId:String):Int 29 | } -------------------------------------------------------------------------------- /feature_book_details/src/main/java/com/allsoftdroid/feature/book_details/domain/usecase/GetDownloadUsecase.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.feature.book_details.domain.usecase 2 | 3 | import com.allsoftdroid.common.base.extension.Event 4 | import com.allsoftdroid.common.base.store.downloader.DownloadEvent 5 | import com.allsoftdroid.common.base.store.downloader.DownloadEventStore 6 | import com.allsoftdroid.common.base.usecase.BaseUseCase 7 | 8 | 9 | class GetDownloadUsecase(private val downloadEventStore: DownloadEventStore) : 10 | BaseUseCase () { 11 | 12 | public override suspend fun executeUseCase(requestValues: RequestValues?) { 13 | //check network connection 14 | requestValues?.let { 15 | downloadEventStore.publish( 16 | Event(it.downloaderAction) 17 | ) 18 | useCaseCallback?.onSuccess(ResponseValues(Event("Sent"))) 19 | }?:useCaseCallback?.onError(Error("Request is null")) 20 | } 21 | 22 | class RequestValues(val downloaderAction: DownloadEvent) : BaseUseCase.RequestValues 23 | class ResponseValues(val event: Event ) : BaseUseCase.ResponseValues 24 | } -------------------------------------------------------------------------------- /feature_book/src/main/java/com/allsoftdroid/feature_book/presentation/recyclerView/adapter/PaginationListener.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.feature_book.presentation.recyclerView.adapter 2 | 3 | import androidx.recyclerview.widget.LinearLayoutManager 4 | import androidx.recyclerview.widget.RecyclerView 5 | 6 | abstract class PaginationListener(private val layoutManager: LinearLayoutManager, 7 | private val pageSize:Int) : RecyclerView.OnScrollListener() { 8 | 9 | 10 | override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { 11 | super.onScrolled(recyclerView, dx, dy) 12 | 13 | val visibleItemCount = layoutManager.childCount 14 | val totalItemCount = layoutManager.itemCount 15 | val firstVisibleItemPos = layoutManager.findFirstVisibleItemPosition() 16 | 17 | if(!isLoading()){ 18 | if ((visibleItemCount + firstVisibleItemPos) >= totalItemCount 19 | && firstVisibleItemPos >= 0 20 | && totalItemCount >= pageSize) { 21 | loadNext() 22 | } 23 | } 24 | } 25 | 26 | protected abstract fun loadNext() 27 | 28 | abstract fun isLoading(): Boolean 29 | } -------------------------------------------------------------------------------- /feature_book/src/main/res/layout/layout_no_results_found.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "263305683750", 4 | "firebase_url": "https://audiobook-a39db.firebaseio.com", 5 | "project_id": "audiobook-a39db", 6 | "storage_bucket": "audiobook-a39db.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:263305683750:android:6118053ea4f7e6be0017e7", 12 | "android_client_info": { 13 | "package_name": "com.allsoftdroid.audiobook" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "263305683750-mmc79b4dpohl2t7sajpr4nllostmogut.apps.googleusercontent.com", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "AIzaSyAMxyRsA7D-lu6lmm0b95xLF_XaYGa4I_c" 25 | } 26 | ], 27 | "services": { 28 | "appinvite_service": { 29 | "other_platform_oauth_client": [ 30 | { 31 | "client_id": "263305683750-mmc79b4dpohl2t7sajpr4nllostmogut.apps.googleusercontent.com", 32 | "client_type": 3 33 | } 34 | ] 35 | } 36 | } 37 | } 38 | ], 39 | "configuration_version": "1" 40 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 |18 | 19 | 27 | 10 | 11 | -------------------------------------------------------------------------------- /feature_book/src/main/res/layout/nav_header_main.xml: -------------------------------------------------------------------------------- 1 | 2 |18 | 19 | 26 | 13 | 14 | -------------------------------------------------------------------------------- /feature_listen_later_ui/src/main/res/layout/layout_no_books_found.xml: -------------------------------------------------------------------------------- 1 | 2 |20 | 21 | 28 | 9 | 10 | -------------------------------------------------------------------------------- /feature_book/src/main/res/drawable/ic_jellyfish.xml: -------------------------------------------------------------------------------- 1 | 2 |18 | 19 | 28 | 7 | -------------------------------------------------------------------------------- /feature_mybooks/src/main/res/layout/layout_no_books_found_local_storage.xml: -------------------------------------------------------------------------------- 1 | 2 |8 | 9 | 10 | -------------------------------------------------------------------------------- /common/src/main/res/drawable/ic_broken_image.xml: -------------------------------------------------------------------------------- 1 | 17 | 18 |18 | 19 | 28 | 24 | 28 | -------------------------------------------------------------------------------- /feature_audiobook_enhance_details/src/test/java/com/allsoftdroid/audiobook/feature/feature_audiobook_enhance_details/domain/usecase/FakeBookDetailsRepository.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.audiobook.feature.feature_audiobook_enhance_details.domain.usecase 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import com.allsoftdroid.audiobook.feature.feature_audiobook_enhance_details.data.model.BookDetails 6 | import com.allsoftdroid.audiobook.feature.feature_audiobook_enhance_details.domain.network.NetworkResponseListener 7 | import com.allsoftdroid.audiobook.feature.feature_audiobook_enhance_details.domain.repository.IFetchAdditionBookDetailsRepository 8 | 9 | class FakeBookDetailsRepository: IFetchAdditionBookDetailsRepository { 10 | 11 | private val details = MutableLiveData27 | () 12 | 13 | override suspend fun fetchBookDetails(bookUrl: String) { 14 | details.value = BookDetails( 15 | null,"1",bookUrl,"url","","","", emptyList() 16 | ) 17 | } 18 | 19 | fun getBookDetails(): LiveData { 20 | return details 21 | } 22 | 23 | override fun registerNetworkResponse(listener: NetworkResponseListener) { 24 | 25 | } 26 | 27 | override fun unRegisterNetworkResponse() { 28 | 29 | } 30 | } -------------------------------------------------------------------------------- /feature_book_details/src/test/java/com/allsoftdroid/feature/book_details/utils/FakeRemoteMetadataSource.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.feature.book_details.utils 2 | 3 | import com.allsoftdroid.feature.book_details.data.network.service.ArchiveMetadataService 4 | import okhttp3.Request 5 | import retrofit2.Call 6 | import retrofit2.Callback 7 | import retrofit2.Response 8 | 9 | class FakeRemoteMetadataSource: ArchiveMetadataService { 10 | override fun getMetadata(bookId: String): Call { 11 | return object : Call { 12 | override fun enqueue(callback: Callback ) { 13 | 14 | } 15 | 16 | override fun isExecuted(): Boolean { 17 | return true 18 | } 19 | 20 | override fun clone(): Call { 21 | return this 22 | } 23 | 24 | override fun isCanceled(): Boolean { 25 | return false 26 | } 27 | 28 | override fun cancel() { 29 | 30 | } 31 | 32 | override fun execute(): Response { 33 | return Response.success("") 34 | } 35 | 36 | override fun request(): Request { 37 | return Request.Builder().build() 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /common/src/main/java/com/allsoftdroid/common/base/store/audioPlayer/AudioPlayerEvent.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.common.base.store.audioPlayer 2 | 3 | import com.allsoftdroid.common.base.extension.AudioPlayListItem 4 | import com.allsoftdroid.common.base.extension.AudioPlayerEventState 5 | 6 | sealed class AudioPlayerEvent 7 | 8 | //Action Event for the Audio Player 9 | data class Next(val result:AudioPlayerEventState) : AudioPlayerEvent() 10 | data class Previous(val result: AudioPlayerEventState) : AudioPlayerEvent() 11 | data class Play(val result: AudioPlayerEventState) : AudioPlayerEvent() 12 | data class Pause(val result: AudioPlayerEventState) : AudioPlayerEvent() 13 | data class EmptyEvent(val default: AudioPlayerEventState):AudioPlayerEvent() 14 | object Rewind : AudioPlayerEvent() 15 | object Forward : AudioPlayerEvent() 16 | object Buffering : AudioPlayerEvent() 17 | object Finished : AudioPlayerEvent() 18 | 19 | //Details or information event for the player and UI 20 | data class PlaySelectedTrack(val trackList : List ,val bookId:String,val bookName:String, val position:Int) : AudioPlayerEvent() 21 | data class TrackDetails(val trackTitle:String,val bookId: String, val position: Int):AudioPlayerEvent() 22 | 23 | //Player State event 24 | data class AudioPlayerPlayingState(val isReady:Boolean) : AudioPlayerEvent() -------------------------------------------------------------------------------- /feature_audiobook_enhance_details/src/main/java/com/allsoftdroid/audiobook/feature/feature_audiobook_enhance_details/data/network/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.audiobook.feature.feature_audiobook_enhance_details.data.network 2 | 3 | class Utils{ 4 | object Books{ 5 | private const val BASE_URL = "https://librivox.org/" 6 | fun getBaseURL() = BASE_URL 7 | 8 | const val ADVANCED_SEARCH="/advanced_search" 9 | 10 | //Param 11 | const val QUERY_TITLE = "title" 12 | const val QUERY_AUTHOR = "author" 13 | const val QUERY_READER = "reader" 14 | const val QUERY_KEYWORDS = "keywords" 15 | const val QUERY_GENRE_ID = "genre_id" 16 | const val QUERY_STATUS = "status" 17 | const val QUERY_PROJECT_TYPE = "project_type" 18 | const val QUERY_RECORDED_LANGUAGE = "recorded_language" 19 | const val QUERY_SORT_ORDER = "sort_order" 20 | const val QUERY_SEARCH_PAGE = "search_page" 21 | const val QUERY_SEARCH_FORM= "search_form" 22 | const val QUERY_Q="q" 23 | 24 | const val DEFAULT_SORT_ORDER = "catalog_date" 25 | const val DEFAULT_PROJECT_TYPE = "either" 26 | const val DEFAULT_STATUS = "all" 27 | const val DEFAULT_SEARCH_FORM = "advanced" 28 | const val DEFAULT_GENRE_ID = 0 29 | } 30 | } -------------------------------------------------------------------------------- /feature_book/src/main/java/com/allsoftdroid/feature_book/data/network/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.feature_book.data.network 2 | 3 | class Utils { 4 | object Books{ 5 | private const val BASE_URL = "https://archive.org/" 6 | private const val FILTER_MOST_RECENT = "+AND+mediatype%3A(audio)&fl[]=creator,date,identifier,title,addeddate&sort[]=-date&output=json" 7 | private const val QUERY="librivox" 8 | private const val COLLECTIONS="librivoxaudio" 9 | private const val OUTPUT_FIELDS = "fl[]=creator,date,identifier,title&sort[]=-date&output=json" 10 | 11 | const val QUERY_PAGE = "page" 12 | const val QUERY_ROW = "rows" 13 | const val QUERY_SEARCH="q" 14 | 15 | const val DEFAULT_ROW_COUNT = 50 16 | fun getBaseURL() = BASE_URL 17 | 18 | const val BOOKS_COLLECTION_PATH = "/advancedsearch.php?q=($QUERY)+AND+collection%3A($COLLECTIONS)$FILTER_MOST_RECENT" 19 | 20 | const val BOOKS_SEARCH_PATH = "/advancedsearch.php?collection%3A($COLLECTIONS)$FILTER_MOST_RECENT" 21 | 22 | const val LIBREVOX_REPOSITORY_SEARCH = "advancedsearch.php?fl[]=creator,date,identifier,title&sort[]=-date&output=json" 23 | 24 | fun buildQuery(search:String):String = 25 | "($search) AND collection:($COLLECTIONS) AND mediatype:(audio)" 26 | 27 | } 28 | } -------------------------------------------------------------------------------- /feature_book_details/src/androidTest/java/com/allsoftdroid/feature/book_details/presentation/utils/FakeRemoteMetadataSource.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.feature.book_details.presentation.utils 2 | 3 | import com.allsoftdroid.feature.book_details.data.network.service.ArchiveMetadataService 4 | import okhttp3.Request 5 | import retrofit2.Call 6 | import retrofit2.Callback 7 | import retrofit2.Response 8 | 9 | class FakeRemoteMetadataSource: ArchiveMetadataService { 10 | override fun getMetadata(bookId: String): Call { 11 | return object : Call { 12 | override fun enqueue(callback: Callback ) { 13 | 14 | } 15 | 16 | override fun isExecuted(): Boolean { 17 | return true 18 | } 19 | 20 | override fun clone(): Call { 21 | return this 22 | } 23 | 24 | override fun isCanceled(): Boolean { 25 | return false 26 | } 27 | 28 | override fun cancel() { 29 | 30 | } 31 | 32 | override fun execute(): Response { 33 | return Response.success("") 34 | } 35 | 36 | override fun request(): Request { 37 | return Request.Builder().build() 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /common/src/main/java/com/allsoftdroid/common/test/LiveDataTestUtils.kt: -------------------------------------------------------------------------------- 1 | package com.allsoftdroid.common.test 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.Observer 6 | import java.util.concurrent.CountDownLatch 7 | import java.util.concurrent.TimeUnit 8 | import java.util.concurrent.TimeoutException 9 | 10 | 11 | @VisibleForTesting(otherwise = VisibleForTesting.NONE) 12 | fun LiveData .getOrAwaitValue( 13 | time: Long = 2, 14 | timeUnit: TimeUnit = TimeUnit.SECONDS, 15 | afterObserve: () -> Unit = {} 16 | ): T { 17 | var data: T? = null 18 | val latch = CountDownLatch(1) 19 | val observer = object : Observer { 20 | override fun onChanged(o: T?) { 21 | data = o 22 | latch.countDown() 23 | this@getOrAwaitValue.removeObserver(this) 24 | } 25 | } 26 | this.observeForever(observer) 27 | 28 | try { 29 | afterObserve.invoke() 30 | 31 | // Don't wait indefinitely if the LiveData is not set. 32 | if (!latch.await(time, timeUnit)) { 33 | throw TimeoutException("LiveData value was never set.") 34 | } 35 | 36 | } finally { 37 | this.removeObserver(observer) 38 | } 39 | 40 | @Suppress("UNCHECKED_CAST") 41 | return data as T 42 | } --------------------------------------------------------------------------------