mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-07-25 22:18:38 +02:00
Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d91b95320 | ||
|
|
beb790ad17 | ||
|
|
073110376a | ||
|
|
16f6f3b7e9 | ||
|
|
4758f3e557 | ||
|
|
38cc2adf0f | ||
|
|
80fbbfc2ef | ||
|
|
ca45956479 | ||
|
|
3d64c42b14 | ||
|
|
97e7a12883 | ||
|
|
e4d2b646e7 | ||
|
|
a5a8f3b6ac | ||
|
|
41b0535213 | ||
|
|
a146a157b8 | ||
|
|
96fff469fc | ||
|
|
a60ff00f7e | ||
|
|
d52c326123 | ||
|
|
76882780a4 | ||
|
|
97eb19da86 | ||
|
|
4ed9d702e9 |
@@ -21,7 +21,11 @@ kotlin {
|
||||
}
|
||||
|
||||
android {
|
||||
kotlinOptions {
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
kotlinOptions {
|
||||
freeCompilerArgs = ['-Xjvm-default=all']
|
||||
}
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
@@ -29,8 +33,8 @@ android {
|
||||
applicationId "com.audiobookshelf.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 84
|
||||
versionName "0.9.53-beta"
|
||||
versionCode 86
|
||||
versionName "0.9.55-beta"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -1,83 +1,94 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:dist="http://schemas.android.com/apk/distribution"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:installLocation="preferExternal"
|
||||
package="com.audiobookshelf.app">
|
||||
xmlns:dist="http://schemas.android.com/apk/distribution"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.audiobookshelf.app"
|
||||
android:installLocation="preferExternal" >
|
||||
|
||||
<!-- Permissions -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
android:usesCleartextTraffic="true" >
|
||||
<!-- <receiver-->
|
||||
<!-- android:name=".NewAppWidget"-->
|
||||
<!-- android:exported="true" >-->
|
||||
<!-- <intent-filter>-->
|
||||
<!-- <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />-->
|
||||
<!-- </intent-filter>-->
|
||||
|
||||
<!-- <meta-data-->
|
||||
<!-- android:name="android.appwidget.provider"-->
|
||||
<!-- android:resource="@xml/new_app_widget_info" />-->
|
||||
<!-- </receiver>-->
|
||||
|
||||
<!--Used by Android Auto-->
|
||||
<meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
|
||||
android:resource="@drawable/icon" />
|
||||
<!-- Used by Android Auto -->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.car.application"
|
||||
android:resource="@xml/automotive_app_desc"/>
|
||||
|
||||
<!-- Support for Cast -->
|
||||
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||
android:value="com.audiobookshelf.app.CastOptionsProvider"/>
|
||||
android:name="com.google.android.gms.car.notification.SmallIcon"
|
||||
android:resource="@drawable/icon" />
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.car.application"
|
||||
android:resource="@xml/automotive_app_desc" /> <!-- Support for Cast -->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||
android:value="com.audiobookshelf.app.CastOptionsProvider" />
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||
android:name="com.audiobookshelf.app.MainActivity"
|
||||
android:label="@string/title_activity_main"
|
||||
android:exported="true"
|
||||
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||
android:launchMode="singleTask">
|
||||
android:label="@string/title_activity_main"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/AppTheme.NoActionBarLaunch" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Register URL scheme -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="@string/custom_url_scheme" />
|
||||
</intent-filter>
|
||||
<!-- Register URL scheme -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="@string/custom_url_scheme" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:exported="true"
|
||||
android:enabled="true"
|
||||
android:name=".player.PlayerNotificationService">
|
||||
<intent-filter>
|
||||
<action android:name="android.media.browse.MediaBrowserService"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".player.PlayerNotificationService"
|
||||
android:enabled="true"
|
||||
android:exported="true" >
|
||||
<intent-filter>
|
||||
<action android:name="android.media.browse.MediaBrowserService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.audiobookshelf.app
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_PAUSE
|
||||
import android.util.Log
|
||||
import android.widget.RemoteViews
|
||||
import androidx.media.session.MediaButtonReceiver.buildMediaButtonPendingIntent
|
||||
import com.audiobookshelf.app.device.DeviceManager
|
||||
import com.audiobookshelf.app.device.WidgetEventEmitter
|
||||
import com.audiobookshelf.app.player.PlayerNotificationService
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.bumptech.glide.request.target.AppWidgetTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
|
||||
/**
|
||||
* Implementation of App Widget functionality.
|
||||
*/
|
||||
class NewAppWidget : AppWidgetProvider() {
|
||||
val tag = "NewAppWidget"
|
||||
|
||||
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
||||
// There may be multiple widgets active, so update all of them
|
||||
for (appWidgetId in appWidgetIds) {
|
||||
updateAppWidget(context, appWidgetManager, appWidgetId, null,false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEnabled(context: Context) {
|
||||
Log.w(tag, "onEnabled check context ${context.packageName}")
|
||||
|
||||
// Enter relevant functionality for when the first widget is created
|
||||
DeviceManager.widgetUpdater = (object : WidgetEventEmitter {
|
||||
override fun onPlayerChanged(pns:PlayerNotificationService) {
|
||||
val isPlaying = pns.currentPlayer.isPlaying
|
||||
Log.i(tag, "onPlayerChanged | Is Playing? $isPlaying")
|
||||
|
||||
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||
val componentName = ComponentName(context, NewAppWidget::class.java)
|
||||
val ids = appWidgetManager.getAppWidgetIds(componentName)
|
||||
|
||||
val playbackSession = pns.getCurrentPlaybackSessionCopy()
|
||||
val cover = playbackSession?.getCoverUri()
|
||||
|
||||
for (widgetId in ids) {
|
||||
updateAppWidget(context, appWidgetManager, widgetId, cover, isPlaying)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onDisabled(context: Context) {
|
||||
// Enter relevant functionality for when the last widget is disabled
|
||||
}
|
||||
}
|
||||
|
||||
internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, coverUri:Uri?, isPlaying:Boolean) {
|
||||
|
||||
val views = RemoteViews(context.packageName, R.layout.new_app_widget)
|
||||
|
||||
val playPausePI = buildMediaButtonPendingIntent(context, ACTION_PLAY_PAUSE)
|
||||
views.setOnClickPendingIntent(R.id.playPauseIcon, playPausePI)
|
||||
|
||||
val wholeWidgetClickI = Intent(context, MainActivity::class.java)
|
||||
wholeWidgetClickI.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
val wholeWidgetClickPI = PendingIntent.getActivity(
|
||||
context,
|
||||
System.currentTimeMillis().toInt(),
|
||||
wholeWidgetClickI,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.appWidget, wholeWidgetClickPI)
|
||||
|
||||
val imageUri = coverUri ?: Uri.parse("android.resource://com.audiobookshelf.app/" + R.drawable.icon)
|
||||
val awt: AppWidgetTarget = object : AppWidgetTarget(context.applicationContext, R.id.imageView, views, appWidgetId) {
|
||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||
super.onResourceReady(resource, transition)
|
||||
}
|
||||
}
|
||||
|
||||
val options = RequestOptions().override(300, 300).placeholder(R.drawable.icon).error(R.drawable.icon)
|
||||
Glide.with(context.applicationContext).asBitmap().load(imageUri).apply(options).into(awt)
|
||||
|
||||
val playPauseResource = if (isPlaying) R.drawable.ic_media_pause_dark else R.drawable.ic_media_play_dark
|
||||
views.setImageViewResource(R.id.playPauseIcon, playPauseResource)
|
||||
|
||||
// Instruct the widget manager to update the widget
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views)
|
||||
}
|
||||
@@ -18,13 +18,19 @@ data class ServerConnectionConfig(
|
||||
|
||||
data class DeviceSettings(
|
||||
var disableAutoRewind:Boolean,
|
||||
var enableAltView:Boolean,
|
||||
var jumpBackwardsTime:Int,
|
||||
var jumpForwardTime:Int
|
||||
) {
|
||||
companion object {
|
||||
// Static method to get default device settings
|
||||
fun default():DeviceSettings {
|
||||
return DeviceSettings(false, 10, 10)
|
||||
return DeviceSettings(
|
||||
disableAutoRewind = false,
|
||||
enableAltView = false,
|
||||
jumpBackwardsTime = 10,
|
||||
jumpForwardTime = 10
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.audiobookshelf.app.device
|
||||
|
||||
import android.util.Log
|
||||
import com.audiobookshelf.app.data.DbManager
|
||||
import com.audiobookshelf.app.data.DeviceData
|
||||
import com.audiobookshelf.app.data.ServerConnectionConfig
|
||||
import com.audiobookshelf.app.data.*
|
||||
import com.audiobookshelf.app.player.PlayerNotificationService
|
||||
|
||||
interface WidgetEventEmitter {
|
||||
fun onPlayerChanged(pns:PlayerNotificationService)
|
||||
}
|
||||
|
||||
object DeviceManager {
|
||||
const val tag = "DeviceManager"
|
||||
@@ -19,6 +22,8 @@ object DeviceManager {
|
||||
val isConnectedToServer get() = serverConnectionConfig != null
|
||||
val hasLastServerConnectionConfig get() = deviceData.getLastServerConnectionConfig() != null
|
||||
|
||||
var widgetUpdater:WidgetEventEmitter? = null
|
||||
|
||||
init {
|
||||
Log.d(tag, "Device Manager Singleton invoked")
|
||||
}
|
||||
|
||||
@@ -199,6 +199,7 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) {
|
||||
if (result) {
|
||||
hasValidConn = true
|
||||
DeviceManager.serverConnectionConfig = config
|
||||
Log.d(tag, "checkSetValidServerConnectionConfig: Set server connection config ${DeviceManager.serverConnectionConfigId}")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
|
||||
currentLocalMediaProgress = null
|
||||
listeningTimerTask?.cancel()
|
||||
lastSyncTime = 0L
|
||||
Log.d(tag, "start: Set last sync time 0 $lastSyncTime")
|
||||
failedSyncs = 0
|
||||
} else {
|
||||
return
|
||||
@@ -53,6 +54,7 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
|
||||
|
||||
listeningTimerRunning = true
|
||||
lastSyncTime = System.currentTimeMillis()
|
||||
Log.d(tag, "start: init last sync time $lastSyncTime")
|
||||
currentPlaybackSession = playerNotificationService.getCurrentPlaybackSessionCopy()
|
||||
|
||||
listeningTimerTask = Timer("ListeningTimer", false).schedule(0L, 5000L) {
|
||||
@@ -62,8 +64,10 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
|
||||
val shouldSyncServer = PlayerNotificationService.isUnmeteredNetwork || System.currentTimeMillis() - lastSyncTime >= METERED_CONNECTION_SYNC_INTERVAL
|
||||
|
||||
val currentTime = playerNotificationService.getCurrentTimeSeconds()
|
||||
sync(shouldSyncServer, currentTime) {
|
||||
Log.d(tag, "Sync complete")
|
||||
if (currentTime > 0) {
|
||||
sync(shouldSyncServer, currentTime) {
|
||||
Log.d(tag, "Sync complete")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,10 +76,18 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
|
||||
|
||||
fun stop(cb: () -> Unit) {
|
||||
if (!listeningTimerRunning) return
|
||||
listeningTimerTask?.cancel()
|
||||
listeningTimerTask = null
|
||||
listeningTimerRunning = false
|
||||
Log.d(tag, "stop: Stopping listening for $currentDisplayTitle")
|
||||
|
||||
val currentTime = playerNotificationService.getCurrentTimeSeconds()
|
||||
sync(true, currentTime) {
|
||||
if (currentTime > 0) { // Current time should always be > 0 on stop
|
||||
sync(true, currentTime) {
|
||||
reset()
|
||||
cb()
|
||||
}
|
||||
} else {
|
||||
reset()
|
||||
cb()
|
||||
}
|
||||
@@ -83,18 +95,27 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
|
||||
|
||||
fun pause(cb: () -> Unit) {
|
||||
if (!listeningTimerRunning) return
|
||||
listeningTimerTask?.cancel()
|
||||
listeningTimerTask = null
|
||||
listeningTimerRunning = false
|
||||
Log.d(tag, "pause: Pausing progress syncer for $currentDisplayTitle")
|
||||
Log.d(tag, "pause: Last sync time $lastSyncTime")
|
||||
|
||||
val currentTime = playerNotificationService.getCurrentTimeSeconds()
|
||||
sync(true, currentTime) {
|
||||
listeningTimerTask?.cancel()
|
||||
listeningTimerTask = null
|
||||
listeningTimerRunning = false
|
||||
if (currentTime > 0) { // Current time should always be > 0 on pause
|
||||
sync(true, currentTime) {
|
||||
lastSyncTime = 0L
|
||||
Log.d(tag, "pause: Set last sync time 0 $lastSyncTime")
|
||||
failedSyncs = 0
|
||||
cb()
|
||||
}
|
||||
} else {
|
||||
lastSyncTime = 0L
|
||||
Log.d(tag, "pause: Set last sync time 0 $lastSyncTime (current time < 0)")
|
||||
failedSyncs = 0
|
||||
|
||||
cb()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun syncFromServerProgress(mediaProgress: MediaProgress) {
|
||||
@@ -107,6 +128,11 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
|
||||
}
|
||||
|
||||
fun sync(shouldSyncServer:Boolean, currentTime:Double, cb: () -> Unit) {
|
||||
if (lastSyncTime <= 0) {
|
||||
Log.e(tag, "Last sync time is not set $lastSyncTime")
|
||||
return
|
||||
}
|
||||
|
||||
val diffSinceLastSync = System.currentTimeMillis() - lastSyncTime
|
||||
if (diffSinceLastSync < 1000L) {
|
||||
return cb()
|
||||
@@ -201,12 +227,10 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
listeningTimerTask?.cancel()
|
||||
listeningTimerTask = null
|
||||
listeningTimerRunning = false
|
||||
currentPlaybackSession = null
|
||||
currentLocalMediaProgress = null
|
||||
lastSyncTime = 0L
|
||||
Log.d(tag, "reset: Set last sync time 0 $lastSyncTime")
|
||||
failedSyncs = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,8 +132,25 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi
|
||||
}
|
||||
|
||||
fun handleCallMediaButton(intent: Intent): Boolean {
|
||||
Log.w(tag, "handleCallMediaButton $intent | ${intent.action}")
|
||||
|
||||
if(Intent.ACTION_MEDIA_BUTTON == intent.action) {
|
||||
val keyEvent = intent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
|
||||
Log.d(tag, "handleCallMediaButton keyEvent = $keyEvent | action ${keyEvent?.action}")
|
||||
|
||||
if (keyEvent?.action == KeyEvent.ACTION_DOWN) {
|
||||
Log.d(tag, "handleCallMediaButton: key action_down for ${keyEvent.keyCode}")
|
||||
when (keyEvent.keyCode) {
|
||||
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
|
||||
Log.d(tag, "handleCallMediaButton: Media Play/Pause")
|
||||
if (playerNotificationService.mPlayer.isPlaying) {
|
||||
playerNotificationService.pause()
|
||||
} else {
|
||||
playerNotificationService.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (keyEvent?.action == KeyEvent.ACTION_UP) {
|
||||
Log.d(tag, "handleCallMediaButton: key action_up for ${keyEvent.keyCode}")
|
||||
|
||||
@@ -118,6 +118,8 @@ class PlayerListener(var playerNotificationService:PlayerNotificationService) :
|
||||
}
|
||||
|
||||
playerNotificationService.clientEventEmitter?.onPlayingUpdate(player.isPlaying)
|
||||
|
||||
DeviceManager.widgetUpdater?.onPlayerChanged(playerNotificationService)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -210,6 +210,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
.apply {
|
||||
setSessionActivity(sessionActivityPendingIntent)
|
||||
isActive = true
|
||||
setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
|
||||
}
|
||||
|
||||
val mediaController = MediaControllerCompat(ctx, mediaSession.sessionToken)
|
||||
@@ -768,7 +769,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
private val ANDROID_WEARABLE_PKG_NAME = "com.google.android.wearable.app"
|
||||
private val ANDROID_GSEARCH_PKG_NAME = "com.google.android.googlequicksearchbox"
|
||||
private val ANDROID_AUTOMOTIVE_PKG_NAME = "com.google.android.carassistant"
|
||||
private val VALID_MEDIA_BROWSERS = mutableListOf<String>(ANDROID_AUTO_PKG_NAME, ANDROID_AUTO_SIMULATOR_PKG_NAME, ANDROID_WEARABLE_PKG_NAME, ANDROID_GSEARCH_PKG_NAME, ANDROID_AUTOMOTIVE_PKG_NAME)
|
||||
private val VALID_MEDIA_BROWSERS = mutableListOf("com.audiobookshelf.app", ANDROID_AUTO_PKG_NAME, ANDROID_AUTO_SIMULATOR_PKG_NAME, ANDROID_WEARABLE_PKG_NAME, ANDROID_GSEARCH_PKG_NAME, ANDROID_AUTOMOTIVE_PKG_NAME)
|
||||
|
||||
private val AUTO_MEDIA_ROOT = "/"
|
||||
private val ALL_ROOT = "__ALL__"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Background for widgets to make the rounded corners based on the
|
||||
appWidgetRadius attribute value
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<corners android:radius="?attr/appWidgetRadius" />
|
||||
<solid android:color="?android:attr/colorBackground" />
|
||||
</shape>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Background for views inside widgets to make the rounded corners based on the
|
||||
appWidgetInnerRadius attribute value
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<corners android:radius="?attr/appWidgetInnerRadius" />
|
||||
<solid android:color="?android:attr/colorPrimaryDark" />
|
||||
</shape>
|
||||
@@ -0,0 +1,26 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/Widget.Android.AppWidget.Container"
|
||||
android:id="@+id/appWidget"
|
||||
android:theme="@style/AppTheme.AppWidgetContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerInParent="true"
|
||||
android:adjustViewBounds="true"
|
||||
android:alpha="0.75"
|
||||
android:contentDescription="Cover image"
|
||||
android:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/playPauseIcon"
|
||||
android:layout_width="71dp"
|
||||
android:layout_height="55dp"
|
||||
android:layout_centerInParent="true"
|
||||
app:srcCompat="@drawable/cast_ic_expanded_controller_play" />
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,11 @@
|
||||
<resources>
|
||||
<style name="Widget.Android.AppWidget.Container" parent="android:Widget">
|
||||
<item name="android:padding">?attr/appWidgetPadding</item>
|
||||
<item name="android:background">@drawable/app_widget_background</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Android.AppWidget.InnerView" parent="android:Widget">
|
||||
<item name="android:padding">?attr/appWidgetPadding</item>
|
||||
<item name="android:background">@drawable/app_widget_inner_view_background</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<resources>
|
||||
<declare-styleable name="AppWidgetAttrs">
|
||||
<attr name="appWidgetPadding" format="dimension" />
|
||||
<attr name="appWidgetInnerRadius" format="dimension" />
|
||||
<attr name="appWidgetRadius" format="dimension" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
@@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<color name="light_blue_50">#FFE1F5FE</color>
|
||||
<color name="light_blue_200">#FF81D4FA</color>
|
||||
<color name="light_blue_600">#FF039BE5</color>
|
||||
<color name="light_blue_900">#FF01579B</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Refer to App Widget Documentation for margin information
|
||||
http://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout
|
||||
-->
|
||||
<dimen name="widget_margin">0dp</dimen>
|
||||
|
||||
</resources>
|
||||
@@ -4,4 +4,6 @@
|
||||
<string name="title_activity_main">audiobookshelf</string>
|
||||
<string name="package_name">com.audiobookshelf.app</string>
|
||||
<string name="custom_url_scheme">com.audiobookshelf.app</string>
|
||||
<string name="add_widget">Add widget</string>
|
||||
<string name="app_widget_description">Simple widget for audiobookshelf playback</string>
|
||||
</resources>
|
||||
|
||||
@@ -19,4 +19,13 @@
|
||||
<style name="AppTheme.NoActionBarLaunch" parent="AppTheme.NoActionBar">
|
||||
<item name="android:background">@drawable/screen</item>
|
||||
</style>
|
||||
<style name="Widget.Android.AppWidget.Container" parent="android:Widget">
|
||||
<item name="android:id">@android:id/background</item>
|
||||
<item name="android:background">?android:attr/colorBackground</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Android.AppWidget.InnerView" parent="android:Widget">
|
||||
<item name="android:background">?android:attr/colorBackground</item>
|
||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<resources>
|
||||
<style name="AppTheme.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault">
|
||||
<!-- Radius of the outer bound of widgets to make the rounded corners -->
|
||||
<item name="appWidgetRadius">16dp</item>
|
||||
<!--
|
||||
Radius of the inner view's bound of widgets to make the rounded corners.
|
||||
It needs to be 8dp or less than the value of appWidgetRadius
|
||||
-->
|
||||
<item name="appWidgetInnerRadius">8dp</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.AppWidgetContainer"
|
||||
parent="AppTheme.AppWidgetContainerParent">
|
||||
<!-- Apply padding to avoid the content of the widget colliding with the rounded corners -->
|
||||
<item name="appWidgetPadding">16dp</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:minWidth="110dp"
|
||||
android:minHeight="40dp"
|
||||
android:updatePeriodMillis="86400000"
|
||||
android:previewImage="@drawable/example_appwidget_preview"
|
||||
android:initialLayout="@layout/new_app_widget"
|
||||
android:description="@string/app_widget_description"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:widgetCategory="home_screen"
|
||||
android:initialKeyguardLayout="@layout/new_app_widget"
|
||||
/>
|
||||
@@ -63,6 +63,12 @@ body {
|
||||
box-shadow: 2px 10px 8px #1111117e;
|
||||
}
|
||||
|
||||
.altBookshelfDivider {
|
||||
background: rgb(38 38 38);
|
||||
/*background: linear-gradient(180deg, rgba(191, 193, 195, 1) 0%, rgb(156, 158, 159) 17%, rgb(114, 115, 117) 88%, rgb(120, 120, 122) 100%);*/
|
||||
box-shadow: 2px 10px 8px #1111117e;
|
||||
}
|
||||
|
||||
/*
|
||||
Bookshelf Label
|
||||
*/
|
||||
@@ -78,6 +84,14 @@ Bookshelf Label
|
||||
color: #fce3a6;
|
||||
}
|
||||
|
||||
.altBookshelfLabel {
|
||||
background-color: #2d3436;
|
||||
background-image: linear-gradient(315deg, #19191a 0%, rgb(15, 15, 15) 74%);
|
||||
border-color: rgb(255, 255, 255);
|
||||
border-style: solid;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.cover-bg {
|
||||
width: calc(100% + 40px);
|
||||
height: calc(100% + 40px);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div id="bookshelf" class="w-full max-w-full h-full">
|
||||
<template v-for="shelf in totalShelves">
|
||||
<div :key="shelf" class="w-full px-2 relative" :class="showBookshelfListView ? '' : 'bookshelfRow'" :id="`shelf-${shelf - 1}`" :style="{ height: shelfHeight + 'px' }">
|
||||
<div v-if="!showBookshelfListView" class="bookshelfDivider w-full absolute bottom-0 left-0 z-30" style="min-height: 16px" :class="`h-${shelfDividerHeightIndex}`" />
|
||||
<div v-else class="flex border-t border-white border-opacity-10" />
|
||||
<div :key="shelf" class="w-full px-2 relative" :class="showBookshelfListView || altViewEnabled ? '' : 'bookshelfRow'" :id="`shelf-${shelf - 1}`" :style="{ height: shelfHeight + 'px' }">
|
||||
<div v-if="!showBookshelfListView && !altViewEnabled" class="w-full absolute bottom-0 left-0 z-30 bookshelfDivider" style="min-height: 16px" :class="`h-${shelfDividerHeightIndex}`" />
|
||||
<div v-else-if="showBookshelfListView" class="flex border-t border-white border-opacity-10" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -119,12 +119,23 @@ export default {
|
||||
},
|
||||
shelfHeight() {
|
||||
if (this.showBookshelfListView) return this.entityHeight + 16
|
||||
if (this.altViewEnabled) {
|
||||
var extraTitleSpace = this.isBookEntity ? 80 : 40
|
||||
return this.entityHeight + extraTitleSpace * this.sizeMultiplier
|
||||
}
|
||||
return this.entityHeight + 40
|
||||
},
|
||||
totalEntityCardWidth() {
|
||||
if (this.showBookshelfListView) return this.entityWidth
|
||||
// Includes margin
|
||||
return this.entityWidth + 24
|
||||
},
|
||||
altViewEnabled() {
|
||||
return this.$store.getters['getAltViewEnabled']
|
||||
},
|
||||
sizeMultiplier() {
|
||||
var baseSize = this.isCoverSquareAspectRatio ? 192 : 120
|
||||
return this.entityWidth / baseSize
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
<template>
|
||||
<div class="w-full relative">
|
||||
<div class="bookshelfRow flex items-end px-3 max-w-full overflow-x-auto" :style="{ height: shelfHeight + 'px' }">
|
||||
<div v-if="altViewEnabled" class="px-5 pb-3 pt-4">
|
||||
<p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ label }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-end px-3 max-w-full overflow-x-auto" :class="altViewEnabled ? '' : 'bookshelfRow'" :style="{ height: shelfHeight + 'px', paddingBottom: entityPaddingBottom + 'px' }">
|
||||
<template v-for="(entity, index) in entities">
|
||||
<cards-lazy-book-card v-if="type === 'book' || type === 'podcast'" :key="entity.id" :index="index" :book-mount="entity" :width="bookWidth" :height="entityHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" is-categorized class="mx-2 relative" />
|
||||
<cards-lazy-book-card v-if="type === 'episode'" :key="entity.recentEpisode.id" :index="index" :book-mount="entity" :width="bookWidth" :height="entityHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" is-categorized class="mx-2 relative" />
|
||||
<cards-lazy-series-card v-else-if="type === 'series'" :key="entity.id" :index="index" :series-mount="entity" :width="bookWidth * 2" :height="entityHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" is-categorized class="mx-2 relative" />
|
||||
<cards-lazy-book-card v-if="type === 'book' || type === 'podcast'" :key="entity.id" :index="index" :book-mount="entity" :width="bookWidth" :height="entityHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" :is-alt-view-enabled="altViewEnabled" class="mx-2 relative" />
|
||||
<cards-lazy-book-card v-if="type === 'episode'" :key="entity.recentEpisode.id" :index="index" :book-mount="entity" :width="bookWidth" :height="entityHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" :is-alt-view-enabled="altViewEnabled" class="mx-2 relative" />
|
||||
<cards-lazy-series-card v-else-if="type === 'series'" :key="entity.id" :index="index" :series-mount="entity" :width="bookWidth * 2" :height="entityHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" :is-alt-view-enabled="altViewEnabled" is-categorized class="mx-2 relative" />
|
||||
<cards-author-card v-else-if="type === 'authors'" :key="entity.id" :width="bookWidth / 1.25" :height="bookWidth" :author="entity" :size-multiplier="1" class="mx-2" />
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="absolute text-center categoryPlacard font-book transform z-30 bottom-0.5 left-4 md:left-8 w-36 rounded-md" style="height: 18px">
|
||||
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border">
|
||||
<div v-if="!altViewEnabled" class="absolute text-center categoryPlacard font-book transform z-30 bottom-0.5 left-4 md:left-8 w-36 rounded-md" style="height: 18px">
|
||||
<div class="w-full h-full flex items-center justify-center rounded-sm border shinyBlack">
|
||||
<p class="transform text-xs">{{ label }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full h-5 z-40 bookshelfDivider"></div>
|
||||
<div v-if="!altViewEnabled" class="w-full h-5 z-40 bookshelfDivider"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -32,23 +36,43 @@ export default {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
entityPaddingBottom() {
|
||||
if (!this.altViewEnabled) return 0
|
||||
if (this.type === 'authors') return 10
|
||||
else if (this.type === 'series') return 40
|
||||
return 60 * this.sizeMultiplier
|
||||
},
|
||||
shelfHeight() {
|
||||
if (this.altViewEnabled) {
|
||||
var extraTitleSpace = this.type === 'authors' ? 10 : this.type === 'series' ? 50 : 60
|
||||
return this.entityHeight + extraTitleSpace * this.sizeMultiplier
|
||||
}
|
||||
return this.entityHeight + 40
|
||||
},
|
||||
bookWidth() {
|
||||
var coverSize = 100
|
||||
if (this.bookCoverAspectRatio === 1) return coverSize * 1.6
|
||||
if (this.isCoverSquareAspectRatio) return coverSize * 1.6
|
||||
return coverSize
|
||||
},
|
||||
bookHeight() {
|
||||
if (this.bookCoverAspectRatio === 1) return this.bookWidth
|
||||
if (this.isCoverSquareAspectRatio) return this.bookWidth
|
||||
return this.bookWidth * 1.6
|
||||
},
|
||||
entityHeight() {
|
||||
return this.bookHeight
|
||||
},
|
||||
sizeMultiplier() {
|
||||
var baseSize = this.isCoverSquareAspectRatio ? 192 : 120
|
||||
return this.bookWidth / baseSize
|
||||
},
|
||||
isCoverSquareAspectRatio() {
|
||||
return this.bookCoverAspectRatio === 1
|
||||
},
|
||||
bookCoverAspectRatio() {
|
||||
return this.$store.getters['getBookCoverAspectRatio']
|
||||
},
|
||||
altViewEnabled() {
|
||||
return this.$store.getters['getAltViewEnabled']
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
</div>
|
||||
|
||||
<!-- Alternative bookshelf title/author/sort -->
|
||||
<!-- <div v-if="isAlternativeBookshelfView" class="absolute left-0 z-50 w-full" :style="{ bottom: `-${titleDisplayBottomOffset}rem` }">
|
||||
<div v-if="isAltViewEnabled" class="absolute left-0 z-50 w-full" :style="{ bottom: `-${titleDisplayBottomOffset}rem` }">
|
||||
<p class="truncate" :style="{ fontSize: 0.9 * sizeMultiplier + 'rem' }">
|
||||
{{ displayTitle }}
|
||||
</p>
|
||||
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displayAuthor }}</p>
|
||||
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displayAuthor || ' ' }}</p>
|
||||
<p v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div v-if="booksInSeries" class="absolute z-20 top-1.5 right-1.5 rounded-md leading-3 text-sm p-1 font-semibold text-white flex items-center justify-center" style="background-color: #cd9d49dd">{{ booksInSeries }}</div>
|
||||
|
||||
@@ -84,7 +84,7 @@ export default {
|
||||
},
|
||||
bookCoverAspectRatio: Number,
|
||||
showSequence: Boolean,
|
||||
bookshelfView: Number,
|
||||
isAltViewEnabled: Boolean,
|
||||
bookMount: {
|
||||
// Book can be passed as prop or set with setEntity()
|
||||
type: Object,
|
||||
@@ -346,6 +346,11 @@ export default {
|
||||
return this.author.slice(0, 27) + '...'
|
||||
}
|
||||
return this.author
|
||||
},
|
||||
titleDisplayBottomOffset() {
|
||||
if (!this.isAltViewEnabled) return 0
|
||||
else if (!this.displaySortLine) return 3 * this.sizeMultiplier
|
||||
return 4.25 * this.sizeMultiplier
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<covers-collection-cover ref="cover" :book-items="books" :width="width" :height="height" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
</div>
|
||||
|
||||
<div class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md font-book text-center" :style="{ width: Math.min(160, width) + 'px' }">
|
||||
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }">
|
||||
<div class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md font-book text-center" :style="{ width: Math.min(240, width) + 'px' }">
|
||||
<div class="w-full h-full flex items-center justify-center rounded-sm border" :class="isAltViewEnabled ? 'altBookshelfLabel' : 'shinyBlack'" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }">
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -19,7 +19,8 @@ export default {
|
||||
index: Number,
|
||||
width: Number,
|
||||
height: Number,
|
||||
bookCoverAspectRatio: Number
|
||||
bookCoverAspectRatio: Number,
|
||||
isAltViewEnabled: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
<covers-group-cover v-if="series" ref="cover" :id="seriesId" :name="title" :book-items="books" :width="width" :height="height" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
</div>
|
||||
|
||||
<div v-if="!isCategorized" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md font-book text-center" :style="{ width: Math.min(160, width) + 'px' }">
|
||||
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }">
|
||||
<div v-if="isAltViewEnabled && isCategorized" class="absolute z-30 left-0 right-0 mx-auto -bottom-8 h-8 py-1 rounded-md text-center">
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ title }}</p>
|
||||
</div>
|
||||
<div v-if="!isCategorized" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md font-book text-center" :style="{ width: Math.min(240, width) + 'px' }">
|
||||
<div class="w-full h-full flex items-center justify-center rounded-sm border" :class="isAltViewEnabled ? 'altBookshelfLabel' : 'shinyBlack'" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }">
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -24,6 +27,7 @@ export default {
|
||||
type: Object,
|
||||
default: () => null
|
||||
},
|
||||
isAltViewEnabled: Boolean,
|
||||
isCategorized: Boolean
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -479,12 +479,12 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 11;
|
||||
CURRENT_PROJECT_VERSION = 12;
|
||||
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 0.9.51;
|
||||
MARKETING_VERSION = 0.9.55;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app.dev;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -503,12 +503,12 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 11;
|
||||
CURRENT_PROJECT_VERSION = 12;
|
||||
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 0.9.51;
|
||||
MARKETING_VERSION = 0.9.55;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import UIKit
|
||||
import Capacitor
|
||||
import RealmSwift
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
@@ -8,6 +9,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
|
||||
let configuration = Realm.Configuration(
|
||||
schemaVersion: 1,
|
||||
migrationBlock: { migration, oldSchemaVersion in
|
||||
if (oldSchemaVersion < 1) {
|
||||
NSLog("Realm schema version was \(oldSchemaVersion)")
|
||||
migration.enumerateObjects(ofType: DeviceSettings.className()) { oldObject, newObject in
|
||||
newObject?["enableAltView"] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
Realm.Configuration.defaultConfiguration = configuration
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ CAP_PLUGIN(AbsDatabase, "AbsDatabase",
|
||||
CAP_PLUGIN_METHOD(getLocalLibraryItem, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(getLocalLibraryItemByLId, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(getLocalLibraryItemsInFolder, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(getAllLocalMediaProgress, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(updateDeviceSettings, CAPPluginReturnPromise);
|
||||
)
|
||||
|
||||
|
||||
@@ -86,12 +86,17 @@ public class AbsDatabase: CAPPlugin {
|
||||
@objc func getLocalLibraryItemsInFolder(_ call: CAPPluginCall) {
|
||||
call.resolve([ "value": [] ])
|
||||
}
|
||||
@objc func getAllLocalMediaProgress(_ call: CAPPluginCall) {
|
||||
call.resolve([ "value": [] ])
|
||||
}
|
||||
@objc func updateDeviceSettings(_ call: CAPPluginCall) {
|
||||
let disableAutoRewind = call.getBool("disableAutoRewind") ?? false
|
||||
let enableAltView = call.getBool("enableAltView") ?? false
|
||||
let jumpBackwardsTime = call.getInt("jumpBackwardsTime") ?? 10
|
||||
let jumpForwardTime = call.getInt("jumpForwardTime") ?? 10
|
||||
let settings = DeviceSettings()
|
||||
settings.disableAutoRewind = disableAutoRewind
|
||||
settings.enableAltView = enableAltView
|
||||
settings.jumpBackwardsTime = jumpBackwardsTime
|
||||
settings.jumpForwardTime = jumpForwardTime
|
||||
|
||||
|
||||
+6
-6
@@ -9,12 +9,12 @@ install! 'cocoapods', :disable_input_output_paths => true
|
||||
def capacitor_pods
|
||||
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||
pod 'CapacitorApp', :path => '..\..\node_modules\@capacitor\app'
|
||||
pod 'CapacitorDialog', :path => '..\..\node_modules\@capacitor\dialog'
|
||||
pod 'CapacitorHaptics', :path => '..\..\node_modules\@capacitor\haptics'
|
||||
pod 'CapacitorNetwork', :path => '..\..\node_modules\@capacitor\network'
|
||||
pod 'CapacitorStatusBar', :path => '..\..\node_modules\@capacitor\status-bar'
|
||||
pod 'CapacitorStorage', :path => '..\..\node_modules\@capacitor\storage'
|
||||
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
|
||||
pod 'CapacitorDialog', :path => '../../node_modules/@capacitor/dialog'
|
||||
pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics'
|
||||
pod 'CapacitorNetwork', :path => '../../node_modules/@capacitor/network'
|
||||
pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
|
||||
pod 'CapacitorStorage', :path => '../../node_modules/@capacitor/storage'
|
||||
end
|
||||
|
||||
target 'App' do
|
||||
|
||||
@@ -10,6 +10,7 @@ import RealmSwift
|
||||
|
||||
class DeviceSettings: Object {
|
||||
@Persisted var disableAutoRewind: Bool
|
||||
@Persisted var enableAltView: Bool
|
||||
@Persisted var jumpBackwardsTime: Int
|
||||
@Persisted var jumpForwardTime: Int
|
||||
}
|
||||
@@ -17,6 +18,7 @@ class DeviceSettings: Object {
|
||||
func getDefaultDeviceSettings() -> DeviceSettings {
|
||||
let settings = DeviceSettings()
|
||||
settings.disableAutoRewind = false
|
||||
settings.enableAltView = false
|
||||
settings.jumpForwardTime = 10
|
||||
settings.jumpBackwardsTime = 10
|
||||
return settings
|
||||
@@ -26,6 +28,7 @@ func deviceSettingsToJSON(settings: DeviceSettings) -> Dictionary<String, Any> {
|
||||
return Database.realmQueue.sync {
|
||||
return [
|
||||
"disableAutoRewind": settings.disableAutoRewind,
|
||||
"enableAltView": settings.enableAltView,
|
||||
"jumpBackwardsTime": settings.jumpBackwardsTime,
|
||||
"jumpForwardTime": settings.jumpForwardTime
|
||||
]
|
||||
|
||||
@@ -11,6 +11,7 @@ class PlayerHandler {
|
||||
private static var player: AudioPlayer?
|
||||
private static var session: PlaybackSession?
|
||||
private static var timer: Timer?
|
||||
private static var lastSyncTime:Double = 0.0
|
||||
|
||||
private static var _remainingSleepTime: Int? = nil
|
||||
public static var remainingSleepTime: Int? {
|
||||
@@ -128,7 +129,7 @@ class PlayerHandler {
|
||||
listeningTimePassedSinceLastSync += 1
|
||||
}
|
||||
|
||||
if listeningTimePassedSinceLastSync > 3 {
|
||||
if listeningTimePassedSinceLastSync >= 5 {
|
||||
syncProgress()
|
||||
}
|
||||
|
||||
@@ -149,6 +150,15 @@ class PlayerHandler {
|
||||
return
|
||||
}
|
||||
|
||||
// Prevent multiple sync requests
|
||||
let timeSinceLastSync = Date().timeIntervalSince1970 - lastSyncTime
|
||||
if (lastSyncTime > 0 && timeSinceLastSync < 1) {
|
||||
NSLog("syncProgress last sync time was < 1 second so not syncing")
|
||||
return
|
||||
}
|
||||
|
||||
lastSyncTime = Date().timeIntervalSince1970 // seconds
|
||||
|
||||
let report = PlaybackReport(currentTime: playerCurrentTime, duration: player.getDuration(), timeListened: listeningTimePassedSinceLastSync)
|
||||
|
||||
session!.currentTime = playerCurrentTime
|
||||
|
||||
+6
-3
@@ -109,7 +109,7 @@ export default {
|
||||
|
||||
console.log(`[default] Got server config, attempt authorize ${serverConfig.address}`)
|
||||
|
||||
var authRes = await this.$axios.$post(`${serverConfig.address}/api/authorize`, null, { headers: { Authorization: `Bearer ${serverConfig.token}` } }).catch((error) => {
|
||||
var authRes = await this.$axios.$post(`${serverConfig.address}/api/authorize`, null, { headers: { Authorization: `Bearer ${serverConfig.token}` }, timeout: 3000 }).catch((error) => {
|
||||
console.error('[Server] Server auth failed', error)
|
||||
var errorMsg = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error'
|
||||
this.error = errorMsg
|
||||
@@ -120,7 +120,8 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
const { user, userDefaultLibraryId } = authRes
|
||||
const { user, userDefaultLibraryId, serverSettings } = authRes
|
||||
this.$store.commit('setServerSettings', serverSettings)
|
||||
|
||||
// Set library - Use last library if set and available fallback to default user library
|
||||
var lastLibraryId = await this.$localStore.getLastLibraryId()
|
||||
@@ -263,6 +264,8 @@ export default {
|
||||
|
||||
await this.$store.dispatch('setupNetworkListener')
|
||||
|
||||
await this.$store.dispatch('globals/loadLocalMediaProgress')
|
||||
|
||||
if (this.$store.state.user.serverConnectionConfig) {
|
||||
console.log(`[default] server connection config set - call init libraries`)
|
||||
await this.initLibraries()
|
||||
@@ -273,7 +276,7 @@ export default {
|
||||
|
||||
console.log(`[default] finished connection attempt or already connected ${!!this.user}`)
|
||||
await this.syncLocalMediaProgress()
|
||||
this.$store.dispatch('globals/loadLocalMediaProgress')
|
||||
|
||||
this.loadSavedSettings()
|
||||
this.hasMounted = true
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ export default {
|
||||
index,
|
||||
width: this.entityWidth,
|
||||
height: this.entityHeight,
|
||||
bookCoverAspectRatio: this.bookCoverAspectRatio
|
||||
bookCoverAspectRatio: this.bookCoverAspectRatio,
|
||||
isAltViewEnabled: this.altViewEnabled
|
||||
}
|
||||
if (this.entityName === 'series-books') props.showSequence = true
|
||||
if (this.entityName === 'books') {
|
||||
@@ -54,7 +55,7 @@ export default {
|
||||
props.sortingIgnorePrefix = !!this.sortingIgnorePrefix
|
||||
}
|
||||
|
||||
var _this = this
|
||||
// var _this = this
|
||||
var instance = new ComponentClass({
|
||||
propsData: props,
|
||||
created() {
|
||||
|
||||
Generated
+3
-2
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "audiobookshelf-app",
|
||||
"version": "0.9.53-beta",
|
||||
"version": "0.9.55-beta",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "0.9.44-beta",
|
||||
"name": "audiobookshelf-app",
|
||||
"version": "0.9.55-beta",
|
||||
"dependencies": {
|
||||
"@capacitor/android": "^3.4.3",
|
||||
"@capacitor/app": "^1.1.1",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf-app",
|
||||
"version": "0.9.53-beta",
|
||||
"version": "0.9.55-beta",
|
||||
"author": "advplyr",
|
||||
"scripts": {
|
||||
"dev": "nuxt --hostname 0.0.0.0 --port 1337",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="w-full h-full min-h-full relative">
|
||||
<div v-if="!loading" class="w-full">
|
||||
<div v-if="!loading" class="w-full" :class="{ 'py-6': altViewEnabled }">
|
||||
<template v-for="(shelf, index) in shelves">
|
||||
<bookshelf-shelf :key="shelf.id" :label="shelf.label" :entities="shelf.entities" :type="shelf.type" :style="{ zIndex: shelves.length - index }" />
|
||||
</template>
|
||||
@@ -53,6 +53,9 @@ export default {
|
||||
},
|
||||
currentLibraryId() {
|
||||
return this.$store.state.libraries.currentLibraryId
|
||||
},
|
||||
altViewEnabled() {
|
||||
return this.$store.getters['getAltViewEnabled']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<div class="w-full h-full p-8">
|
||||
<div class="flex items-center py-3" @click="toggleEnableAltView">
|
||||
<div class="w-10 flex justify-center">
|
||||
<ui-toggle-switch v-model="settings.enableAltView" @input="saveSettings" />
|
||||
</div>
|
||||
<p class="pl-4">Alternative Bookshelf View</p>
|
||||
</div>
|
||||
<div v-if="$platform !== 'ios'" class="flex items-center py-3" @click="toggleDisableAutoRewind">
|
||||
<div class="w-10 flex justify-center">
|
||||
<ui-toggle-switch v-model="settings.disableAutoRewind" @input="saveSettings" />
|
||||
@@ -28,6 +34,7 @@ export default {
|
||||
deviceData: null,
|
||||
settings: {
|
||||
disableAutoRewind: false,
|
||||
enableAltView: false,
|
||||
jumpForwardTime: 10,
|
||||
jumpBackwardsTime: 10
|
||||
}
|
||||
@@ -60,6 +67,10 @@ export default {
|
||||
this.settings.disableAutoRewind = !this.settings.disableAutoRewind
|
||||
this.saveSettings()
|
||||
},
|
||||
toggleEnableAltView() {
|
||||
this.settings.enableAltView = !this.settings.enableAltView
|
||||
this.saveSettings()
|
||||
},
|
||||
toggleJumpForward() {
|
||||
var next = (this.currentJumpForwardTimeIndex + 1) % 3
|
||||
this.settings.jumpForwardTime = this.jumpForwardItems[next].value
|
||||
@@ -85,6 +96,7 @@ export default {
|
||||
|
||||
const deviceSettings = this.deviceData.deviceSettings || {}
|
||||
this.settings.disableAutoRewind = !!deviceSettings.disableAutoRewind
|
||||
this.settings.enableAltView = !!deviceSettings.enableAltView
|
||||
this.settings.jumpForwardTime = deviceSettings.jumpForwardTime || 10
|
||||
this.settings.jumpBackwardsTime = deviceSettings.jumpBackwardsTime || 10
|
||||
}
|
||||
|
||||
@@ -47,6 +47,10 @@ export const getters = {
|
||||
getJumpBackwardsTime: state => {
|
||||
if (!state.deviceData || !state.deviceData.deviceSettings) return 10
|
||||
return state.deviceData.deviceSettings.jumpBackwardsTime || 10
|
||||
},
|
||||
getAltViewEnabled: state => {
|
||||
if (!state.deviceData || !state.deviceData.deviceSettings) return false
|
||||
return state.deviceData.deviceSettings.enableAltView
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user