Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21ed78d959 | ||
|
|
5ad99b9f27 | ||
|
|
3de5390140 | ||
|
|
d215581e19 | ||
|
|
54612c6b74 | ||
|
|
eaac728a26 | ||
|
|
65d2f8e33f | ||
|
|
baf4e0f0fc | ||
|
|
26c7bee106 | ||
|
|
6e51611867 | ||
|
|
d67e432731 | ||
|
|
8b61396b0f | ||
|
|
0fb6e55b12 | ||
|
|
dd7aa2291b | ||
|
|
ec33c32c89 | ||
|
|
e0ad4e3701 | ||
|
|
253f8033c5 | ||
|
|
c1aed1a4c1 | ||
|
|
23f58439ba | ||
|
|
4c99ced597 | ||
|
|
8d215a7f1c | ||
|
|
38fc4a0936 | ||
|
|
d9949349da | ||
|
|
877d29d285 | ||
|
|
9a17aa8b98 | ||
|
|
fd41395ab8 | ||
|
|
269066e036 | ||
|
|
488460ea9d | ||
|
|
d16a9c234f | ||
|
|
07b507691c | ||
|
|
bde34d3df0 | ||
|
|
e5b7756f96 | ||
|
|
04e692e5e9 | ||
|
|
a23a663d32 | ||
|
|
023bd8071a | ||
|
|
72b1517f61 | ||
|
|
e62ea72c2f | ||
|
|
a24ccf2556 | ||
|
|
49838e2e0f | ||
|
|
8ed1248ee1 | ||
|
|
e9d54957ae | ||
|
|
3cd5843c4b | ||
|
|
75513d3bd4 | ||
|
|
c0c84269ef | ||
|
|
fa2e029f9f | ||
|
|
f1bfb095b7 | ||
|
|
4328415efc | ||
|
|
092ae14ea2 | ||
|
|
26af8a692f | ||
|
|
b870f4c866 | ||
|
|
cf4e78eafc | ||
|
|
83e23c44d9 |
61
CHANGELOG.md
61
CHANGELOG.md
@@ -1,5 +1,66 @@
|
||||
# Changelog
|
||||
|
||||
## Pending release
|
||||
|
||||
## What's Changed
|
||||
* fix: Addressing some UI/UX quirks by @tiltshiftfocus in https://github.com/eddyizm/tempus/pull/413
|
||||
* fix: keep observer until data is received on continuousPlay bug by @eddyizm in https://github.com/eddyizm/tempus/pull/421
|
||||
* fix: album art now displays on android auto by @trobinson in https://github.com/eddyizm/tempus/pull/414
|
||||
* feat: improve landscape view and increase items per row on landscape view by @tvillega in https://github.com/eddyizm/tempus/pull/411
|
||||
|
||||
## New Contributors
|
||||
* @tiltshiftfocus made their first contribution in https://github.com/eddyizm/tempus/pull/413
|
||||
* @trobinson made their first contribution in https://github.com/eddyizm/tempus/pull/414
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.9.8...v4.10.0
|
||||
|
||||
## What's Changed
|
||||
## [4.9.8](https://github.com/eddyizm/tempo/releases/tag/v4.9.8) (2026-02-02)
|
||||
* fix: missing Replay Gain metadata from .m4a files by @pgrit in https://github.com/eddyizm/tempus/pull/396
|
||||
* fix: Improve Synced Lyrics by @pgrit in https://github.com/eddyizm/tempus/pull/384
|
||||
* fix: Add selector for playlist visibility by @tvillega in https://github.com/eddyizm/tempus/pull/394
|
||||
* chore(i18n): set links as untranslatable by @tvillega in https://github.com/eddyizm/tempus/pull/400
|
||||
|
||||
## New Contributors
|
||||
* @tvillega made their first contribution in https://github.com/eddyizm/tempus/pull/394
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.9.5...v4.5.8
|
||||
|
||||
## What's Changed
|
||||
## [4.9.5](https://github.com/eddyizm/tempo/releases/tag/v4.9.5) (2026-01-26)
|
||||
* fix: Avoid crash when server has no songs by @jaime-grj in https://github.com/eddyizm/tempus/pull/389
|
||||
* fix: updated dialog import to address crashing on android 15 by @eddyizm in https://github.com/eddyizm/tempus/pull/392
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.9.3...v4.9.5
|
||||
|
||||
## What's Changed
|
||||
## [4.9.3](https://github.com/eddyizm/tempo/releases/tag/v4.9.3) (2026-01-25)
|
||||
* fix: Proper raw stream detection by @jaime-grj in https://github.com/eddyizm/tempus/pull/382
|
||||
* chore(i18n): Update Spanish translation by @jaime-grj in https://github.com/eddyizm/tempus/pull/381
|
||||
* feat: add configurable timeout by @eddyizm in https://github.com/eddyizm/tempus/pull/386
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.9.1...v4.9.3
|
||||
|
||||
## What's Changed
|
||||
## [4.9.1](https://github.com/eddyizm/tempo/releases/tag/v4.9.1) (2026-01-24)
|
||||
* chore: i18n: Add Romanian translation (including locale_config this time!) by @DevMatei in https://github.com/eddyizm/tempus/pull/357
|
||||
* French localization update by @benoit-smith in https://github.com/eddyizm/tempus/pull/356
|
||||
* chore(i18n): Update Spanish translation by @jaime-grj in https://github.com/eddyizm/tempus/pull/364
|
||||
* docs: updated readme and added known issues for airsonic work around by @eddyizm in https://github.com/eddyizm/tempus/pull/366
|
||||
* fix: toast for made for you click indication by @eddyizm in https://github.com/eddyizm/tempus/pull/365
|
||||
* fix: sort playlist view by @eddyizm in https://github.com/eddyizm/tempus/pull/368
|
||||
* feat: sort preference for playlists by @eddyizm in https://github.com/eddyizm/tempus/pull/370
|
||||
* fix: use existing future when adding tracks, dialed random album tracks off in instant mix by @eddyizm in https://github.com/eddyizm/tempus/pull/373
|
||||
* chore(i18n): Update Polish translation by @skajmer in https://github.com/eddyizm/tempus/pull/374
|
||||
* fix: Check for OpenSubsonic extensions also with password authentication by @pgrit in https://github.com/eddyizm/tempus/pull/375
|
||||
* feat: Implement duration and seeking for transcodes by @drakeerv in https://github.com/eddyizm/tempus/pull/358
|
||||
* feat: Playback speed controls for music by @pgrit in https://github.com/eddyizm/tempus/pull/376
|
||||
|
||||
## New Contributors
|
||||
* @pgrit made their first contribution in https://github.com/eddyizm/tempus/pull/375
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.6.4...v4.9.1
|
||||
|
||||
## What's Changed
|
||||
## [4.6.4](https://github.com/eddyizm/tempo/releases/tag/v4.6.4) (2026-01-13)
|
||||
* fix: instant mix random songs and broken continuous play by @eddyizm in https://github.com/eddyizm/tempus/pull/354
|
||||
|
||||
25
README.md
25
README.md
@@ -10,15 +10,21 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
<a href="https://github.com/eddyizm/tempus/releases/">
|
||||
<img alt="Releases" src="https://img.shields.io/github/downloads/eddyizm/tempus/total.svg?color=4B95DE&style=flat">
|
||||
</a>
|
||||
<!-- Reproducible build -->
|
||||
<a href="https://shields.rbtlog.dev/com.eddyizm.degoogled.tempus"><img src="https://shields.rbtlog.dev/simple/com.eddyizm.degoogled.tempus" alt="RB Status"></a>
|
||||
|
||||
<a href="https://www.gnu.org/licenses/gpl-3.0">
|
||||
<img src="https://img.shields.io/badge/license-GPL%20v3-2B6DBE.svg?style=flat">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/eddyizm/tempus/releases"><img src="https://i.ibb.co/q0mdc4Z/get-it-on-github.png" width="200"></a>
|
||||
<a href="https://apt.izzysoft.de/fdroid/index/apk/com.eddyizm.degoogled.tempus"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" width="200"></a>
|
||||
<a href="https://apps.obtainium.imranr.dev/redirect?r=obtainium://app/%7B%22id%22%3A%22com.eddyizm.tempus%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2Feddyizm%2Ftempus%22%2C%22author%22%3A%22eddyizm%22%2C%22name%22%3A%22Tempus%22%2C%22preferredApkIndex%22%3A0%2C%22additionalSettings%22%3A%22%7B%5C%22includePrereleases%5C%22%3Afalse%2C%5C%22fallbackToOlderReleases%5C%22%3Atrue%2C%5C%22filterReleaseTitlesByRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22filterReleaseNotesByRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22verifyLatestTag%5C%22%3Afalse%2C%5C%22sortMethodChoice%5C%22%3A%5C%22date%5C%22%2C%5C%22useLatestAssetDateAsReleaseDate%5C%22%3Afalse%2C%5C%22releaseTitleAsVersion%5C%22%3Afalse%2C%5C%22trackOnly%5C%22%3Afalse%2C%5C%22versionExtractionRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22matchGroupToUse%5C%22%3A%5C%22%5C%22%2C%5C%22versionDetection%5C%22%3Atrue%2C%5C%22releaseDateAsVersion%5C%22%3Afalse%2C%5C%22useVersionCodeAsOSVersion%5C%22%3Afalse%2C%5C%22apkFilterRegEx%5C%22%3A%5C%22tempus%5C%22%2C%5C%22invertAPKFilter%5C%22%3Afalse%2C%5C%22autoApkFilterByArch%5C%22%3Atrue%2C%5C%22appName%5C%22%3A%5C%22%5C%22%2C%5C%22appAuthor%5C%22%3A%5C%22%5C%22%2C%5C%22shizukuPretendToBeGooglePlay%5C%22%3Afalse%2C%5C%22allowInsecure%5C%22%3Afalse%2C%5C%22exemptFromBackgroundUpdates%5C%22%3Afalse%2C%5C%22skipUpdateNotifications%5C%22%3Afalse%2C%5C%22about%5C%22%3A%5C%22%5C%22%2C%5C%22refreshBeforeDownload%5C%22%3Afalse%2C%5C%22includeZips%5C%22%3Afalse%2C%5C%22zippedApkFilterRegEx%5C%22%3A%5C%22%5C%22%7D%22%2C%22overrideSource%22%3A%22GitHub%22%7D"><img width="200" src="https://github.com/user-attachments/assets/119e7ff4-2636-43cb-ab7f-1b6a58ac3570" /></a>
|
||||
<a href="https://www.openapk.net/tempus/com.eddyizm.degoogled.tempus/"><img src="https://camo.githubusercontent.com/cd56895b28a73ebd781a65b4f567add5419e45797a5cf1485ce408e851c2318e/68747470733a2f2f7777772e6f70656e61706b2e6e65742f696d616765732f6f70656e61706b2d62616467652e706e67" width="200"></a>
|
||||
</p>
|
||||
<!--
|
||||
<a href="https://f-droid.org/packages/com.cappielloantonio.notquitemy.tempo"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" width="200"></a>
|
||||
@@ -31,6 +37,10 @@ Tempus does not rely on magic algorithms to decide what you should listen to. In
|
||||
|
||||
The project is a fork of [Tempo](#credits).
|
||||
|
||||
[Changelog](CHANGELOG.md)
|
||||
[Wiki](USAGE.md)
|
||||
[Donate](https://github.com/eddyizm/tempus#donate)
|
||||
|
||||
**If you find Tempus useful, please consider starring the project on GitHub. It would mean a lot to me and help promote the app to a wider audience.**
|
||||
|
||||
**Use the Github version of the app for full Android Auto and Chromecast support.**
|
||||
@@ -46,11 +56,6 @@ Please note the two variants in the release assets include release/debug and 32/
|
||||
|
||||
`app-degoogled*` <- The izzyOnDroid release that goes without any of the google stuff. It is now available on izzyOnDroid (64bit) I am releasing the both 32/64bit apk's here on github for those who need a 32bit version.
|
||||
|
||||
[CHANGELOG.md](CHANGELOG.md)
|
||||
|
||||
## Usage
|
||||
|
||||
[Documentation](USAGE.md) (work in progress)
|
||||
|
||||
## Features
|
||||
- **Subsonic Integration**: Tempus seamlessly integrates with your Subsonic server, providing you with easy access to your entire music collection on the go.
|
||||
@@ -62,7 +67,7 @@ Please note the two variants in the release assets include release/debug and 32/
|
||||
- **Chromecast Support**: Stream your music to Chromecast devices. The support is currently in a rudimentary state.*
|
||||
- **Scrobbling Integration**: Optionally integrate Tempus with Last.fm or Listenbrainz.org to scrobble your played tracks, gather music insights, and further personalize your music recommendations, if supported by your Subsonic server.
|
||||
- **Podcasts and Radio**: If your Subsonic server supports it, listen to podcasts and radio shows directly within Tempus, expanding your audio entertainment options.
|
||||
- **Instant Mix**: Full refactor of instant mix function which leverages subsonics similar songs by artist/album and randomSongs endpoints to server a larger play queue more reliably.
|
||||
- **Instant Mix**: Full refactor of instant mix function which leverages subsonics similarSongs2 by artist/album and similarSongs endpoints to server a larger play queue more reliably.
|
||||
- **Transcoding Support**: Activate transcoding of tracks on your Subsonic server, allowing you to set a transcoding profile for optimized streaming directly from the app. This feature requires support from your Subsonic server.
|
||||
- **Android Auto Support**: Enjoy your favorite music on the go with full Android Auto integration, allowing you to seamlessly control and listen to your tracks directly from your mobile device while driving.*
|
||||
- **Multiple Libraries**: Tempus handles multi-library setups gracefully. They are displayed as Library folders.
|
||||
@@ -115,7 +120,10 @@ Currently there are no tests but I would love to start on some unit tests.
|
||||
|
||||
Not a hard requirement but any new feature/change should ideally include an update to the nacent documention.
|
||||
|
||||
## Support
|
||||
*Special Thanks*
|
||||
All the amazing [contributors](https://github.com/eddyizm/tempus/graphs/contributors)❤️
|
||||
|
||||
## Donate
|
||||
|
||||
[**Buy me a coffee**](https://ko-fi.com/eddyizm)
|
||||
bitcoin: `3QVHSSCJvn6yXEcJ3A3cxYLMmbvFsrnUs5`
|
||||
@@ -124,6 +132,7 @@ bitcoin: `3QVHSSCJvn6yXEcJ3A3cxYLMmbvFsrnUs5`
|
||||
|
||||
Tempus is released under the [GNU General Public License v3.0](LICENSE). Feel free to modify, distribute, and use the app in accordance with the terms of the license. Contributions to the project are also welcome.
|
||||
|
||||
|
||||
## Credits
|
||||
Thanks to the original repo/creator [CappielloAntonio](https://github.com/CappielloAntonio) (forked from v3.9.0)
|
||||
|
||||
|
||||
14
USAGE.md
14
USAGE.md
@@ -6,14 +6,13 @@
|
||||
- [Getting Started](#getting-started)
|
||||
- [Server Configuration](#server-configuration)
|
||||
- [Main Features](#main-features)
|
||||
|
||||
- [Navigation](#navigation)
|
||||
- [Playback Controls](#playback-controls)
|
||||
- [Favorites](#favorites)
|
||||
- [Playlist Management](#playlist-management)
|
||||
- [Android Auto](#android-auto)
|
||||
- [Settings](#settings)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Known Issues](#known-issues)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -193,15 +192,12 @@ To allow the Tempus app on your car's head unit, "Unknown sources" needs to be e
|
||||
### Appearance
|
||||
**TODO**
|
||||
|
||||
## Troubleshooting
|
||||
## Known Issues
|
||||
|
||||
### Connection Issues
|
||||
### Airsonic Distorted Playback
|
||||
|
||||
**TODO**
|
||||
|
||||
### Common Issues
|
||||
|
||||
**TODO**
|
||||
First reported in issue [#226](https://github.com/eddyizm/tempus/issues/226)
|
||||
The work around is to disable the cache in the settings, (set to 0), and if needed, cleaning the (Android) cache fixes the problem.
|
||||
|
||||
### Support
|
||||
For additional help:
|
||||
|
||||
@@ -10,8 +10,8 @@ android {
|
||||
minSdkVersion 24
|
||||
targetSdk 35
|
||||
|
||||
versionCode 13
|
||||
versionName '4.6.4'
|
||||
versionCode 18
|
||||
versionName '4.10.0'
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
javaCompileOptions {
|
||||
|
||||
@@ -96,7 +96,12 @@
|
||||
android:resource="@xml/widget_info"/>
|
||||
</receiver>
|
||||
|
||||
|
||||
<provider
|
||||
android:name=".provider.AlbumArtContentProvider"
|
||||
android:authorities="com.cappielloantonio.tempo.provider"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
/>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.cappielloantonio.tempo.model
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.Keep
|
||||
@@ -13,6 +14,7 @@ import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest
|
||||
import com.cappielloantonio.tempo.provider.AlbumArtContentProvider
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode
|
||||
@@ -197,7 +199,7 @@ class SessionMediaItem() {
|
||||
|
||||
fun getMediaItem(): MediaItem {
|
||||
val uri: Uri = getStreamUri()
|
||||
val artworkUri = Uri.parse(CustomGlideRequest.createUrl(coverArtId, getImageSize()))
|
||||
val artworkUri = AlbumArtContentProvider.contentUri(coverArtId)
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putString("id", id)
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.cappielloantonio.tempo.provider;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.UriMatcher;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class AlbumArtContentProvider extends ContentProvider {
|
||||
public static final String AUTHORITY = "com.cappielloantonio.tempo.provider";
|
||||
public static final String ALBUM_ART = "albumArt";
|
||||
private ExecutorService executor;
|
||||
|
||||
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
|
||||
static {
|
||||
uriMatcher.addURI(AUTHORITY, "albumArt/*", 1);
|
||||
}
|
||||
|
||||
public static Uri contentUri(String artworkId) {
|
||||
return new Uri.Builder()
|
||||
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority(AUTHORITY)
|
||||
.appendPath(ALBUM_ART)
|
||||
.appendPath(artworkId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
|
||||
Context context = getContext();
|
||||
String albumId = uri.getLastPathSegment();
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(albumId, Preferences.getImageSize()));
|
||||
|
||||
try {
|
||||
// use pipe to communicate between background thread and caller of openFile()
|
||||
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
|
||||
ParcelFileDescriptor readSide = pipe[0];
|
||||
ParcelFileDescriptor writeSide = pipe[1];
|
||||
|
||||
// perform loading in background thread to avoid blocking UI
|
||||
executor.execute(() -> {
|
||||
try (OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(writeSide)) {
|
||||
|
||||
// request artwork from API using Glide
|
||||
File file = Glide.with(context)
|
||||
.asFile()
|
||||
.load(artworkUri)
|
||||
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
||||
.submit()
|
||||
.get();
|
||||
|
||||
// copy artwork down pipe returned by ContentProvider
|
||||
try (InputStream in = new FileInputStream(file)) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
writeSide.closeWithError("Failed to load image: " + e.getMessage());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
writeSide.closeWithError("Failed to load image: " + e.getMessage());
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
});
|
||||
|
||||
return readSide;
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException("Could not create pipe: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
executor = Executors.newFixedThreadPool(
|
||||
Math.max(2, Runtime.getRuntime().availableProcessors() / 2)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
if (executor != null) {
|
||||
executor.shutdown();
|
||||
try {
|
||||
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
|
||||
@@ -22,6 +23,7 @@ import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.model.Chronology;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.model.SessionMediaItem;
|
||||
import com.cappielloantonio.tempo.provider.AlbumArtContentProvider;
|
||||
import com.cappielloantonio.tempo.service.DownloaderManager;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
@@ -70,7 +72,7 @@ public class AutomotiveRepository {
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(album.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(album.getName())
|
||||
@@ -217,7 +219,7 @@ public class AutomotiveRepository {
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(album.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(album.getName())
|
||||
@@ -272,7 +274,7 @@ public class AutomotiveRepository {
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (ArtistID3 artist : artists) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(artist.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(artist.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(artist.getName())
|
||||
@@ -397,7 +399,7 @@ public class AutomotiveRepository {
|
||||
List<Child> children = response.body().getSubsonicResponse().getIndexes().getChildren();
|
||||
|
||||
for (Child song : children) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(song.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(song.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(song.getTitle())
|
||||
@@ -451,7 +453,7 @@ public class AutomotiveRepository {
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (Child child : directory.getChildren()) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(child.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(child.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(child.getTitle())
|
||||
@@ -550,7 +552,7 @@ public class AutomotiveRepository {
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (PodcastEpisode episode : episodes) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(episode.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(episode.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(episode.getTitle())
|
||||
@@ -687,7 +689,7 @@ public class AutomotiveRepository {
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(album.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(album.getName())
|
||||
@@ -800,7 +802,7 @@ public class AutomotiveRepository {
|
||||
|
||||
if (response.body().getSubsonicResponse().getSearchResult3().getArtists() != null) {
|
||||
for (ArtistID3 artist : response.body().getSubsonicResponse().getSearchResult3().getArtists()) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(artist.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(artist.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(artist.getName())
|
||||
@@ -822,7 +824,7 @@ public class AutomotiveRepository {
|
||||
|
||||
if (response.body().getSubsonicResponse().getSearchResult3().getAlbums() != null) {
|
||||
for (AlbumID3 album : response.body().getSubsonicResponse().getSearchResult3().getAlbums()) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(album.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(album.getName())
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import static android.provider.Settings.System.getString;
|
||||
|
||||
import android.provider.Settings;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -107,13 +104,13 @@ public class PlaylistRepository {
|
||||
return playlistLiveData;
|
||||
}
|
||||
|
||||
public void addSongToPlaylist(String playlistId, ArrayList<String> songsId) {
|
||||
public void addSongToPlaylist(String playlistId, ArrayList<String> songsId, Boolean playlistVisibilityIsPublic) {
|
||||
if (songsId.isEmpty()) {
|
||||
Toast.makeText(App.getContext(), App.getContext().getString(R.string.playlist_chooser_dialog_toast_all_skipped), Toast.LENGTH_SHORT).show();
|
||||
} else{
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
.updatePlaylist(playlistId, null, true, songsId, null)
|
||||
.updatePlaylist(playlistId, null, playlistVisibilityIsPublic, songsId, null)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
@@ -283,7 +283,10 @@ public class SongRepository {
|
||||
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null) {
|
||||
songs.addAll(Objects.requireNonNull(response.body().getSubsonicResponse().getRandomSongs().getSongs()));
|
||||
List<Child> returned = response.body().getSubsonicResponse().getRandomSongs().getSongs();
|
||||
if (returned != null) {
|
||||
songs.addAll(returned);
|
||||
}
|
||||
}
|
||||
randomSongsSample.setValue(songs);
|
||||
}
|
||||
@@ -299,7 +302,10 @@ public class SongRepository {
|
||||
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null) {
|
||||
songs.addAll(Objects.requireNonNull(response.body().getSubsonicResponse().getRandomSongs().getSongs()));
|
||||
List<Child> returned = response.body().getSubsonicResponse().getRandomSongs().getSongs();
|
||||
if (returned != null) {
|
||||
songs.addAll(returned);
|
||||
}
|
||||
}
|
||||
randomSongsSample.setValue(songs);
|
||||
}
|
||||
@@ -342,7 +348,10 @@ public class SongRepository {
|
||||
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSongsByGenre() != null) {
|
||||
songs.addAll(Objects.requireNonNull(response.body().getSubsonicResponse().getSongsByGenre().getSongs()));
|
||||
List<Child> returned = response.body().getSubsonicResponse().getSongsByGenre().getSongs();
|
||||
if (returned != null) {
|
||||
songs.addAll(returned);
|
||||
}
|
||||
}
|
||||
songsByGenre.setValue(songs);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.app.TaskStackBuilder
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.ConnectivityManager
|
||||
@@ -32,6 +33,8 @@ import com.google.common.collect.ImmutableList
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
|
||||
private const val TAG = "BaseMediaService"
|
||||
|
||||
@UnstableApi
|
||||
open class BaseMediaService : MediaLibraryService() {
|
||||
companion object {
|
||||
@@ -81,7 +84,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
}
|
||||
|
||||
fun updateMediaItems(player: Player) {
|
||||
Log.d(javaClass.toString(), "update items")
|
||||
Log.d(TAG, "update items")
|
||||
val n = player.mediaItemCount
|
||||
val k = player.currentMediaItemIndex
|
||||
val current = player.currentPosition
|
||||
@@ -120,7 +123,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
fun initializePlayerListener(player: Player) {
|
||||
player.addListener(object : Player.Listener {
|
||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||
Log.d(javaClass.toString(), "onMediaItemTransition" + player.currentMediaItemIndex)
|
||||
Log.d(TAG, "onMediaItemTransition" + player.currentMediaItemIndex)
|
||||
if (mediaItem == null) return
|
||||
|
||||
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
|
||||
@@ -130,7 +133,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
}
|
||||
|
||||
override fun onTracksChanged(tracks: Tracks) {
|
||||
Log.d(javaClass.toString(), "onTracksChanged " + player.currentMediaItemIndex)
|
||||
Log.d(TAG, "onTracksChanged " + player.currentMediaItemIndex)
|
||||
ReplayGainUtil.setReplayGain(player, tracks)
|
||||
val currentMediaItem = player.currentMediaItem
|
||||
if (currentMediaItem != null) {
|
||||
@@ -138,14 +141,19 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
if (item.mediaMetadata.extras != null)
|
||||
MediaManager.scrobble(item, false)
|
||||
|
||||
if (player.nextMediaItemIndex == C.INDEX_UNSET)
|
||||
MediaManager.continuousPlay(player.currentMediaItem)
|
||||
if (player.nextMediaItemIndex == C.INDEX_UNSET) {
|
||||
val browserFuture = MediaBrowser.Builder(
|
||||
this@BaseMediaService,
|
||||
SessionToken(this@BaseMediaService, ComponentName(this@BaseMediaService, this@BaseMediaService::class.java))
|
||||
).buildAsync()
|
||||
MediaManager.continuousPlay(player.currentMediaItem, browserFuture)
|
||||
}
|
||||
}
|
||||
|
||||
if (player is ExoPlayer) {
|
||||
// https://stackoverflow.com/questions/56937283/exoplayer-shuffle-doesnt-reproduce-all-the-songs
|
||||
if (MediaManager.justStarted.get()) {
|
||||
Log.d(javaClass.toString(), "update shuffle order")
|
||||
Log.d(TAG, "update shuffle order")
|
||||
MediaManager.justStarted.set(false)
|
||||
val shuffledList = IntArray(player.mediaItemCount) { i -> i }
|
||||
shuffledList.shuffle()
|
||||
@@ -163,7 +171,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
}
|
||||
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
Log.d(javaClass.toString(), "onIsPlayingChanged " + player.currentMediaItemIndex)
|
||||
Log.d(TAG, "onIsPlayingChanged " + player.currentMediaItemIndex)
|
||||
if (!isPlaying) {
|
||||
MediaManager.setPlayingPausedTimestamp(
|
||||
player.currentMediaItem,
|
||||
@@ -181,7 +189,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
}
|
||||
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
Log.d(javaClass.toString(), "onPlaybackStateChanged")
|
||||
Log.d(TAG, "onPlaybackStateChanged")
|
||||
super.onPlaybackStateChanged(playbackState)
|
||||
if (!player.hasNextMediaItem() &&
|
||||
playbackState == Player.STATE_ENDED &&
|
||||
@@ -198,7 +206,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
newPosition: Player.PositionInfo,
|
||||
reason: Int
|
||||
) {
|
||||
Log.d(javaClass.toString(), "onPositionDiscontinuity")
|
||||
Log.d(TAG, "onPositionDiscontinuity")
|
||||
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
|
||||
|
||||
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
|
||||
@@ -222,7 +230,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
}
|
||||
|
||||
override fun onAudioSessionIdChanged(audioSessionId: Int) {
|
||||
Log.d(javaClass.toString(), "onAudioSessionIdChanged")
|
||||
Log.d(TAG, "onAudioSessionIdChanged")
|
||||
attachEqualizerIfPossible(audioSessionId)
|
||||
}
|
||||
})
|
||||
@@ -314,7 +322,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
}
|
||||
|
||||
private fun initializeMediaLibrarySession(player: Player) {
|
||||
Log.d(javaClass.toString(), "initializeMediaLibrarySession")
|
||||
Log.d(TAG, "initializeMediaLibrarySession")
|
||||
val sessionActivityPendingIntent =
|
||||
TaskStackBuilder.create(this).run {
|
||||
addNextIntent(Intent(baseContext, MainActivity::class.java))
|
||||
@@ -461,7 +469,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
customCommand: SessionCommand,
|
||||
args: Bundle
|
||||
): ListenableFuture<SessionResult> {
|
||||
Log.d(javaClass.toString(), "onCustomCommand")
|
||||
Log.d(TAG, "onCustomCommand")
|
||||
when (customCommand.customAction) {
|
||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON -> session.player.shuffleModeEnabled = true
|
||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF -> session.player.shuffleModeEnabled = false
|
||||
@@ -486,7 +494,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
controller: ControllerInfo,
|
||||
mediaItems: List<MediaItem>
|
||||
): ListenableFuture<List<MediaItem>> {
|
||||
Log.d(javaClass.toString(), "onAddMediaItems")
|
||||
Log.d(TAG, "onAddMediaItems")
|
||||
val updatedMediaItems = mediaItems.map { mediaItem ->
|
||||
val mediaMetadata = mediaItem.mediaMetadata
|
||||
val newMetadata = mediaMetadata.buildUpon()
|
||||
|
||||
@@ -444,29 +444,33 @@ public class MediaManager {
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public static void continuousPlay(MediaItem mediaItem) {
|
||||
if (mediaItem != null && Preferences.isContinuousPlayEnabled() && Preferences.isInstantMixUsable()) {
|
||||
Preferences.setLastInstantMix();
|
||||
|
||||
LiveData<List<Child>> instantMix = getSongRepository().getContinuousMix(mediaItem.mediaId,25);
|
||||
|
||||
instantMix.observeForever(new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> media) {
|
||||
if (media != null) {
|
||||
Log.e(TAG, "continuous play");
|
||||
ListenableFuture<MediaBrowser> mediaBrowserListenableFuture = new MediaBrowser.Builder(
|
||||
App.getContext(),
|
||||
new SessionToken(App.getContext(), new ComponentName(App.getContext(), MediaService.class))
|
||||
).buildAsync();
|
||||
|
||||
enqueue(mediaBrowserListenableFuture, media, true);
|
||||
}
|
||||
|
||||
instantMix.removeObserver(this);
|
||||
}
|
||||
});
|
||||
public static void continuousPlay(MediaItem mediaItem,
|
||||
ListenableFuture<MediaBrowser> existingBrowserFuture) {
|
||||
if (mediaItem == null
|
||||
|| !Preferences.isContinuousPlayEnabled()
|
||||
|| !Preferences.isInstantMixUsable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Preferences.setLastInstantMix();
|
||||
|
||||
LiveData<List<Child>> instantMix =
|
||||
getSongRepository().getContinuousMix(mediaItem.mediaId, 25);
|
||||
|
||||
instantMix.observeForever(new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> media) {
|
||||
if (media == null || media.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingBrowserFuture != null) {
|
||||
Log.d(TAG, "Continuous play: adding " + media.size() + " tracks");
|
||||
enqueue(existingBrowserFuture, media, true);
|
||||
}
|
||||
instantMix.removeObserver(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void saveChronology(MediaItem mediaItem) {
|
||||
|
||||
@@ -24,13 +24,15 @@ public class SystemClient {
|
||||
|
||||
public Call<ApiResponse> ping() {
|
||||
Log.d(TAG, "ping()");
|
||||
int timeoutSeconds = Preferences.getNetworkPingTimeout();
|
||||
Call<ApiResponse> pingCall = systemService.ping(subsonic.getParams());
|
||||
if (Preferences.isInUseServerAddressLocal()) {
|
||||
pingCall.timeout()
|
||||
.timeout(1, TimeUnit.SECONDS);
|
||||
.timeout(timeoutSeconds, TimeUnit.SECONDS);
|
||||
} else {
|
||||
int finalTimeout = Math.min(timeoutSeconds * 2, 10);
|
||||
pingCall.timeout()
|
||||
.timeout(3, TimeUnit.SECONDS);
|
||||
.timeout(finalTimeout, TimeUnit.SECONDS);
|
||||
}
|
||||
return pingCall;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.cappielloantonio.tempo.ui.activity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
@@ -11,6 +13,7 @@ import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.splashscreen.SplashScreen;
|
||||
@@ -62,6 +65,7 @@ public class MainActivity extends BaseActivity {
|
||||
private BottomNavigationView bottomNavigationView;
|
||||
public NavController navController;
|
||||
private BottomSheetBehavior bottomSheetBehavior;
|
||||
private boolean isLandscape = false;
|
||||
private AssetLinkNavigator assetLinkNavigator;
|
||||
private AssetLinkUtil.AssetLink pendingAssetLink;
|
||||
|
||||
@@ -85,6 +89,8 @@ public class MainActivity extends BaseActivity {
|
||||
connectivityStatusBroadcastReceiver = new ConnectivityStatusBroadcastReceiver(this);
|
||||
connectivityStatusReceiverManager(true);
|
||||
|
||||
isLandscape = (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
|
||||
|
||||
init();
|
||||
checkConnectionType();
|
||||
getOpenSubsonicExtensions();
|
||||
@@ -141,6 +147,15 @@ public class MainActivity extends BaseActivity {
|
||||
} else {
|
||||
goToLogin();
|
||||
}
|
||||
|
||||
// Set bottom navigation height
|
||||
if (isLandscape) {
|
||||
ViewGroup.LayoutParams layoutParams = bottomNavigationView.getLayoutParams();
|
||||
Rect windowRect = new Rect();
|
||||
bottomNavigationView.getWindowVisibleDisplayFrame(windowRect);
|
||||
layoutParams.width = windowRect.height();
|
||||
bottomNavigationView.setLayoutParams(layoutParams);
|
||||
}
|
||||
}
|
||||
|
||||
// BOTTOM SHEET/NAVIGATION
|
||||
@@ -215,7 +230,9 @@ public class MainActivity extends BaseActivity {
|
||||
@Override
|
||||
public void onSlide(@NonNull View view, float slideOffset) {
|
||||
animateBottomSheet(slideOffset);
|
||||
animateBottomNavigation(slideOffset, navigationHeight);
|
||||
if (!isLandscape) {
|
||||
animateBottomNavigation(slideOffset, navigationHeight);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -354,7 +371,7 @@ public class MainActivity extends BaseActivity {
|
||||
|
||||
// TODO Enter all settings to be reset
|
||||
Preferences.setOpenSubsonic(false);
|
||||
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_100);
|
||||
Preferences.setPlaybackSpeed(1.0f);
|
||||
Preferences.setSkipSilenceMode(false);
|
||||
Preferences.setDataSavingMode(false);
|
||||
Preferences.setStarredSyncEnabled(false);
|
||||
@@ -384,7 +401,7 @@ public class MainActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
private void pingServer() {
|
||||
if (Preferences.getToken() == null) return;
|
||||
if (Preferences.getToken() == null && Preferences.getPassword() == null) return;
|
||||
|
||||
if (Preferences.isInUseServerAddressLocal()) {
|
||||
mainViewModel.ping().observe(this, subsonicResponse -> {
|
||||
@@ -428,7 +445,7 @@ public class MainActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
private void getOpenSubsonicExtensions() {
|
||||
if (Preferences.getToken() != null) {
|
||||
if (Preferences.getToken() != null || Preferences.getPassword() != null) {
|
||||
mainViewModel.getOpenSubsonicExtensions().observe(this, openSubsonicExtensions -> {
|
||||
if (openSubsonicExtensions != null) {
|
||||
Preferences.setOpenSubsonicExtensions(openSubsonicExtensions);
|
||||
|
||||
@@ -173,10 +173,12 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
)
|
||||
)
|
||||
) {
|
||||
holder.item.differentDiskDividerSector.setVisibility(View.VISIBLE);
|
||||
|
||||
if (songs.get(position).getDiscNumber() != null && !Objects.requireNonNull(songs.get(position).getDiscNumber()).toString().isBlank()) {
|
||||
holder.item.discTitleTextView.setText(holder.itemView.getContext().getString(R.string.disc_titleless, songs.get(position).getDiscNumber().toString()));
|
||||
holder.item.differentDiskDividerSector.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.item.differentDiskDividerSector.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (album.getDiscTitles() != null) {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
public class PlaybackSpeedDialog extends DialogFragment {
|
||||
private static final String TAG = "PlaybackSpeedDialog";
|
||||
|
||||
public interface PlaybackSpeedListener {
|
||||
void onSpeedSelected(float speed);
|
||||
}
|
||||
|
||||
private PlaybackSpeedListener listener;
|
||||
|
||||
private static final float[] SPEED_VALUES = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f};
|
||||
private static final String[] SPEED_LABELS = {"0.5x", "0.75x", "1.0x", "1.25x", "1.5x", "1.75x", "2.0x"};
|
||||
|
||||
public void setPlaybackSpeedListener(PlaybackSpeedListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
float currentSpeed = Preferences.getPlaybackSpeed();
|
||||
int selectedIndex = getSelectedIndex(currentSpeed);
|
||||
|
||||
return new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.playback_speed_dialog_title)
|
||||
.setSingleChoiceItems(SPEED_LABELS, selectedIndex, (dialog, which) -> {
|
||||
float selectedSpeed = SPEED_VALUES[which];
|
||||
Preferences.setPlaybackSpeed(selectedSpeed);
|
||||
if (listener != null) {
|
||||
listener.onSpeedSelected(selectedSpeed);
|
||||
}
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(R.string.playback_speed_dialog_negative_button, (dialog, id) -> dialog.cancel())
|
||||
.create();
|
||||
}
|
||||
|
||||
private int getSelectedIndex(float currentSpeed) {
|
||||
for (int i = 0; i < SPEED_VALUES.length; i++) {
|
||||
if (Math.abs(SPEED_VALUES[i] - currentSpeed) < 0.01f) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 2; // Default to 1.0x
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
@@ -20,6 +21,7 @@ import com.cappielloantonio.tempo.viewmodel.PlaylistChooserViewModel;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class PlaylistChooserDialog extends DialogFragment implements ClickCallback {
|
||||
private DialogPlaylistChooserBinding bind;
|
||||
@@ -35,9 +37,21 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
|
||||
|
||||
playlistChooserViewModel = new ViewModelProvider(requireActivity()).get(PlaylistChooserViewModel.class);
|
||||
|
||||
String[] playlistVisibilityChoice = {
|
||||
getString(R.string.playlist_chooser_dialog_visibility_public),
|
||||
getString(R.string.playlist_chooser_dialog_visibility_private)
|
||||
};
|
||||
|
||||
return new MaterialAlertDialogBuilder(getActivity())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.playlist_chooser_dialog_title)
|
||||
.setSingleChoiceItems(
|
||||
playlistVisibilityChoice,
|
||||
0,
|
||||
(dialog, which) -> {
|
||||
boolean isPublic = (which == 0);
|
||||
playlistChooserViewModel.setIsPlaylistPublic(isPublic);
|
||||
})
|
||||
.setNeutralButton(R.string.playlist_chooser_dialog_neutral_button, (dialog, id) -> { })
|
||||
.setNegativeButton(R.string.playlist_chooser_dialog_negative_button, (dialog, id) -> dialog.cancel())
|
||||
.create();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -49,6 +50,7 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
|
||||
private AlbumCatalogueViewModel albumCatalogueViewModel;
|
||||
|
||||
private AlbumCatalogueAdapter albumAdapter;
|
||||
private int spanCount = 2;
|
||||
private String currentSortOrder;
|
||||
private List<com.cappielloantonio.tempo.subsonic.models.AlbumID3> originalAlbums;
|
||||
|
||||
@@ -90,6 +92,10 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
|
||||
bind = FragmentAlbumCatalogueBinding.inflate(inflater, container, false);
|
||||
View view = bind.getRoot();
|
||||
|
||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
spanCount = Preferences.getLandscapeItemsPerRow();
|
||||
}
|
||||
|
||||
initAppBar();
|
||||
initAlbumCatalogueView();
|
||||
initProgressLoader();
|
||||
@@ -133,8 +139,8 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initAlbumCatalogueView() {
|
||||
bind.albumCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
|
||||
bind.albumCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
|
||||
bind.albumCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), spanCount));
|
||||
bind.albumCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(spanCount, 20, false));
|
||||
bind.albumCatalogueRecyclerView.setHasFixedSize(true);
|
||||
|
||||
albumAdapter = new AlbumCatalogueAdapter(this, true);
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -50,6 +51,7 @@ public class ArtistCatalogueFragment extends Fragment implements ClickCallback {
|
||||
private ArtistCatalogueViewModel artistCatalogueViewModel;
|
||||
|
||||
private ArtistCatalogueAdapter artistAdapter;
|
||||
private int spanCount = 2;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
@@ -66,6 +68,10 @@ public class ArtistCatalogueFragment extends Fragment implements ClickCallback {
|
||||
bind = FragmentArtistCatalogueBinding.inflate(inflater, container, false);
|
||||
View view = bind.getRoot();
|
||||
|
||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
spanCount = Preferences.getLandscapeItemsPerRow();
|
||||
}
|
||||
|
||||
initAppBar();
|
||||
initArtistCatalogueView();
|
||||
|
||||
@@ -108,8 +114,8 @@ public class ArtistCatalogueFragment extends Fragment implements ClickCallback {
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initArtistCatalogueView() {
|
||||
bind.artistCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
|
||||
bind.artistCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
|
||||
bind.artistCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), spanCount));
|
||||
bind.artistCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(spanCount, 20, false));
|
||||
bind.artistCatalogueRecyclerView.setHasFixedSize(true);
|
||||
|
||||
artistAdapter = new ArtistCatalogueAdapter(this);
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@@ -63,6 +64,8 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
private int spanCount = 2;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
activity = (MainActivity) getActivity();
|
||||
@@ -72,6 +75,10 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
artistPageViewModel = new ViewModelProvider(requireActivity()).get(ArtistPageViewModel.class);
|
||||
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||
|
||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
spanCount = Preferences.getLandscapeItemsPerRow();
|
||||
}
|
||||
|
||||
init(view);
|
||||
initAppBar();
|
||||
initArtistInfo();
|
||||
@@ -277,8 +284,8 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
private void initAlbumsView() {
|
||||
bind.albumsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
|
||||
bind.albumsRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
|
||||
bind.albumsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), spanCount));
|
||||
bind.albumsRecyclerView.addItemDecoration(new GridItemDecoration(spanCount, 20, false));
|
||||
bind.albumsRecyclerView.setHasFixedSize(true);
|
||||
|
||||
albumCatalogueAdapter = new AlbumCatalogueAdapter(this, false);
|
||||
@@ -296,8 +303,8 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
private void initSimilarArtistsView() {
|
||||
bind.similarArtistsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
|
||||
bind.similarArtistsRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
|
||||
bind.similarArtistsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), spanCount));
|
||||
bind.similarArtistsRecyclerView.addItemDecoration(new GridItemDecoration(spanCount, 20, false));
|
||||
bind.similarArtistsRecyclerView.setHasFixedSize(true);
|
||||
|
||||
artistCatalogueAdapter = new ArtistCatalogueAdapter(this);
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -32,6 +33,7 @@ import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.GenreCatalogueAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.GenreCatalogueViewModel;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
@@ -41,6 +43,7 @@ public class GenreCatalogueFragment extends Fragment implements ClickCallback {
|
||||
private GenreCatalogueViewModel genreCatalogueViewModel;
|
||||
|
||||
private GenreCatalogueAdapter genreCatalogueAdapter;
|
||||
private int spanCount = 2;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
@@ -56,6 +59,10 @@ public class GenreCatalogueFragment extends Fragment implements ClickCallback {
|
||||
View view = bind.getRoot();
|
||||
genreCatalogueViewModel = new ViewModelProvider(requireActivity()).get(GenreCatalogueViewModel.class);
|
||||
|
||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
spanCount = Preferences.getLandscapeItemsPerRow();
|
||||
}
|
||||
|
||||
init();
|
||||
initAppBar();
|
||||
initGenreCatalogueView();
|
||||
@@ -97,8 +104,8 @@ public class GenreCatalogueFragment extends Fragment implements ClickCallback {
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initGenreCatalogueView() {
|
||||
bind.genreCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
|
||||
bind.genreCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(2, 16, false));
|
||||
bind.genreCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), spanCount));
|
||||
bind.genreCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(spanCount, 16, false));
|
||||
bind.genreCatalogueRecyclerView.setHasFixedSize(true);
|
||||
|
||||
genreCatalogueAdapter = new GenreCatalogueAdapter(this);
|
||||
|
||||
@@ -1262,7 +1262,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
final boolean[] playbackStarted = {false};
|
||||
|
||||
Toast.makeText(requireContext(), R.string.bottom_sheet_generating_instant_mix, Toast.LENGTH_SHORT).show();
|
||||
homeViewModel.getMediaInstantMix(getViewLifecycleOwner(), track)
|
||||
.observe(getViewLifecycleOwner(), songs -> {
|
||||
if (playbackStarted[0] || songs == null || songs.isEmpty()) return;
|
||||
|
||||
@@ -39,6 +39,7 @@ import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerControllerBindi
|
||||
import com.cappielloantonio.tempo.service.EqualizerManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.dialog.PlaybackSpeedDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.TrackInfoDialog;
|
||||
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerHorizontalPager;
|
||||
@@ -413,10 +414,10 @@ public class PlayerControllerFragment extends Fragment {
|
||||
bind.getRoot().setShowNextButton(true);
|
||||
bind.getRoot().setShowFastForwardButton(false);
|
||||
bind.getRoot().setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL | RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE);
|
||||
bind.getRoot().findViewById(R.id.player_playback_speed_button).setVisibility(View.GONE);
|
||||
bind.getRoot().findViewById(R.id.player_playback_speed_button).setVisibility(View.VISIBLE);
|
||||
bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button).setVisibility(View.GONE);
|
||||
bind.getRoot().findViewById(R.id.button_favorite).setVisibility(View.VISIBLE);
|
||||
resetPlaybackParameters(mediaBrowser);
|
||||
setPlaybackParameters(mediaBrowser);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -522,33 +523,12 @@ public class PlayerControllerFragment extends Fragment {
|
||||
|
||||
private void initPlaybackSpeedButton(MediaBrowser mediaBrowser) {
|
||||
playbackSpeedButton.setOnClickListener(view -> {
|
||||
float currentSpeed = Preferences.getPlaybackSpeed();
|
||||
|
||||
if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_080) {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_100));
|
||||
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_100));
|
||||
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_100);
|
||||
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_100) {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_125));
|
||||
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_125));
|
||||
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_125);
|
||||
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_125) {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_150));
|
||||
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_150));
|
||||
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_150);
|
||||
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_150) {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_175));
|
||||
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_175));
|
||||
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_175);
|
||||
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_175) {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_200));
|
||||
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_200));
|
||||
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_200);
|
||||
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_200) {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_080));
|
||||
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_080));
|
||||
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_080);
|
||||
}
|
||||
PlaybackSpeedDialog dialog = new PlaybackSpeedDialog();
|
||||
dialog.setPlaybackSpeedListener(speed -> {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(speed));
|
||||
playbackSpeedButton.setText(getString(R.string.player_playback_speed, speed));
|
||||
});
|
||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||
});
|
||||
|
||||
skipSilenceToggleButton.setOnClickListener(view -> {
|
||||
@@ -600,7 +580,7 @@ public class PlayerControllerFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void resetPlaybackParameters(MediaBrowser mediaBrowser) {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_100));
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(1.0f));
|
||||
// TODO Resettare lo skip del silenzio
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ import android.os.Handler;
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextPaint;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -51,6 +53,7 @@ public class PlayerLyricsFragment extends Fragment {
|
||||
private Runnable syncLyricsRunnable;
|
||||
private String currentLyrics;
|
||||
private LyricsList currentLyricsList;
|
||||
private Integer lastLineIdx;
|
||||
private String currentDescription;
|
||||
|
||||
@Override
|
||||
@@ -109,6 +112,7 @@ public class PlayerLyricsFragment extends Fragment {
|
||||
currentLyrics = null;
|
||||
currentLyricsList = null;
|
||||
currentDescription = null;
|
||||
lastLineIdx = null;
|
||||
}
|
||||
|
||||
private void initOverlay() {
|
||||
@@ -162,6 +166,7 @@ public class PlayerLyricsFragment extends Fragment {
|
||||
|
||||
playerBottomSheetViewModel.getLiveLyricsList().observe(getViewLifecycleOwner(), lyricsList -> {
|
||||
currentLyricsList = lyricsList;
|
||||
lastLineIdx = null;
|
||||
updatePanelContent();
|
||||
});
|
||||
|
||||
@@ -194,7 +199,7 @@ public class PlayerLyricsFragment extends Fragment {
|
||||
bind.nowPlayingSongLyricsSrollView.smoothScrollTo(0, 0);
|
||||
|
||||
if (hasStructuredLyrics(currentLyricsList)) {
|
||||
setSyncLirics(currentLyricsList);
|
||||
setSyncLyrics(currentLyricsList);
|
||||
bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
|
||||
bind.emptyDescriptionImageView.setVisibility(View.GONE);
|
||||
bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
|
||||
@@ -241,14 +246,14 @@ public class PlayerLyricsFragment extends Fragment {
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
private void setSyncLirics(LyricsList lyricsList) {
|
||||
private void setSyncLyrics(LyricsList lyricsList) {
|
||||
if (lyricsList.getStructuredLyrics() != null && !lyricsList.getStructuredLyrics().isEmpty() && lyricsList.getStructuredLyrics().get(0).getLine() != null) {
|
||||
StringBuilder lyricsBuilder = new StringBuilder();
|
||||
List<Line> lines = lyricsList.getStructuredLyrics().get(0).getLine();
|
||||
|
||||
if (lines != null) {
|
||||
for (Line line : lines) {
|
||||
lyricsBuilder.append(line.getValue().trim()).append("\n");
|
||||
lyricsBuilder.append(line.getValue().trim()).append("\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,67 +293,75 @@ public class PlayerLyricsFragment extends Fragment {
|
||||
int timestamp = (int) (mediaBrowser.getCurrentPosition());
|
||||
|
||||
if (hasStructuredLyrics(lyricsList)) {
|
||||
StringBuilder lyricsBuilder = new StringBuilder();
|
||||
List<Line> lines = lyricsList.getStructuredLyrics().get(0).getLine();
|
||||
|
||||
if (lines == null || lines.isEmpty()) return;
|
||||
|
||||
for (Line line : lines) {
|
||||
lyricsBuilder.append(line.getValue().trim()).append("\n");
|
||||
if (lines == null || lines.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Line toHighlight = lines.stream().filter(line -> line != null && line.getStart() != null && line.getStart() < timestamp).reduce((first, second) -> second).orElse(null);
|
||||
|
||||
if (toHighlight != null) {
|
||||
String lyrics = lyricsBuilder.toString();
|
||||
Spannable spannableString = new SpannableString(lyrics);
|
||||
|
||||
int startingPosition = getStartPosition(lines, toHighlight);
|
||||
int endingPosition = startingPosition + toHighlight.getValue().length();
|
||||
|
||||
spannableString.setSpan(new ForegroundColorSpan(requireContext().getResources().getColor(R.color.shadowsLyricsTextColor, null)), 0, lyrics.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
spannableString.setSpan(new ForegroundColorSpan(requireContext().getResources().getColor(R.color.lyricsTextColor, null)), startingPosition, endingPosition, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
bind.nowPlayingSongLyricsTextView.setText(spannableString);
|
||||
|
||||
if (playerBottomSheetViewModel.getSyncLyricsState()) {
|
||||
bind.nowPlayingSongLyricsSrollView.smoothScrollTo(0, getScroll(lines, toHighlight));
|
||||
// Find the index of the currently playing line
|
||||
int curIdx = 0;
|
||||
for (; curIdx < lines.size(); ++curIdx) {
|
||||
Integer start = lines.get(curIdx).getStart();
|
||||
if (start != null && start > timestamp) {
|
||||
curIdx--; // Found the first line that starts after the current timestamp
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getStartPosition(List<Line> lines, Line toHighlight) {
|
||||
int start = 0;
|
||||
// Only update if the highlighted line has changed
|
||||
if (lastLineIdx != null && curIdx == lastLineIdx) {
|
||||
return;
|
||||
}
|
||||
lastLineIdx = curIdx;
|
||||
|
||||
for (Line line : lines) {
|
||||
if (line != toHighlight) {
|
||||
start = start + line.getValue().length() + 1;
|
||||
} else {
|
||||
break;
|
||||
StringBuilder lyricsBuilder = new StringBuilder();
|
||||
for (Line line : lines) {
|
||||
lyricsBuilder.append(line.getValue().trim()).append("\n\n");
|
||||
}
|
||||
String lyrics = lyricsBuilder.toString();
|
||||
Spannable spannableString = new SpannableString(lyrics);
|
||||
|
||||
// Make each line clickable for navigation and highlight the current one
|
||||
int offset = 0;
|
||||
int highlightStart = -1;
|
||||
for (int i = 0; i < lines.size(); ++i) {
|
||||
boolean highlight = i == curIdx;
|
||||
if (highlight) highlightStart = offset;
|
||||
|
||||
int len = lines.get(i).getValue().length() + 2;
|
||||
final int lineStart = lines.get(i).getStart();
|
||||
spannableString.setSpan(new ClickableSpan() {
|
||||
@Override
|
||||
public void onClick(@NonNull View view) {
|
||||
// Seeking to 1ms after the actual start prevents scrolling / highlighting artifacts
|
||||
mediaBrowser.seekTo(lineStart + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDrawState(@NonNull TextPaint ds) {
|
||||
super.updateDrawState(ds);
|
||||
ds.setUnderlineText(false);
|
||||
if (highlight) {
|
||||
ds.setColor(requireContext().getResources().getColor(R.color.lyricsTextColor, null));
|
||||
} else {
|
||||
ds.setColor(requireContext().getResources().getColor(R.color.shadowsLyricsTextColor, null));
|
||||
}
|
||||
}
|
||||
}, offset, offset + len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
offset += len;
|
||||
}
|
||||
|
||||
bind.nowPlayingSongLyricsTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
bind.nowPlayingSongLyricsTextView.setText(spannableString);
|
||||
|
||||
// Scroll to the highlighted line, but only if there is one
|
||||
if (highlightStart >= 0 && playerBottomSheetViewModel.getSyncLyricsState()) {
|
||||
bind.nowPlayingSongLyricsSrollView.smoothScrollTo(0, getScroll(highlightStart));
|
||||
}
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
private int getLineCount(List<Line> lines, Line toHighlight) {
|
||||
int start = 0;
|
||||
|
||||
for (Line line : lines) {
|
||||
if (line != toHighlight) {
|
||||
bind.tempLyricsLineTextView.setText(line.getValue());
|
||||
start = start + bind.tempLyricsLineTextView.getLineCount();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
private int getScroll(List<Line> lines, Line toHighlight) {
|
||||
int startIndex = getStartPosition(lines, toHighlight);
|
||||
private int getScroll(int startIndex) {
|
||||
Layout layout = bind.nowPlayingSongLyricsTextView.getLayout();
|
||||
if (layout == null) return 0;
|
||||
|
||||
|
||||
@@ -156,10 +156,10 @@ public class PlaylistCatalogueFragment extends Fragment implements ClickCallback
|
||||
|
||||
popup.setOnMenuItemClickListener(menuItem -> {
|
||||
if (menuItem.getItemId() == R.id.menu_playlist_sort_name) {
|
||||
playlistHorizontalAdapter.sort(Constants.GENRE_ORDER_BY_NAME);
|
||||
playlistHorizontalAdapter.sort(Constants.PLAYLIST_ORDER_BY_NAME);
|
||||
return true;
|
||||
} else if (menuItem.getItemId() == R.id.menu_playlist_sort_random) {
|
||||
playlistHorizontalAdapter.sort(Constants.GENRE_ORDER_BY_RANDOM);
|
||||
playlistHorizontalAdapter.sort(Constants.PLAYLIST_ORDER_BY_RANDOM);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import android.media.audiofx.AudioEffect;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.text.InputFilter;
|
||||
import android.text.InputType;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -27,6 +29,7 @@ import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavOptions;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.preference.EditTextPreference;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
@@ -141,6 +144,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
setStreamingCacheSize();
|
||||
setAppLanguage();
|
||||
setVersion();
|
||||
setNetorkPingTimeoutBase();
|
||||
|
||||
actionLogout();
|
||||
actionScan();
|
||||
@@ -261,6 +265,30 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
}
|
||||
|
||||
private void setNetorkPingTimeoutBase() {
|
||||
EditTextPreference networkPingTimeoutBase = findPreference("network_ping_timeout_base");
|
||||
|
||||
if (networkPingTimeoutBase != null) {
|
||||
networkPingTimeoutBase.setSummaryProvider(EditTextPreference.SimpleSummaryProvider.getInstance());
|
||||
networkPingTimeoutBase.setOnBindEditTextListener(editText -> {
|
||||
editText.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
editText.setFilters(new InputFilter[]{ (source, start, end, dest, dstart, dend) -> {
|
||||
for (int i = start; i < end; i++) {
|
||||
if (!Character.isDigit(source.charAt(i))) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}});
|
||||
});
|
||||
|
||||
networkPingTimeoutBase.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
String input = (String) newValue;
|
||||
return input != null && !input.isEmpty();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void setStreamingCacheSize() {
|
||||
ListPreference streamingCachePreference = findPreference("streaming_cache_size");
|
||||
|
||||
|
||||
@@ -61,13 +61,6 @@ object Constants {
|
||||
const val MEDIA_TYPE_VIDEO = "video"
|
||||
const val MEDIA_TYPE_RADIO = "radio"
|
||||
|
||||
const val MEDIA_PLAYBACK_SPEED_080 = 0.8f
|
||||
const val MEDIA_PLAYBACK_SPEED_100 = 1.0f
|
||||
const val MEDIA_PLAYBACK_SPEED_125 = 1.25f
|
||||
const val MEDIA_PLAYBACK_SPEED_150 = 1.50f
|
||||
const val MEDIA_PLAYBACK_SPEED_175 = 1.75f
|
||||
const val MEDIA_PLAYBACK_SPEED_200 = 2.0f
|
||||
|
||||
const val MEDIA_RECENTLY_PLAYED = "MEDIA_RECENTLY_PLAYED"
|
||||
const val MEDIA_MOST_PLAYED = "MEDIA_MOST_PLAYED"
|
||||
const val MEDIA_RECENTLY_ADDED = "MEDIA_RECENTLY_ADDED"
|
||||
|
||||
@@ -46,8 +46,16 @@ class DynamicMediaSourceFactory(
|
||||
|
||||
else -> {
|
||||
val extractorsFactory: ExtractorsFactory = DefaultExtractorsFactory()
|
||||
ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory)
|
||||
.createMediaSource(mediaItem)
|
||||
val progressiveFactory = ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory)
|
||||
|
||||
val uri = mediaItem.localConfiguration?.uri
|
||||
val isTranscoding = uri?.getQueryParameter("format") != null && uri.getQueryParameter("format") != "raw"
|
||||
|
||||
if (isTranscoding && OpenSubsonicExtensionsUtil.isTranscodeOffsetExtensionAvailable()) {
|
||||
TranscodingMediaSource(mediaItem, dataSourceFactory, progressiveFactory)
|
||||
} else {
|
||||
progressiveFactory.createMediaSource(mediaItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.cappielloantonio.tempo.util;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
@@ -15,6 +16,7 @@ import androidx.media3.common.HeartRating;
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.provider.AlbumArtContentProvider;
|
||||
import com.cappielloantonio.tempo.repository.DownloadRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||
@@ -45,7 +47,7 @@ public class MappingUtil {
|
||||
Uri artworkUri = null;
|
||||
|
||||
if (coverArtId != null) {
|
||||
artworkUri = Uri.parse(CustomGlideRequest.createUrl(coverArtId, Preferences.getImageSize()));
|
||||
artworkUri = AlbumArtContentProvider.contentUri(coverArtId);
|
||||
}
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
@@ -235,7 +237,7 @@ public class MappingUtil {
|
||||
|
||||
public static MediaItem mapMediaItem(PodcastEpisode podcastEpisode) {
|
||||
Uri uri = getUri(podcastEpisode);
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(podcastEpisode.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(podcastEpisode.getCoverArtId());
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("id", podcastEpisode.getId());
|
||||
|
||||
@@ -31,7 +31,7 @@ public class MusicUtil {
|
||||
private static final Pattern BITRATE_PATTERN = Pattern.compile("&maxBitRate=\\d+");
|
||||
private static final Pattern FORMAT_PATTERN = Pattern.compile("&format=\\w+");
|
||||
|
||||
public static Uri getStreamUri(String id) {
|
||||
public static Uri getStreamUri(String id, int timeOffset) {
|
||||
Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
|
||||
|
||||
StringBuilder uri = new StringBuilder();
|
||||
@@ -58,6 +58,8 @@ public class MusicUtil {
|
||||
uri.append("&format=").append(getTranscodingFormatPreference());
|
||||
if (Preferences.askForEstimateContentLength())
|
||||
uri.append("&estimateContentLength=true");
|
||||
if (timeOffset > 0)
|
||||
uri.append("&timeOffset=").append(timeOffset);
|
||||
|
||||
uri.append("&id=").append(id);
|
||||
|
||||
@@ -66,6 +68,10 @@ public class MusicUtil {
|
||||
return Uri.parse(uri.toString());
|
||||
}
|
||||
|
||||
public static Uri getStreamUri(String id) {
|
||||
return getStreamUri(id, 0);
|
||||
}
|
||||
|
||||
public static Uri updateStreamUri(Uri uri) {
|
||||
String s = uri.toString();
|
||||
Matcher m1 = BITRATE_PATTERN.matcher(s);
|
||||
|
||||
@@ -29,6 +29,7 @@ object Preferences {
|
||||
private const val REPEAT_MODE = "repeat_mode"
|
||||
private const val IMAGE_CACHE_SIZE = "image_cache_size"
|
||||
private const val STREAMING_CACHE_SIZE = "streaming_cache_size"
|
||||
private const val LANDSCAPE_ITEMS_PER_ROW = "landscape_items_per_row"
|
||||
private const val IMAGE_SIZE = "image_size"
|
||||
private const val MAX_BITRATE_WIFI = "max_bitrate_wifi"
|
||||
private const val MAX_BITRATE_MOBILE = "max_bitrate_mobile"
|
||||
@@ -74,6 +75,8 @@ object Preferences {
|
||||
private const val CONTINUOUS_PLAY = "continuous_play"
|
||||
private const val LAST_INSTANT_MIX = "last_instant_mix"
|
||||
private const val ALLOW_PLAYLIST_DUPLICATES = "allow_playlist_duplicates"
|
||||
private const val HOME_SORT_PLAYLISTS = "home_sort_playlists"
|
||||
private const val DEFAULT_HOME_SORT_PLAYLISTS_SORT_ORDER = Constants.PLAYLIST_ORDER_BY_RANDOM
|
||||
private const val EQUALIZER_ENABLED = "equalizer_enabled"
|
||||
private const val EQUALIZER_BAND_LEVELS = "equalizer_band_levels"
|
||||
private const val MINI_SHUFFLE_BUTTON_VISIBILITY = "mini_shuffle_button_visibility"
|
||||
@@ -83,6 +86,8 @@ object Preferences {
|
||||
private const val ARTIST_SORT_BY_ALBUM_COUNT= "artist_sort_by_album_count"
|
||||
private const val SORT_SEARCH_CHRONOLOGICALLY= "sort_search_chronologically"
|
||||
private const val ARTIST_DISPLAY_BIOGRAPHY= "artist_display_biography"
|
||||
private const val NETWORK_PING_TIMEOUT = "network_ping_timeout_base"
|
||||
|
||||
|
||||
@JvmStatic
|
||||
fun getServer(): String? {
|
||||
@@ -94,6 +99,19 @@ object Preferences {
|
||||
App.getInstance().preferences.edit().putString(SERVER, server).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getNetworkPingTimeout(): Int {
|
||||
val timeoutString = App.getInstance().preferences.getString(NETWORK_PING_TIMEOUT, "2") ?: "2"
|
||||
return (timeoutString.toIntOrNull() ?: 2).coerceAtLeast(1)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setNetworkPingTimeout(pingTimeout: String?) {
|
||||
App.getInstance().preferences.edit().putString(NETWORK_PING_TIMEOUT, pingTimeout).apply()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@JvmStatic
|
||||
fun getUser(): String? {
|
||||
return App.getInstance().preferences.getString(USER, null)
|
||||
@@ -287,6 +305,11 @@ object Preferences {
|
||||
return App.getInstance().preferences.getString(IMAGE_CACHE_SIZE, "500")!!.toInt()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getLandscapeItemsPerRow(): Int {
|
||||
return App.getInstance().preferences.getString(LANDSCAPE_ITEMS_PER_ROW, "4")!!.toInt()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getImageSize(): Int {
|
||||
return App.getInstance().preferences.getString(IMAGE_SIZE, "-1")!!.toInt()
|
||||
@@ -625,6 +648,16 @@ object Preferences {
|
||||
return App.getInstance().preferences.getBoolean(ALLOW_PLAYLIST_DUPLICATES, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getHomeSortPlaylists(): String {
|
||||
return App.getInstance().preferences.getString(HOME_SORT_PLAYLISTS, DEFAULT_HOME_SORT_PLAYLISTS_SORT_ORDER) ?: DEFAULT_HOME_SORT_PLAYLISTS_SORT_ORDER
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getHomeSortPlaylists(sortOrder: String) {
|
||||
App.getInstance().preferences.edit().putString(HOME_SORT_PLAYLISTS, sortOrder).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setEqualizerEnabled(enabled: Boolean) {
|
||||
App.getInstance().preferences.edit().putBoolean(EQUALIZER_ENABLED, enabled).apply()
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.media3.common.Metadata;
|
||||
import androidx.media3.common.Tracks;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.extractor.metadata.id3.InternalFrame;
|
||||
|
||||
import com.cappielloantonio.tempo.model.ReplayGain;
|
||||
|
||||
@@ -82,26 +83,32 @@ public class ReplayGainUtil {
|
||||
private static ReplayGain setReplayGains(Metadata.Entry entry) {
|
||||
ReplayGain replayGain = new ReplayGain();
|
||||
|
||||
if (entry.toString().contains(tags[0])) {
|
||||
replayGain.setTrackGain(parseReplayGainTag(entry));
|
||||
// The logic below assumes .toString() contains the dB value. That's not the case for InternalFrame
|
||||
String str = entry.toString();
|
||||
if (entry instanceof InternalFrame) {
|
||||
str = ((InternalFrame) entry).description + ((InternalFrame) entry).text;
|
||||
}
|
||||
|
||||
if (entry.toString().contains(tags[1])) {
|
||||
replayGain.setAlbumGain(parseReplayGainTag(entry));
|
||||
if (str.contains(tags[0])) {
|
||||
replayGain.setTrackGain(parseReplayGainTag(str));
|
||||
}
|
||||
|
||||
if (entry.toString().contains(tags[2])) {
|
||||
replayGain.setTrackGain(parseReplayGainTag(entry) / 256f);
|
||||
if (str.contains(tags[1])) {
|
||||
replayGain.setAlbumGain(parseReplayGainTag(str));
|
||||
}
|
||||
|
||||
if (entry.toString().contains(tags[3])) {
|
||||
replayGain.setAlbumGain(parseReplayGainTag(entry) / 256f);
|
||||
if (str.contains(tags[2])) {
|
||||
replayGain.setTrackGain(parseReplayGainTag(str) / 256f);
|
||||
}
|
||||
|
||||
if (str.contains(tags[3])) {
|
||||
replayGain.setAlbumGain(parseReplayGainTag(str) / 256f);
|
||||
}
|
||||
|
||||
return replayGain;
|
||||
}
|
||||
|
||||
private static Float parseReplayGainTag(Metadata.Entry entry) {
|
||||
private static Float parseReplayGainTag(String entry) {
|
||||
try {
|
||||
return Float.parseFloat(entry.toString().replaceAll("[^\\d.-]", ""));
|
||||
} catch (NumberFormatException exception) {
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
package com.cappielloantonio.tempo.util
|
||||
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Timeline
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.common.util.Util
|
||||
import androidx.media3.datasource.DataSource
|
||||
import androidx.media3.datasource.TransferListener
|
||||
import androidx.media3.decoder.DecoderInputBuffer
|
||||
import androidx.media3.exoplayer.FormatHolder
|
||||
import androidx.media3.exoplayer.LoadingInfo
|
||||
import androidx.media3.exoplayer.SeekParameters
|
||||
import androidx.media3.exoplayer.source.CompositeMediaSource
|
||||
import androidx.media3.exoplayer.source.ForwardingTimeline
|
||||
import androidx.media3.exoplayer.source.MediaPeriod
|
||||
import androidx.media3.exoplayer.source.MediaSource
|
||||
import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
||||
import androidx.media3.exoplayer.source.SampleStream
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection
|
||||
import androidx.media3.exoplayer.upstream.Allocator
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
class TranscodingMediaSource(
|
||||
private val mediaItem: MediaItem,
|
||||
private val dataSourceFactory: DataSource.Factory,
|
||||
private val progressiveMediaSourceFactory: ProgressiveMediaSource.Factory
|
||||
) : CompositeMediaSource<Void>() {
|
||||
|
||||
private var durationUs: Long = C.TIME_UNSET
|
||||
private var currentSource: MediaSource? = null
|
||||
|
||||
init {
|
||||
val extras = mediaItem.mediaMetadata.extras
|
||||
if (extras != null && extras.containsKey("duration")) {
|
||||
val seconds = extras.getInt("duration")
|
||||
if (seconds > 0) {
|
||||
durationUs = Util.msToUs(seconds * 1000L)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMediaItem() = mediaItem
|
||||
|
||||
override fun prepareSourceInternal(mediaTransferListener: TransferListener?) {
|
||||
super.prepareSourceInternal(mediaTransferListener)
|
||||
val initialSource = progressiveMediaSourceFactory.createMediaSource(mediaItem)
|
||||
currentSource = initialSource
|
||||
prepareChildSource(null, initialSource)
|
||||
}
|
||||
|
||||
override fun onChildSourceInfoRefreshed(
|
||||
childSourceId: Void?,
|
||||
mediaSource: MediaSource,
|
||||
newTimeline: Timeline
|
||||
) {
|
||||
val timeline =
|
||||
if (durationUs != C.TIME_UNSET) {
|
||||
DurationOverridingTimeline(newTimeline, durationUs)
|
||||
} else {
|
||||
newTimeline
|
||||
}
|
||||
refreshSourceInfo(timeline)
|
||||
}
|
||||
|
||||
override fun createPeriod(
|
||||
id: MediaSource.MediaPeriodId,
|
||||
allocator: Allocator,
|
||||
startPositionUs: Long
|
||||
): MediaPeriod {
|
||||
val source = currentSource ?: throw IllegalStateException("Source not ready")
|
||||
val childPeriod = source.createPeriod(id, allocator, startPositionUs)
|
||||
return TranscodingMediaPeriod(childPeriod, source, id, allocator)
|
||||
}
|
||||
|
||||
override fun releasePeriod(mediaPeriod: MediaPeriod) {
|
||||
val transcodingPeriod = mediaPeriod as TranscodingMediaPeriod
|
||||
transcodingPeriod.release()
|
||||
|
||||
if (transcodingPeriod.currentOffsetUs > 0) {
|
||||
releaseChildSource(null)
|
||||
val initialSource = progressiveMediaSourceFactory.createMediaSource(mediaItem)
|
||||
currentSource = initialSource
|
||||
prepareChildSource(null, initialSource)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMediaPeriodIdForChildMediaPeriodId(
|
||||
childSourceId: Void?,
|
||||
mediaPeriodId: MediaSource.MediaPeriodId
|
||||
) = mediaPeriodId
|
||||
|
||||
private inner class TranscodingMediaPeriod(
|
||||
private var currentPeriod: MediaPeriod,
|
||||
private var source: MediaSource,
|
||||
private val id: MediaSource.MediaPeriodId,
|
||||
private val allocator: Allocator
|
||||
) : MediaPeriod, MediaPeriod.Callback {
|
||||
|
||||
private var localCallback: MediaPeriod.Callback? = null
|
||||
internal var currentOffsetUs: Long = 0
|
||||
private var isReloading = false
|
||||
|
||||
private var lastSelections: Array<out ExoTrackSelection?>? = null
|
||||
private var lastMayRetainStreamFlags: BooleanArray? = null
|
||||
private var activeWrappers: Array<OffsetSampleStream?> = emptyArray()
|
||||
|
||||
fun release() {
|
||||
source.releasePeriod(currentPeriod)
|
||||
}
|
||||
|
||||
override fun prepare(callback: MediaPeriod.Callback, positionUs: Long) {
|
||||
localCallback = callback
|
||||
currentPeriod.prepare(this, positionUs)
|
||||
}
|
||||
|
||||
override fun maybeThrowPrepareError() {
|
||||
if (!isReloading) currentPeriod.maybeThrowPrepareError()
|
||||
}
|
||||
|
||||
override fun getTrackGroups() = currentPeriod.trackGroups
|
||||
|
||||
override fun getStreamKeys(trackSelections: MutableList<ExoTrackSelection>) =
|
||||
currentPeriod.getStreamKeys(trackSelections)
|
||||
|
||||
override fun selectTracks(
|
||||
selections: Array<out ExoTrackSelection?>,
|
||||
mayRetainStreamFlags: BooleanArray,
|
||||
streams: Array<SampleStream?>,
|
||||
streamResetFlags: BooleanArray,
|
||||
positionUs: Long
|
||||
): Long {
|
||||
lastSelections = selections
|
||||
lastMayRetainStreamFlags = mayRetainStreamFlags
|
||||
|
||||
val childStreams = arrayOfNulls<SampleStream>(streams.size)
|
||||
streams.forEachIndexed { i, stream ->
|
||||
childStreams[i] = (stream as? OffsetSampleStream)?.childStream
|
||||
}
|
||||
|
||||
val startPos =
|
||||
currentPeriod.selectTracks(
|
||||
selections,
|
||||
mayRetainStreamFlags,
|
||||
childStreams,
|
||||
streamResetFlags,
|
||||
positionUs - currentOffsetUs
|
||||
)
|
||||
|
||||
val newWrappers = arrayOfNulls<OffsetSampleStream>(streams.size)
|
||||
for (i in streams.indices) {
|
||||
val child = childStreams[i]
|
||||
if (child == null) {
|
||||
streams[i] = null
|
||||
} else {
|
||||
val existingWrapper = streams[i] as? OffsetSampleStream
|
||||
if (existingWrapper != null && existingWrapper.childStream === child) {
|
||||
newWrappers[i] = existingWrapper
|
||||
} else {
|
||||
val wrapper = OffsetSampleStream(child)
|
||||
newWrappers[i] = wrapper
|
||||
streams[i] = wrapper
|
||||
}
|
||||
}
|
||||
}
|
||||
activeWrappers = newWrappers
|
||||
|
||||
return startPos + currentOffsetUs
|
||||
}
|
||||
|
||||
override fun discardBuffer(positionUs: Long, toKeyframe: Boolean) {
|
||||
if (!isReloading) {
|
||||
currentPeriod.discardBuffer(positionUs - currentOffsetUs, toKeyframe)
|
||||
}
|
||||
}
|
||||
|
||||
override fun readDiscontinuity(): Long {
|
||||
if (isReloading) return C.TIME_UNSET
|
||||
val discontinuity = currentPeriod.readDiscontinuity()
|
||||
return if (discontinuity == C.TIME_UNSET) C.TIME_UNSET
|
||||
else discontinuity + currentOffsetUs
|
||||
}
|
||||
|
||||
override fun seekToUs(positionUs: Long): Long {
|
||||
if (positionUs == 0L && currentOffsetUs == 0L) {
|
||||
return currentPeriod.seekToUs(positionUs)
|
||||
}
|
||||
|
||||
reloadSource(positionUs)
|
||||
return positionUs
|
||||
}
|
||||
|
||||
override fun getAdjustedSeekPositionUs(positionUs: Long, seekParameters: SeekParameters) =
|
||||
positionUs
|
||||
|
||||
override fun getBufferedPositionUs(): Long {
|
||||
if (isReloading) return currentOffsetUs
|
||||
val buffered = currentPeriod.bufferedPositionUs
|
||||
if (buffered == C.TIME_END_OF_SOURCE) return C.TIME_END_OF_SOURCE
|
||||
return if (buffered == C.TIME_UNSET) C.TIME_UNSET else buffered + currentOffsetUs
|
||||
}
|
||||
|
||||
override fun getNextLoadPositionUs(): Long {
|
||||
if (isReloading) return C.TIME_UNSET
|
||||
val next = currentPeriod.nextLoadPositionUs
|
||||
if (next == C.TIME_END_OF_SOURCE) return C.TIME_END_OF_SOURCE
|
||||
return if (next == C.TIME_UNSET) C.TIME_UNSET else next + currentOffsetUs
|
||||
}
|
||||
|
||||
override fun reevaluateBuffer(positionUs: Long) {
|
||||
if (!isReloading) currentPeriod.reevaluateBuffer(positionUs - currentOffsetUs)
|
||||
}
|
||||
|
||||
override fun continueLoading(isLoading: LoadingInfo): Boolean {
|
||||
if (isReloading) return false
|
||||
val builder = isLoading.buildUpon()
|
||||
builder.setPlaybackPositionUs(isLoading.playbackPositionUs - currentOffsetUs)
|
||||
return currentPeriod.continueLoading(builder.build())
|
||||
}
|
||||
|
||||
override fun isLoading() = isReloading || currentPeriod.isLoading
|
||||
|
||||
override fun onPrepared(mediaPeriod: MediaPeriod) {
|
||||
if (isReloading && mediaPeriod == currentPeriod) {
|
||||
isReloading = false
|
||||
restoreTracks()
|
||||
localCallback?.onContinueLoadingRequested(this)
|
||||
} else {
|
||||
localCallback?.onPrepared(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onContinueLoadingRequested(source: MediaPeriod) {
|
||||
if (!isReloading) localCallback?.onContinueLoadingRequested(this)
|
||||
}
|
||||
|
||||
private fun reloadSource(positionUs: Long) {
|
||||
isReloading = true
|
||||
currentOffsetUs = positionUs
|
||||
|
||||
activeWrappers.forEach { it?.childStream = null }
|
||||
|
||||
source.releasePeriod(currentPeriod)
|
||||
releaseChildSource(null)
|
||||
|
||||
val seconds = Util.usToMs(positionUs) / 1000
|
||||
val newUri = MusicUtil.getStreamUri(mediaItem.mediaId, seconds.toInt())
|
||||
val newMediaItem = mediaItem.buildUpon().setUri(newUri).build()
|
||||
|
||||
val newSource = progressiveMediaSourceFactory.createMediaSource(newMediaItem)
|
||||
|
||||
source = newSource
|
||||
currentSource = newSource
|
||||
prepareChildSource(null, newSource)
|
||||
|
||||
val newPeriod = newSource.createPeriod(id, allocator, 0)
|
||||
currentPeriod = newPeriod
|
||||
newPeriod.prepare(this, 0)
|
||||
}
|
||||
|
||||
private fun restoreTracks() {
|
||||
val selections = lastSelections ?: return
|
||||
val flags = lastMayRetainStreamFlags ?: return
|
||||
|
||||
val childStreams = arrayOfNulls<SampleStream>(activeWrappers.size)
|
||||
val streamResetFlags = BooleanArray(activeWrappers.size)
|
||||
|
||||
currentPeriod.selectTracks(selections, flags, childStreams, streamResetFlags, 0)
|
||||
|
||||
for (i in activeWrappers.indices) {
|
||||
activeWrappers[i]?.childStream = childStreams[i]
|
||||
}
|
||||
}
|
||||
|
||||
private inner class OffsetSampleStream(var childStream: SampleStream?) : SampleStream {
|
||||
override fun isReady() = childStream?.isReady ?: false
|
||||
override fun maybeThrowError() {
|
||||
childStream?.maybeThrowError()
|
||||
}
|
||||
|
||||
override fun readData(
|
||||
formatHolder: FormatHolder,
|
||||
buffer: DecoderInputBuffer,
|
||||
readFlags: Int
|
||||
): Int {
|
||||
val stream = childStream ?: return C.RESULT_NOTHING_READ
|
||||
val result = stream.readData(formatHolder, buffer, readFlags)
|
||||
if (result == C.RESULT_BUFFER_READ && !buffer.isEndOfStream) {
|
||||
buffer.timeUs += currentOffsetUs
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun skipData(positionUs: Long) =
|
||||
childStream?.skipData(positionUs - currentOffsetUs) ?: 0
|
||||
}
|
||||
}
|
||||
|
||||
private class DurationOverridingTimeline(timeline: Timeline, private val durationUs: Long) :
|
||||
ForwardingTimeline(timeline) {
|
||||
|
||||
override fun getWindow(
|
||||
windowIndex: Int,
|
||||
window: Window,
|
||||
defaultPositionProjectionUs: Long
|
||||
): Window {
|
||||
super.getWindow(windowIndex, window, defaultPositionProjectionUs)
|
||||
window.durationUs = durationUs
|
||||
window.isSeekable = true
|
||||
window.isDynamic = false
|
||||
window.liveConfiguration = null
|
||||
return window
|
||||
}
|
||||
|
||||
override fun getPeriod(periodIndex: Int, period: Period, setIds: Boolean): Period {
|
||||
super.getPeriod(periodIndex, period, setIds)
|
||||
period.durationUs = durationUs
|
||||
return period
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Playlist;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Share;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
@@ -100,7 +101,7 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
}
|
||||
|
||||
public LiveData<List<Child>> getRandomShuffleSample() {
|
||||
return songRepository.getRandomSample(1000, null, null);
|
||||
return songRepository.getRandomSample(100, null, null);
|
||||
}
|
||||
|
||||
public LiveData<List<Chronology>> getChronologySample(LifecycleOwner owner) {
|
||||
@@ -250,12 +251,19 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
playlistRepository.getPlaylists(false, -1).observe(owner, remotes -> {
|
||||
if (remotes != null && !remotes.isEmpty()) {
|
||||
List<Playlist> playlists = new ArrayList<>(remotes);
|
||||
Collections.shuffle(playlists);
|
||||
List<Playlist> randomPlaylists = playlists.size() > 5
|
||||
String result = Preferences.getHomeSortPlaylists();
|
||||
if (Preferences.getHomeSortPlaylists().equals(Constants.PLAYLIST_ORDER_BY_RANDOM))
|
||||
{
|
||||
Collections.shuffle(playlists);
|
||||
}
|
||||
else {
|
||||
playlists.sort(Comparator.comparing(Playlist::getName));
|
||||
}
|
||||
List<Playlist> subsetPlaylists = playlists.size() > 5
|
||||
? playlists.subList(0, 5)
|
||||
: playlists;
|
||||
|
||||
pinnedPlaylists.setValue(randomPlaylists);
|
||||
pinnedPlaylists.setValue(subsetPlaylists);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.cappielloantonio.tempo.viewmodel;
|
||||
|
||||
import android.app.Application;
|
||||
import android.app.Dialog;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
@@ -21,8 +20,17 @@ import java.util.List;
|
||||
|
||||
public class PlaylistChooserViewModel extends AndroidViewModel {
|
||||
private final PlaylistRepository playlistRepository;
|
||||
|
||||
private final MutableLiveData<List<Playlist>> playlists = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<Boolean> playlistIsPublic = new MutableLiveData<>(false);
|
||||
|
||||
public Boolean getIsPlaylistPublic() {
|
||||
return playlistIsPublic.getValue();
|
||||
}
|
||||
|
||||
public void setIsPlaylistPublic(boolean isPublic) {
|
||||
playlistIsPublic.setValue(isPublic);
|
||||
}
|
||||
|
||||
private ArrayList<Child> toAdd = new ArrayList<>();
|
||||
|
||||
public PlaylistChooserViewModel(@NonNull Application application) {
|
||||
@@ -39,7 +47,7 @@ public class PlaylistChooserViewModel extends AndroidViewModel {
|
||||
public void addSongsToPlaylist(LifecycleOwner owner, Dialog dialog, String playlistId) {
|
||||
List<String> songIds = Lists.transform(toAdd, Child::getId);
|
||||
if (Preferences.allowPlaylistDuplicates()) {
|
||||
playlistRepository.addSongToPlaylist(playlistId, new ArrayList<>(songIds));
|
||||
playlistRepository.addSongToPlaylist(playlistId, new ArrayList<>(songIds), getIsPlaylistPublic());
|
||||
dialog.dismiss();
|
||||
} else {
|
||||
playlistRepository.getPlaylistSongs(playlistId).observe(owner, playlistSongs -> {
|
||||
@@ -47,7 +55,7 @@ public class PlaylistChooserViewModel extends AndroidViewModel {
|
||||
List<String> playlistSongIds = Lists.transform(playlistSongs, Child::getId);
|
||||
songIds.removeAll(playlistSongIds);
|
||||
}
|
||||
playlistRepository.addSongToPlaylist(playlistId, new ArrayList<>(songIds));
|
||||
playlistRepository.addSongToPlaylist(playlistId, new ArrayList<>(songIds), getIsPlaylistPublic());
|
||||
dialog.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
14
app/src/main/res/drawable/ic_graphic_eq_land.xml
Normal file
14
app/src/main/res/drawable/ic_graphic_eq_land.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:pivotY="12"
|
||||
android:pivotX="12"
|
||||
android:rotation="270">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M8,18c0.55,0 1,-0.45 1,-1L9,7c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v10c0,0.55 0.45,1 1,1zM12,22c0.55,0 1,-0.45 1,-1L13,3c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v18c0,0.55 0.45,1 1,1zM4,14c0.55,0 1,-0.45 1,-1v-2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v2c0,0.55 0.45,1 1,1zM16,18c0.55,0 1,-0.45 1,-1L17,7c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v10c0,0.55 0.45,1 1,1zM19,11v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1v-2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1z" />
|
||||
</group>
|
||||
</vector>
|
||||
14
app/src/main/res/drawable/ic_home_land.xml
Normal file
14
app/src/main/res/drawable/ic_home_land.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:pivotY="12"
|
||||
android:pivotX="12"
|
||||
android:rotation="270">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,5.69l5,4.5V18h-2v-6H9v6H7v-7.81l5,-4.5M12,3L2,12h3v8h6v-6h2v6h6v-8h3L12,3z" />
|
||||
</group>
|
||||
</vector>
|
||||
14
app/src/main/res/drawable/ic_play_for_work_land.xml
Normal file
14
app/src/main/res/drawable/ic_play_for_work_land.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:pivotY="12"
|
||||
android:pivotX="12"
|
||||
android:rotation="270">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M11,5v5.59L7.5,10.59l4.5,4.5 4.5,-4.5L13,10.59L13,5h-2zM6,14c0,3.31 2.69,6 6,6s6,-2.69 6,-6h-2c0,2.21 -1.79,4 -4,4s-4,-1.79 -4,-4L6,14z"/>
|
||||
</group>
|
||||
</vector>
|
||||
68
app/src/main/res/layout-land/activity_main.xml
Normal file
68
app/src/main/res/layout-land/activity_main.xml
Normal file
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/colorSurface"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
android:id="@+id/bottom_navigation"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="75dp"
|
||||
android:rotation="90"
|
||||
android:layout_gravity="center"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:visibility="gone"
|
||||
app:menu="@menu/bottom_nav_menu" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/nav_host_fragment"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
app:defaultNavHost="true"
|
||||
app:navGraph="@navigation/nav_graph" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/player_bottom_sheet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:behavior_hideable="true"
|
||||
app:behavior_peekHeight="@dimen/bottom_sheet_peek_height"
|
||||
app:layout_behavior="@string/bottom_sheet_behavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/offline_mode_text_view"
|
||||
style="@style/NoConnectionTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/activity_info_offline_mode"
|
||||
android:textSize="6sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
305
app/src/main/res/layout-land/fragment_album_page.xml
Normal file
305
app/src/main/res/layout-land/fragment_album_page.xml
Normal file
@@ -0,0 +1,305 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/anim_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorSurface"
|
||||
app:layout_collapseMode="pin"
|
||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1">
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/album_info_sector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorSurface"
|
||||
android:paddingStart="20dp"
|
||||
app:layout_scrollFlags="exitUntilCollapsed">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/album_cover_image_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginLeft="64dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginRight="64dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_name_label"
|
||||
style="@style/LabelExtraLarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:paddingTop="8dp"
|
||||
android:singleLine="false"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="@+id/album_cover_image_view"
|
||||
app:layout_constraintStart_toStartOf="@+id/album_cover_image_view"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_cover_image_view" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/album_other_info_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/album_name_label"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/album_name_label"
|
||||
app:layout_constraintTop_toTopOf="@+id/album_name_label">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:background="@drawable/ic_arrow_down" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_artist_label"
|
||||
style="@style/LabelMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_name_label" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_release_year_label"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_artist_label" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/album_detail_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_release_year_label"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_genres_textview"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_song_count_duration_textview"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:paddingVertical="2dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_genres_textview" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_notes_textview"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:justificationMode="inter_word"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_song_count_duration_textview" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_release_years_textview"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_notes_textview" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/upper_button_divider"
|
||||
style="@style/Divider"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_detail_view" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/upper_button_divider">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/album_page_button_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/album_page_play_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="10dp"
|
||||
android:text="@string/album_page_play_button"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_play"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="18dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/album_page_shuffle_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="10dp"
|
||||
android:text="@string/album_page_shuffle_button"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_shuffle"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="18dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ToggleButton
|
||||
android:id="@+id/button_favorite"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:background="@drawable/button_favorite_selector"
|
||||
android:checked="false"
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:gravity="center"
|
||||
android:text=""
|
||||
android:textOff=""
|
||||
android:textOn="" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_bio_label"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/album_page_button_layout"
|
||||
tools:ignore="NotSibling" />
|
||||
|
||||
<View
|
||||
android:id="@+id/bottom_button_divider"
|
||||
style="@style/Divider"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:layout_marginBottom="18dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_bio_label" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/song_recycler_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="75dp"
|
||||
android:clipToPadding="false"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
||||
218
app/src/main/res/layout-land/fragment_playlist_page.xml
Normal file
218
app/src/main/res/layout-land/fragment_playlist_page.xml
Normal file
@@ -0,0 +1,218 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/anim_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorSurface"
|
||||
app:layout_collapseMode="pin"
|
||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="@dimen/global_padding_bottom"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/playlist_info_sector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorSurface"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/playlist_cover_image_view_top_left"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginTop="8dp"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toStartOf="@id/playlist_cover_image_view_top_right"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/playlist_cover_image_view_top_right"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="64dp"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/playlist_cover_image_view_top_left"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/playlist_cover_image_view_bottom_left"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toStartOf="@id/playlist_cover_image_view_bottom_right"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/playlist_cover_image_view_top_left" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/playlist_cover_image_view_bottom_right"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginEnd="64dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/playlist_cover_image_view_bottom_left"
|
||||
app:layout_constraintTop_toTopOf="@id/playlist_cover_image_view_bottom_left" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playlist_name_label"
|
||||
style="@style/LabelExtraLarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:paddingTop="8dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playlist_cover_image_view_bottom_left" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playlist_song_count_label"
|
||||
style="@style/LabelMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playlist_name_label" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playlist_duration_label"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playlist_song_count_label" />
|
||||
|
||||
<View
|
||||
android:id="@+id/upper_button_divider"
|
||||
style="@style/Divider"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playlist_duration_label" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/playlist_page_button_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/upper_button_divider">
|
||||
|
||||
<Button
|
||||
android:id="@+id/playlist_page_play_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="10dp"
|
||||
android:text="@string/playlist_page_play_button"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_play"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="18dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/playlist_page_shuffle_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="10dp"
|
||||
android:text="@string/playlist_page_shuffle_button"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_shuffle"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="18dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_bio_label"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playlist_page_button_layout" />
|
||||
|
||||
<View
|
||||
android:id="@+id/bottom_button_divider"
|
||||
style="@style/Divider"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:layout_marginBottom="18dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playlist_page_button_layout" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/song_recycler_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:paddingTop="8dp"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
||||
@@ -16,6 +16,23 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/player_playback_speed_button"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginLeft="0dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
app:cornerRadius="30dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:tint="?attr/colorOnPrimaryContainer" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/player_media_extension"
|
||||
style="@style/Widget.Material3.Chip.Suggestion"
|
||||
@@ -253,23 +270,6 @@
|
||||
app:layout_constraintStart_toEndOf="@+id/placeholder_view_middle_right"
|
||||
app:layout_constraintTop_toTopOf="@+id/placeholder_view_middle_right" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/player_playback_speed_button"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
app:cornerRadius="30dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/placeholder_view_middle_left"
|
||||
app:layout_constraintEnd_toStartOf="@+id/placeholder_view_middle_left"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/placeholder_view_middle_left"
|
||||
app:tint="?attr/colorOnPrimaryContainer" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/exo_shuffle"
|
||||
android:layout_width="32dp"
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
style="@style/BodyLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_horizontal"
|
||||
android:lineSpacingExtra="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
15
app/src/main/res/menu-land/bottom_nav_menu.xml
Normal file
15
app/src/main/res/menu-land/bottom_nav_menu.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/homeFragment"
|
||||
android:icon="@drawable/ic_home_land"
|
||||
android:title="@string/menu_home_label" />
|
||||
<item
|
||||
android:id="@+id/libraryFragment"
|
||||
android:icon="@drawable/ic_graphic_eq_land"
|
||||
android:title="@string/menu_library_label" />
|
||||
<item
|
||||
android:id="@+id/downloadFragment"
|
||||
android:icon="@drawable/ic_play_for_work_land"
|
||||
android:title="@string/menu_download_label" />
|
||||
</menu>
|
||||
@@ -240,6 +240,15 @@
|
||||
<item>8</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="playlist_sort_option_titles">
|
||||
<item>Por nombre</item>
|
||||
<item>Aleatoriamente</item>
|
||||
</string-array>
|
||||
<string-array name="playlist_sort_option_values">
|
||||
<item>ORDER_BY_NAME</item>
|
||||
<item>ORDER_BY_RANDOM</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="skip_min_star_rating_titles">
|
||||
<item>0 estrellas como mínimo</item>
|
||||
<item>1 estrella como mínimo</item>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<string name="album_page_tracks_count_and_duration">%1$d pistas • %2$d minutos</string>
|
||||
<string name="app_name">Tempus</string>
|
||||
<string name="activity_battery_optimizations_conclusion">Si tienes problemas, visita https://dontkillmyapp.com. Ofrece instrucciones detalladas para desactivar características de ahorro de energía que podrían afectar al rendimiento de la app.</string>
|
||||
<string name="activity_battery_optimizations_summary">Por favor, desactive las optimizaciones de batería para continuar la reproducción multimedia mientras la pantalla está apagada.</string>
|
||||
<string name="activity_battery_optimizations_summary">Por favor, desactiva las optimizaciones de batería para continuar la reproducción multimedia mientras la pantalla está apagada.</string>
|
||||
<string name="activity_battery_optimizations_title">Optimizaciones de batería</string>
|
||||
<string name="activity_info_offline_mode">Modo sin conexión</string>
|
||||
<string name="album_bottom_sheet_add_to_playlist">Añadir a la lista de reproducción</string>
|
||||
@@ -40,6 +40,7 @@
|
||||
<string name="artist_list_page_downloaded">Artistas descargados</string>
|
||||
<string name="artist_list_page_starred">Artistas destacados</string>
|
||||
<string name="artist_list_page_title">Artistas</string>
|
||||
<string name="artist_no_artist_info_toast">No hay más información del artista</string>
|
||||
<string name="artist_page_radio_button">Radio</string>
|
||||
<string name="artist_page_shuffle_button">Aleatorio</string>
|
||||
<string name="artist_page_switch_layout_button">Cambiar disposición</string>
|
||||
@@ -52,6 +53,7 @@
|
||||
<string name="battery_optimization_negative_button">Ignorar</string>
|
||||
<string name="battery_optimization_neutral_button">No volver a preguntar</string>
|
||||
<string name="battery_optimization_positive_button">Desactivar</string>
|
||||
<string name="bottom_sheet_problem_generating_instant_mix">No se pudieron obtener las pistas del servidor.</string>
|
||||
<string name="connection_alert_dialog_negative_button">Cancelar</string>
|
||||
<string name="connection_alert_dialog_neutral_button">Habilitar el ahorro de datos</string>
|
||||
<string name="connection_alert_dialog_positive_button">Aceptar</string>
|
||||
@@ -60,7 +62,7 @@
|
||||
<string name="content_description_shuffle_button">Aleatorio</string>
|
||||
<string name="delete_download_storage_dialog_negative_button">Cancelar</string>
|
||||
<string name="delete_download_storage_dialog_positive_button">Continuar</string>
|
||||
<string name="delete_download_storage_dialog_summary">Por favor, sea consciente de que si continúa, todos los elementos descargados de todos los servidores se eliminarán.</string>
|
||||
<string name="delete_download_storage_dialog_summary">Por favor, ten en cuenta que si continúas, todos los elementos descargados de todos los servidores se eliminarán.</string>
|
||||
<string name="delete_download_storage_dialog_title">Eliminar elementos guardados</string>
|
||||
<string name="description_empty_title">Letra no disponible</string>
|
||||
<string name="disc_titlefull">Disco %1$s - %2$s</string>
|
||||
@@ -111,12 +113,12 @@
|
||||
<string name="home_rearrangement_dialog_neutral_button">Restablecer</string>
|
||||
<string name="home_rearrangement_dialog_positive_button">Guardar</string>
|
||||
<string name="home_rearrangement_dialog_title">Reorganizar la página de inicio</string>
|
||||
<string name="home_rearrangement_dialog_subtitle">Tenga en cuenta que para que los cambios surtan efecto, hay que reiniciar la aplicación.</string>
|
||||
<string name="home_rearrangement_dialog_subtitle">Ten en cuenta que para que los cambios surtan efecto, hay que reiniciar la aplicación.</string>
|
||||
<string name="home_section_music">Música</string>
|
||||
<string name="home_section_podcast">Pódcasts</string>
|
||||
<string name="home_section_radio">Radio</string>
|
||||
<string name="home_subtitle_best_of">Mejores pistas de tus artistas favoritos</string>
|
||||
<string name="home_subtitle_made_for_you">Iniciar mix desde una cación que te gustó</string>
|
||||
<string name="home_subtitle_made_for_you">Iniciar mix desde una canción que te gustó</string>
|
||||
<string name="home_subtitle_new_internet_radio_station">Añadir una nueva emisora de radio</string>
|
||||
<string name="home_subtitle_new_podcast_channel">Añadir un nuevo canal de pódcasts</string>
|
||||
<string name="home_sync_starred_cancel">Cancelar</string>
|
||||
@@ -225,6 +227,8 @@
|
||||
<string name="playlist_chooser_dialog_title">Añadir a una lista de reproducción</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_failure">Error al añadir a la lista</string>
|
||||
<string name="playlist_chooser_dialog_toast_all_skipped">Todas las pistas se han descartado porque están repetidas</string>
|
||||
<string name="playlist_chooser_dialog_visibility_public">Público</string>
|
||||
<string name="playlist_chooser_dialog_visibility_private">Privado</string>
|
||||
<string name="playlist_counted_tracks">%1$d pistas • %2$s</string>
|
||||
<string name="playlist_duration">Duración • %1$s</string>
|
||||
<string name="playlist_editor_dialog_action_delete_toast">Pulsación larga para eliminar</string>
|
||||
@@ -246,6 +250,7 @@
|
||||
<string name="podcast_channel_catalogue_title_expanded">Explorar canales</string>
|
||||
<string name="podcast_channel_editor_dialog_hint_rss_url">URL del feed RSS</string>
|
||||
<string name="podcast_channel_editor_dialog_title">Canal del pódcast</string>
|
||||
<string name="podcast_channel_not_supported_snackbar">Este servidor no soporta pódcasts.</string>
|
||||
<string name="podcast_channel_page_title_description_section">Descripción</string>
|
||||
<string name="podcast_channel_page_title_episode_section">Episodios</string>
|
||||
<string name="podcast_channel_page_title_no_episode_available">No hay episodios disponibles</string>
|
||||
@@ -260,10 +265,12 @@
|
||||
<string name="radio_editor_dialog_negative_button">Cancelar</string>
|
||||
<string name="radio_editor_dialog_neutral_button">Eliminar</string>
|
||||
<string name="radio_editor_dialog_positive_button">Guardar</string>
|
||||
<string name="radio_editor_dialog_title">"Emisora "</string>
|
||||
<string name="radio_editor_dialog_updated">Emisora de radio actualizada</string>
|
||||
<string name="radio_editor_dialog_title">Emisora</string>
|
||||
<string name="radio_station_info_empty_button">Pulsa para ocultar la sección\nLos cambios serán visibles al reiniciar la app</string>
|
||||
<string name="radio_station_info_empty_subtitle">Una vez que añadas una emisora de radio, la encontrarás aquí</string>
|
||||
<string name="radio_station_info_empty_title">No hay emisoras de radio</string>
|
||||
<string name="radio_dialog_not_supported_snackbar">Este servidor no soporta emisoras de radio en Internet.</string>
|
||||
<string name="rating_dialog_negative_button">Cancelar</string>
|
||||
<string name="rating_dialog_positive_button">Guardar</string>
|
||||
<string name="rating_dialog_title">Valorar</string>
|
||||
@@ -289,34 +296,34 @@
|
||||
<string name="server_unreachable_dialog_negative_button">Cancelar</string>
|
||||
<string name="server_unreachable_dialog_neutral_button">Ir al inicio de sesión</string>
|
||||
<string name="server_unreachable_dialog_positive_button">Continuar de todas formas</string>
|
||||
<string name="server_unreachable_dialog_summary">El servidor no está disponible. Si decide continuar, este diálogo no aparecerá de nuevo durante una hora.</string>
|
||||
<string name="server_unreachable_dialog_summary">El servidor no está disponible. Si decides continuar, este diálogo no aparecerá de nuevo durante una hora.</string>
|
||||
<string name="server_unreachable_dialog_title">No se puede conectar con el servidor</string>
|
||||
<string name="settings_about_summary">Tempus es un cliente de música Subsonic ligero y de código abierto, diseñado nativamente para Android.</string>
|
||||
<string name="settings_about_title">Acerca de</string>
|
||||
<string name="settings_always_on_display">Pantalla siempre activa</string>
|
||||
<string name="settings_allow_playlist_duplicates_summary">Si está habilitada, no se comprobará si hay pistas repetidas cuando se añadan a la lista.</string>
|
||||
<string name="settings_allow_playlist_duplicates_summary">Si se habilita, no se comprobará si hay pistas repetidas cuando se añadan a la lista.</string>
|
||||
<string name="settings_audio_transcode_download_format">Formato de transcodificación</string>
|
||||
<string name="settings_audio_transcode_download_priority_summary">Si está habilitada, Tempus no descargará la pista con las opciones de transcodificación que aparecen a continuación.</string>
|
||||
<string name="settings_audio_transcode_download_priority_summary">Si se habilita, Tempus no descargará la pista con las opciones de transcodificación que aparecen a continuación.</string>
|
||||
<string name="settings_audio_transcode_download_priority_title">Dar prioridad a las opciones del servidor usadas para el streaming en las descargas</string>
|
||||
<string name="settings_audio_transcode_download_summary">Si está habilitada, Tempus descargará las pistas transcodificadas.</string>
|
||||
<string name="settings_audio_transcode_download_summary">Si se habilita, Tempus descargará las pistas transcodificadas.</string>
|
||||
<string name="settings_audio_transcode_download_title">Descargas pistas transcodificadas</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_summary">Si está habilitada, se pedirá al servidor la duración estimada de la pista.</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_summary">Si se habilita, se pedirá al servidor la duración estimada de la pista.</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_title">Calcular la duración del contenido</string>
|
||||
<string name="settings_audio_transcode_format_download">Formato de transcodificación para las descargas</string>
|
||||
<string name="settings_audio_transcode_format_mobile">Formato de transcodificación en red de datos móviles</string>
|
||||
<string name="settings_audio_transcode_format_wifi">Formato de transcodificación en red Wi-Fi</string>
|
||||
<string name="settings_audio_transcode_priority_summary">Si está habilitada, Tempus no reproducirá la pista con las opciones de transcodificación que aparecen a continuación.</string>
|
||||
<string name="settings_audio_transcode_priority_summary">Si se habilita, Tempus no reproducirá la pista con las opciones de transcodificación que aparecen a continuación.</string>
|
||||
<string name="settings_audio_transcode_priority_title">Dar prioridad a las opciones de transcodificación del servidor</string>
|
||||
<string name="settings_audio_transcode_priority_toast">Prioridad a la hora de transcodificar una pista</string>
|
||||
<string name="settings_buffering_strategy">Estrategia de buffer</string>
|
||||
<string name="settings_buffering_strategy_summary">Para que los cambios surtan efecto, debes reinciar la app.</string>
|
||||
<string name="settings_buffering_strategy_summary">Para que los cambios surtan efecto, debes reiniciar la app.</string>
|
||||
<string name="settings_choose_download_folder">Elige una carpeta para descargar los archivos de música</string>
|
||||
<string name="settings_clear_download_folder">Limpiar la carpeta de descargas</string>
|
||||
<string name="settings_continuous_play_summary">Permite que la música siga reproduciéndose una vez que la lista de reproducción ha terminado, reproduciendo pistas similares</string>
|
||||
<string name="settings_continuous_play_title">Reproducción continua</string>
|
||||
<string name="settings_covers_cache">Tamaño de la caché de portadas de álbumes</string>
|
||||
<string name="settings_data_saving_mode_summary">Evitar descargar las portadas de álbumes para reducir el uso de datos</string>
|
||||
<string name="settings_data_saving_mode_title">Limitr el uso de datos móviles</string>
|
||||
<string name="settings_data_saving_mode_title">Limitar el uso de datos móviles</string>
|
||||
<string name="settings_delete_download_storage_summary">Al continuar se eliminarán de forma irreversible todos los elementos guardados.</string>
|
||||
<string name="settings_delete_download_storage_title">Eliminar elementos guardados</string>
|
||||
<string name="settings_download_storage_title">Almacenamiento de descargas</string>
|
||||
@@ -325,21 +332,22 @@
|
||||
<string name="settings_max_bitrate_wifi">Tasa de bits en Wi-Fi</string>
|
||||
<string name="settings_media_cache">Tamaño de la caché multimedia</string>
|
||||
<string name="settings_music_directory">Mostrar carpetas de música</string>
|
||||
<string name="settings_music_directory_summary">Si está habilitada, se mostrará la sección de carpetas de música. Tenga en cuenta que para que la navegación funcione correctamente, el servidor debe soportar esta característica.</string>
|
||||
<string name="settings_music_directory_summary">Si se habilita, se mostrará la sección de carpetas de música. Tenga en cuenta que para que la navegación funcione correctamente, el servidor debe soportar esta característica.</string>
|
||||
<string name="settings_podcast">Mostrar pódcasts</string>
|
||||
<string name="settings_podcast_summary">Si está habilitada, se mostrará la sección de pódcasts. Reinicia la aplicación para que los cambios surtan efecto.</string>
|
||||
<string name="settings_podcast_summary">Si se habilita, se mostrará la sección de pódcasts. Reinicia la aplicación para que los cambios surtan efecto.</string>
|
||||
<string name="settings_playlist_sort">Ordenar listas de reproducción</string>
|
||||
<string name="settings_audio_quality">Mostrar calidad de audio</string>
|
||||
<string name="settings_audio_quality_summary">La tasa de bits y el formato de audio se mostrarán para cada pista de audio.</string>
|
||||
<string name="settings_song_rating_summary">Si está habilitada, muestra la valoración de la pista como barra de 5 estrellas en la página del control de reproducción.\n\n*Requiere reiniciar la aplicación</string>
|
||||
<string name="settings_song_rating_summary">Si se habilita, muestra la valoración de la pista como barra de 5 estrellas en la página del control de reproducción.\n\n*Requiere reiniciar la aplicación</string>
|
||||
<string name="settings_item_rating">Mostrar valoración de los elementos</string>
|
||||
<string name="settings_queue_syncing_title">Sincronizar cola de reproducción para este usuario</string>
|
||||
<string name="settings_show_mini_shuffle_button_summary">Si está habilitada, muestra el botón de reproducción aleatoria y oculta el botón de «Favoritos» en el minirreproductor</string>
|
||||
<string name="settings_show_mini_shuffle_button_summary">Si se habilita, muestra el botón de reproducción aleatoria y oculta el botón de «Favoritos» en el minirreproductor.</string>
|
||||
<string name="settings_radio">Mostrar emisoras de radio</string>
|
||||
<string name="settings_auto_download_lyrics_summary">Descargar las letras automáticamente cuando estén disponibles para que se puedan mostrar cuando no hay conexión.</string>
|
||||
<string name="settings_replay_gain">Configurar el modo de ganancia de reproducción</string>
|
||||
<string name="settings_rounded_corner">Esquinas redondeadas</string>
|
||||
<string name="settings_rounded_corner_size">Tamaño de las esquinas</string>
|
||||
<string name="settings_rounded_corner_summary">Si está habilitada, establece un ángulo de curvatura para todas las portadas de álbumes. Los cambios se aplicarán después de reiniciar la app.</string>
|
||||
<string name="settings_rounded_corner_summary">Si se habilita, establece un ángulo de curvatura para todas las portadas de álbumes. Los cambios se aplicarán después de reiniciar la app.</string>
|
||||
<string name="settings_scan_title">Escanear biblioteca</string>
|
||||
<string name="streaming_cache_storage_dialog_summary">Cambiar la ubicación de los archivos en caché a otro almacenamiento puede causar el borrado de todos los archivos en caché en el anterior almacenamiento.</string>
|
||||
<string name="streaming_cache_storage_dialog_title">Seleccióna un tipo de almacenamiento</string>
|
||||
@@ -368,10 +376,10 @@
|
||||
<string name="settings_summary_streaming_cache_size">%1$s\nEn uso: %2$s MiB</string>
|
||||
<string name="undraw_url">https://undraw.co/</string>
|
||||
<string name="track_info_track_number">Número de pista</string>
|
||||
<string name="settings_item_rating_summary">Si está habilitada, se mostrará la valoración del elemento y si está marcado como favorito.</string>
|
||||
<string name="settings_item_rating_summary">Si se habilita, se mostrará la valoración del elemento y si está marcado como favorito.</string>
|
||||
<string name="settings_queue_syncing_countdown">Temporizador de sincronización</string>
|
||||
<string name="settings_queue_syncing_summary">Si está habilitada, el usuario podrá guardar la cola de reproducción y restaurarla cuando abra la aplicación.</string>
|
||||
<string name="settings_radio_summary">Si está habilitada, se mostrará la sección de emisoras de radio. Reinicia la app para que los cambios surtan efecto.</string>
|
||||
<string name="settings_queue_syncing_summary">Si se habilita, el usuario podrá guardar la cola de reproducción y restaurarla cuando abra la aplicación.</string>
|
||||
<string name="settings_radio_summary">Si se habilita, se mostrará la sección de emisoras de radio. Reinicia la app para que los cambios surtan efecto.</string>
|
||||
<string name="settings_rounded_corner_size_summary">Establece la proporción del ángulo de curvatura.</string>
|
||||
<string name="track_info_dialog_title">Información de la pista</string>
|
||||
<string name="track_info_disc_number">Número de disco</string>
|
||||
@@ -443,10 +451,10 @@
|
||||
<string name="starred_sync_dialog_title">Sincronizar las pistas destacadas</string>
|
||||
<string name="starred_album_sync_dialog_title">Sincronizar álbumes favoritos</string>
|
||||
<string name="streaming_cache_storage_dialog_sub_summary">Para que los cambios tengan efecto, reinicia la app.</string>
|
||||
<string name="settings_summary_transcoding_download">Descarga los archivos multimedia transcodificados. Si esta opción está habilitada, no se usará el endpoint de descarga, sino las siguientes opciones.\n\nSi el formato de transcodificación para las descargas se establece en \"Descarga directa\", no se modificará la tasa de bits del archivo.</string>
|
||||
<string name="settings_summary_transcoding_download">Descarga los archivos multimedia transcodificados. Si se habilita, no se usará el endpoint de descarga, sino las siguientes opciones.\n\nSi el formato de transcodificación para las descargas se establece en \"Descarga directa\", no se modificará la tasa de bits del archivo.</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">Cuando el archivo se transcodifica en tiempo real, el cliente normalmente no muestra la duración de la pista. Es posible solicitar a los servidores que soporten esta característica, que calculen la duración de la pista que se está reproduciendo, pero los tiempos de respuesta podrían aumentar.</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_title">Sincronizar álbumes favoritos para uso sin conexión</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_summary">Si está habilitada, las pistas destacadas se descargarán para uso sin conexión.</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_summary">Si se habilita, las pistas destacadas se descargarán para uso sin conexión.</string>
|
||||
<string name="track_info_summary_downloaded_file">El archivo se ha descargado usando las APIs de Subsonic. El códec y la tasa de bits del archivo se mantienen sin cambios respecto al archivo de origen.</string>
|
||||
<string name="track_info_summary_full_transcode">La aplicación pedirá al servidor transcodificar el archivo y modificar su tasa de bits. El códec pedido por el usuario es %1$s, con una tasa de bits de %2$s. Cualquier cambio en el códec y tasa de bits del archivo en el formato elegido será manejado por el servidor, que puede, o no, soportar esta operación.</string>
|
||||
<string name="track_info_summary_original_file">La aplicación solo leerá el archivo original ofrecido por el servidor. La app pedirá al servidor, de forma explícita, el archivo sin transcodificar con la tasa de bits de origen.</string>
|
||||
@@ -456,7 +464,7 @@
|
||||
<string name="settings_song_rating">Mostrar valoración de las pistas</string>
|
||||
<string name="home_sync_starred_albums_title">Sincronizar álbumes favoritos</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_title">Sincronizar artistas destacados para uso sin conexión</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_summary">Si está habilitada, los álbumes favoritos se descargarán para uso sin conexión.</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_summary">Si se habilita, los álbumes favoritos se descargarán para uso sin conexión.</string>
|
||||
<string name="starred_artist_sync_dialog_title">Sincronizar artistas destacados</string>
|
||||
<string name="starred_album_sync_dialog_summary">Descargar los álbumes favoritos puede consumir una gran cantidad de datos.</string>
|
||||
<string name="equalizer_fragment_title">Ecualizador</string>
|
||||
@@ -479,6 +487,7 @@
|
||||
<string name="widget_content_desc_prev">Pista anterior</string>
|
||||
<string name="download_refresh_no_directory">Establece una carpeta de descarga para actualizar tus descargas</string>
|
||||
<string name="home_sync_starred_artists_title">Sincronizar artistas destacados</string>
|
||||
<string name="player_queue_load_queue">Cargar cola de reproducción</string>
|
||||
<string name="player_lyrics_download_content_description">Descargar letras para uso sin conexión</string>
|
||||
<string name="player_lyrics_downloaded_content_description">Letras descargadas para uso sin conexión</string>
|
||||
<string name="player_lyrics_download_success">Letra guardada para uso sin conexión</string>
|
||||
@@ -488,13 +497,13 @@
|
||||
<string name="settings_show_mini_shuffle_button">Mostrar el botón «Aleatorio»</string>
|
||||
<string name="settings_auto_download_lyrics">Descargar automáticamente las letras</string>
|
||||
<string name="starred_artist_sync_dialog_summary">Descargar los artistas destacados podría consumir una gran cantidad de datos.</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_summary">Si está habilitada, los artistas destacados se descargarán para uso sin conexión.</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_summary">Si se habilita, los artistas destacados se descargarán para uso sin conexión.</string>
|
||||
<string name="widget_time_elapsed_placeholder">0:00</string>
|
||||
<string name="exo_controls_heart_off_description">Eliminar de favoritos</string>
|
||||
<string name="asset_link_chip_text">%1$s • %2$s</string>
|
||||
<string name="asset_link_copied_toast">Copiado %1$s al portapapeles</string>
|
||||
<string name="settings_album_detail">Mostrar los detalles del álbum</string>
|
||||
<string name="settings_album_detail_summary">Si está habilitada, muestra los detalles del álbum, como el género, el número de pistas, etc. en la página de álbum</string>
|
||||
<string name="settings_album_detail_summary">Si se habilita, muestra los detalles del álbum, como el género, el número de pistas, etc. en la página de álbum</string>
|
||||
<string name="asset_link_clipboard_label">Enlace de recurso de Tempus</string>
|
||||
<string name="asset_link_label_song">UID de la pista</string>
|
||||
<string name="asset_link_label_album">UID del álbum</string>
|
||||
@@ -509,6 +518,14 @@
|
||||
<string name="asset_link_error_artist">No se ha podido abrir el artista</string>
|
||||
<string name="asset_link_error_playlist">No se ha podido abrir la lista de reproducción</string>
|
||||
<string name="settings_github_update">Actualizaciones</string>
|
||||
<string name="bottom_sheet_generating_instant_mix">Generando mix instantáneo…</string>
|
||||
<string name="player_queue_save_to_playlist">Guardar cola en una lista de reproducción</string>
|
||||
<string name="radio_editor_dialog_added">Emisora de radio añadida</string>
|
||||
<string name="settings_artist_sort_by_album_count">Ordenar artistas por número de álbumes</string>
|
||||
<string name="settings_artist_sort_by_album_count_summary">Ordena los artistas por número de álbumes si la opción está habilitada. Si no, los ordena por nombre.</string>
|
||||
<string name="settings_artist_sort_by_album_count_summary">Si se habilita, ordena los artistas por número de álbumes. En caso contrario, se ordenan por nombre.</string>
|
||||
<string name="folder_play_collecting">Obteniendo pistas de la carpeta…</string>
|
||||
<string name="folder_play_playing">Reproduciendo %d pistas</string>
|
||||
<string name="folder_play_no_songs">No se encontraron pistas en la carpeta</string>
|
||||
<string name="search_sort_title">Ordenar las búsquedas recientes cronológicamente</string>
|
||||
<string name="search_sort_summary">Si se habilita, se ordenan las búsquedas en orden cronológico. En caso contrario, se ordenan por nombre.</string>
|
||||
</resources>
|
||||
@@ -39,6 +39,7 @@
|
||||
<string name="artist_list_page_downloaded">Artistes téléchargés</string>
|
||||
<string name="artist_list_page_starred">Artistes favoris</string>
|
||||
<string name="artist_list_page_title">Artistes</string>
|
||||
<string name="artist_no_artist_info_toast">Pas d\'autre information sur l\'artiste</string>
|
||||
<string name="artist_page_radio_button">Radio</string>
|
||||
<string name="artist_page_shuffle_button">Mélanger</string>
|
||||
<string name="artist_page_switch_layout_button">Changer la disposition</string>
|
||||
@@ -51,6 +52,8 @@
|
||||
<string name="battery_optimization_negative_button">Ignorer</string>
|
||||
<string name="battery_optimization_neutral_button">Ne pas me redemander</string>
|
||||
<string name="battery_optimization_positive_button">Désactiver</string>
|
||||
<string name="bottom_sheet_generating_instant_mix">Génération du mix instantané…</string>
|
||||
<string name="bottom_sheet_problem_generating_instant_mix">Echec de récupération de pistes du serveur Subsonic.</string>
|
||||
<string name="connection_alert_dialog_negative_button">Annuler</string>
|
||||
<string name="connection_alert_dialog_neutral_button">Activer l\'économie de données</string>
|
||||
<string name="connection_alert_dialog_positive_button">OK</string>
|
||||
@@ -68,6 +71,7 @@
|
||||
<string name="download_directory_dialog_positive_button">Télécharger</string>
|
||||
<string name="download_directory_dialog_summary">Toutes les pistes dans ce dossier seront téléchargées. Les pistes dans les sous-dossiers ne seront pas téléchargées.</string>
|
||||
<string name="download_directory_dialog_title">Télécharger toutes les pistes.</string>
|
||||
<string name="download_directory_set">Répertoire de téléchargement de la musique</string>
|
||||
<string name="download_info_empty_subtitle">Dès que vous téléchargerez une musique, vous la trouverez ici</string>
|
||||
<string name="download_info_empty_title">Aucun téléchargement pour l\'instant</string>
|
||||
<string name="download_item_multiple_subtitle_formatter">%1$s • %2$s éléments</string>
|
||||
@@ -78,7 +82,15 @@
|
||||
<string name="download_storage_dialog_title">Sélectionnez l\'option de stockage</string>
|
||||
<string name="download_storage_external_dialog_positive_button">Externe</string>
|
||||
<string name="download_storage_internal_dialog_negative_button">Interne</string>
|
||||
<string name="download_storage_directory_dialog_neutral_button">Répertoire</string>
|
||||
<string name="download_title_section">Téléchargements</string>
|
||||
<string name="download_refresh_no_directory">Sélectionner un répertoire pour rafraîchir vos téléchargements.</string>
|
||||
<string name="download_refresh_no_changes">Aucun téléchargement manquant détecté.</string>
|
||||
<plurals name="download_refresh_removed">
|
||||
<item quantity="one">%d téléchargement manquant retiré.</item>
|
||||
<item quantity="other">%d téléchargements manquants retirés.</item>
|
||||
</plurals>
|
||||
<string name="download_refresh_button_content_description">Rafraîchir les téléchargements</string>
|
||||
<string name="downloaded_bottom_sheet_add_to_queue">Ajouter à la liste d\'attente</string>
|
||||
<string name="downloaded_bottom_sheet_play_next">Lire juste après</string>
|
||||
<string name="downloaded_bottom_sheet_remove">Retirer</string>
|
||||
@@ -88,6 +100,9 @@
|
||||
<string name="error_required">Requis</string>
|
||||
<string name="error_server_prefix">préfixe http ou https requis</string>
|
||||
<string name="exo_download_notification_channel_name">Téléchargements</string>
|
||||
<string name="exo_controls_heart_off_description">Désactiver les boutons Cœur</string>
|
||||
<string name="exo_controls_heart_on_description">Activer les boutons Cœur</string>
|
||||
<string name="cast_expanded_controller_loading">Chargement…</string>
|
||||
<string name="filter_info_selection">Sélectionnez deux filtres ou plus</string>
|
||||
<string name="filter_title">Filtrer</string>
|
||||
<string name="filter_artist">Filtrer par artiste</string>
|
||||
@@ -118,7 +133,13 @@
|
||||
<string name="home_sync_starred_subtitle">Télécharger ces titres peut entraîner une utilisation importante de données</string>
|
||||
<string name="home_sync_starred_title">On dirait qu\'il y a des titres favoris à synchroniser</string>
|
||||
<string name="home_sync_starred_albums_title">Synchroniser les albums favoris</string>
|
||||
<string name="home_sync_starred_albums_subtitle">Les albums marqués d\'une étoile seront disponibles hors-ligne</string>
|
||||
<string name="home_sync_starred_albums_subtitle">Les albums favoris seront disponibles hors-ligne</string>
|
||||
<string name="home_sync_starred_artists_title">Synchroniser les artistes favoris</string>
|
||||
<string name="home_sync_starred_artists_subtitle">Pour certains de vos artistes favoris, il reste des titres à télécharger</string>
|
||||
<plurals name="home_sync_starred_songs_count">
|
||||
<item quantity="one">%d titre doit être synchronisé</item>
|
||||
<item quantity="other">%d titres doivent être synchronisés</item>
|
||||
</plurals>
|
||||
<string name="home_title_best_of">Best of</string>
|
||||
<string name="home_title_discovery">Découverte</string>
|
||||
<string name="home_title_discovery_shuffle_all_button">Tout mélanger</string>
|
||||
@@ -184,6 +205,7 @@
|
||||
<string name="menu_sort_artist">Artiste</string>
|
||||
<string name="menu_sort_name">Nom</string>
|
||||
<string name="menu_sort_random">Aléatoire</string>
|
||||
<string name="menu_sort_album_count">Nombre d\'albums</string>
|
||||
<string name="menu_sort_recently_added">Récemment ajoutés</string>
|
||||
<string name="menu_sort_recently_played">Récemment lus</string>
|
||||
<string name="menu_sort_most_played">Plus lus</string>
|
||||
@@ -195,6 +217,12 @@
|
||||
<string name="player_playback_speed">%1$.2fx</string>
|
||||
<string name="player_queue_clean_all_button">Vider la file d\'attente</string>
|
||||
<string name="player_queue_save_queue_success">File d\'attente sauvegardée</string>
|
||||
<string name="player_queue_save_to_playlist">Sauvegarder la file d\'attente dans une playlist</string>
|
||||
<string name="player_queue_load_queue">Charger la file d\'attente</string>
|
||||
<string name="player_lyrics_download_content_description">Télécharger les paroles pour lecture hors-ligne</string>
|
||||
<string name="player_lyrics_downloaded_content_description">Paroles téléchargées pour lecture hors-ligne</string>
|
||||
<string name="player_lyrics_download_success">Paroles sauvegardées pour lecture hors-ligne.</string>
|
||||
<string name="player_lyrics_download_failure">Impossible de télécharger les paroles.</string>
|
||||
<string name="player_server_priority">Priorité serveur</string>
|
||||
<string name="player_unknown_format">Format inconnu</string>
|
||||
<string name="player_transcoding">Transcodage</string>
|
||||
@@ -207,6 +235,9 @@
|
||||
<string name="playlist_chooser_dialog_title">Ajouter à une playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_success">Titre ajouté à la playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_failure">Échec d\'ajout du titre à la playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_all_skipped">Tous les titres ont été traités comme des doublons et ignorés</string>
|
||||
<string name="playlist_chooser_dialog_visibility_public">Publique</string>
|
||||
<string name="playlist_chooser_dialog_visibility_private">Privé</string>
|
||||
<string name="playlist_counted_tracks">%1$d titres • %2$s</string>
|
||||
<string name="playlist_duration">Durée • %1$s</string>
|
||||
<string name="playlist_editor_dialog_action_delete_toast">Appui long pour supprimer</string>
|
||||
@@ -228,13 +259,14 @@
|
||||
<string name="podcast_channel_catalogue_title_expanded">Parcourir les chaînes</string>
|
||||
<string name="podcast_channel_editor_dialog_hint_rss_url">Url RSS</string>
|
||||
<string name="podcast_channel_editor_dialog_title">Chaîne</string>
|
||||
<string name="podcast_channel_not_supported_snackbar">Ce serveur ne supporte pas les podcasts.</string>
|
||||
<string name="podcast_channel_page_title_description_section">Description</string>
|
||||
<string name="podcast_channel_page_title_episode_section">Épisodes</string>
|
||||
<string name="podcast_channel_page_title_no_episode_available">Aucun épisode disponible</string>
|
||||
<string name="podcast_episode_download_request_snackbar">Votre requête a été envoyée au serveur</string>
|
||||
<string name="podcast_info_empty_button">Cliquez pour cacher la section\nLes changements seront visibles au redémarrage de l\'app</string>
|
||||
<string name="podcast_info_empty_subtitle">Dès que vous ajouterez une chaîne, vous la retrouverez ici</string>
|
||||
<string name="podcast_info_empty_title">Aucun podcast trouvé!</string>
|
||||
<string name="podcast_info_empty_title">Aucun podcast trouvé !</string>
|
||||
<string name="podcast_release_date_duration_formatter">%1$s • %2$s</string>
|
||||
<string name="radio_editor_dialog_hint_homepage_url">URL de la page d\'accueil de la radio</string>
|
||||
<string name="radio_editor_dialog_hint_name">Nom de la radio</string>
|
||||
@@ -242,10 +274,13 @@
|
||||
<string name="radio_editor_dialog_negative_button">Annuler</string>
|
||||
<string name="radio_editor_dialog_neutral_button">Supprimer</string>
|
||||
<string name="radio_editor_dialog_positive_button">Enregistrer</string>
|
||||
<string name="radio_editor_dialog_title">Station Radio Internet</string>
|
||||
<string name="radio_editor_dialog_added">Station de radio ajoutée</string>
|
||||
<string name="radio_editor_dialog_updated">Station de radio modifiée</string>
|
||||
<string name="radio_editor_dialog_title">Station de radio Internet</string>
|
||||
<string name="radio_station_info_empty_button">Cliquez pour cacher la section\nLes changements seront visibles au redémarrage de l\'app</string>
|
||||
<string name="radio_station_info_empty_subtitle">Dès que vous ajouterez une station radio, vous la retrouverez ici</string>
|
||||
<string name="radio_station_info_empty_title">Aucune station trouvée!</string>
|
||||
<string name="radio_station_info_empty_title">Aucune station trouvée !</string>
|
||||
<string name="radio_dialog_not_supported_snackbar">Ce serveur ne supporte pas la gestion de radios Internet.</string>
|
||||
<string name="rating_dialog_negative_button">Annuler</string>
|
||||
<string name="rating_dialog_positive_button">Enregistrer</string>
|
||||
<string name="rating_dialog_title">Noter</string>
|
||||
@@ -273,6 +308,8 @@
|
||||
<string name="settings_about_summary">Tempus est un client open source et léger pour Subsonic, développé et compilé nativement pour Android.</string>
|
||||
<string name="settings_about_title">À propos</string>
|
||||
<string name="settings_always_on_display">Toujours visible</string>
|
||||
<string name="settings_allow_playlist_duplicates">Autoriser l\'ajout de doublons à une playlist</string>
|
||||
<string name="settings_allow_playlist_duplicates_summary">Si activé, les doublons ne seront pas détectés à l\'ajout d\'un titre à une playlist.</string>
|
||||
<string name="settings_audio_transcode_download_format">Format de transcodage</string>
|
||||
<string name="settings_audio_transcode_download_priority_summary">Si activé, Tempus ne forcera pas le téléchargement de la piste avec les paramètres de transcodage ci-dessous.</string>
|
||||
<string name="settings_audio_transcode_download_priority_title">Prioriser les paramètres du serveurs, utilisés pour le streaming, dans les téléchargements</string>
|
||||
@@ -288,6 +325,8 @@
|
||||
<string name="settings_audio_transcode_priority_toast">La priorité au transcodage de la piste est donnée au serveur</string>
|
||||
<string name="settings_buffering_strategy">Stratégie de mise en mémoire tampon</string>
|
||||
<string name="settings_buffering_strategy_summary">Redémarrez l\'application pour appliquer les changements.</string>
|
||||
<string name="settings_choose_download_folder">Sélectionner un répertoire pour le téléchargement des fichiers de musique</string>
|
||||
<string name="settings_clear_download_folder">Vider le répertoire de téléchargement</string>
|
||||
<string name="settings_continuous_play_summary">Permet de prolonger la lecture après la fin d\'une playlist avec des titres similaires</string>
|
||||
<string name="settings_continuous_play_title">Lecture continue</string>
|
||||
<string name="settings_covers_cache">Taille du cache des illustrations</string>
|
||||
@@ -296,16 +335,26 @@
|
||||
<string name="settings_delete_download_storage_summary">Continuer entraînera la suppression irréversible de tous les éléments sauvegardés.</string>
|
||||
<string name="settings_delete_download_storage_title">Supprimer les éléments sauvegardés</string>
|
||||
<string name="settings_download_storage_title">Stockage des téléchargements</string>
|
||||
<string name="settings_system_equalizer_summary">Ajuster les paramètres audios</string>
|
||||
<string name="settings_download_folder_cleared">Répertoire de téléchargement vidé.</string>
|
||||
<string name="settings_download_folder_set">Répertoire de téléchargement réglé</string>
|
||||
<string name="settings_set_download_folder">Régler le répertoire de téléchargement</string>
|
||||
<string name="settings_system_equalizer_summary">Ajuster les paramètres audio</string>
|
||||
<string name="settings_system_equalizer_title">Égaliseur du système</string>
|
||||
<string name="settings_github_link">https://github.com/eddyizm/tempus</string>
|
||||
<string name="settings_github_summary">Suivre le développement</string>
|
||||
<string name="settings_github_title">Github</string>
|
||||
<string name="settings_support_discussion_link">https://github.com/eddyizm/tempus/discussions</string>
|
||||
<string name="settings_github_update">Mises à jour</string>
|
||||
<string name="settings_github_update_title">Vérifier les mises à jour sur Github</string>
|
||||
<string name="settings_github_update_summary">Si vous utilisez l\'app publiée sur Github, elle vérifiera par défaut les sorties de nouvelles versions. Cliquer ici pour désactiver les vérifications automatiques</string>
|
||||
<string name="settings_support_summary">Rejoindre les discussions et le support de la communauté</string>
|
||||
<string name="settings_support_title">Support utilisateur</string>
|
||||
<string name="settings_scan_result">Analyse : comptage de %1$d pistes</string>
|
||||
<string name="settings_image_size">Définir la résolution des images</string>
|
||||
<string name="settings_language">Langue</string>
|
||||
<string name="settings_logout_title">Se déconnecter</string>
|
||||
<string name="settings_max_bitrate_download">Débit binaire pour les téléchargements</string>
|
||||
<string name="settings_max_bitrate_mobile">Débit binaire en données mobile</string>
|
||||
<string name="settings_max_bitrate_mobile">Débit binaire en données mobiles</string>
|
||||
<string name="settings_max_bitrate_wifi">Débit binaire en Wi-Fi</string>
|
||||
<string name="settings_media_cache">Taille du cache des fichiers audio</string>
|
||||
<string name="settings_music_directory">Afficher les dossiers</string>
|
||||
@@ -320,14 +369,18 @@
|
||||
<string name="settings_item_rating_summary">Si activé, la note et le statut de mise en favori de l\'élément seront affichés.</string>
|
||||
<string name="settings_queue_syncing_countdown">Minuteur de synchronisation</string>
|
||||
<string name="settings_queue_syncing_summary">Si activé, l\'utilisateur pourra sauvegarder sa file d\'attente et la recharger au démarrage de l\'application.</string>
|
||||
<string name="settings_queue_syncing_title">Synchroniser la file d\'attente pour cet utilisateur</string>
|
||||
<string name="settings_queue_syncing_title">Synchroniser la file d\'attente pour cet utilisateur [fonctionnalité en évolution]</string>
|
||||
<string name="settings_show_mini_shuffle_button">Afficher le bouton Mélanger</string>
|
||||
<string name="settings_show_mini_shuffle_button_summary">Si activé, le bouton Mélanger sera visible et le bouton Cœur caché sur le mini-lecteur</string>
|
||||
<string name="settings_radio">Voir les radios</string>
|
||||
<string name="settings_radio_summary">Si activé, rend visible la section Radio</string>
|
||||
<string name="settings_radio_summary">Si activé, rend visible la section Radio. Redémarrez l\'application pour appliquer ce paramètre.</string>
|
||||
<string name="settings_auto_download_lyrics">Téléchargement automatique des paroles</string>
|
||||
<string name="settings_auto_download_lyrics_summary">Ce paramètre active la sauvegarde automatique des paroles pour affichage hors-ligne, si elles sont disponibles.</string>
|
||||
<string name="settings_replay_gain">Ajuster le Replay Gain</string>
|
||||
<string name="settings_rounded_corner">Coins arrondis</string>
|
||||
<string name="settings_rounded_corner_size">Taille des arrondis</string>
|
||||
<string name="settings_rounded_corner_size_summary">Définit l\'ampleur de l\'angle de courbure.</string>
|
||||
<string name="settings_rounded_corner_summary">Si activé, arrondi les angles des illustrations. Les modifications prendront effet au redémarrage.</string>
|
||||
<string name="settings_rounded_corner_summary">Si activé, arrondit les angles des illustrations. Les modifications prendront effet au redémarrage.</string>
|
||||
<string name="settings_scan_title">Scanner la bibliothèque</string>
|
||||
<string name="settings_scrobble_title">Activer le scrobbling</string>
|
||||
<string name="settings_system_language">Langue du système</string>
|
||||
@@ -344,13 +397,16 @@
|
||||
<string name="settings_summary_transcoding">Le mode de transcodage à prioriser. Si réglé sur \"Lecture directe\", le débit binaire du fichier ne sera pas modifié.</string>
|
||||
<string name="settings_summary_transcoding_download">Télécharge les médias transcodés. Si activé, les paramètres de transcodage suivants seront utilisés pour les téléchargements.\n\n Si le format de transcodage est reglé à \"Téléchargement direct\", le débit binaire du fichier ne sera pas modifé.</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">Quand le fichier est transcodé à la volée, en général, le client n\'affiche pas la durée de la piste. Il est possible de demander aux serveurs qui le supportent d\'estimer la durée de la piste écoutée, mais les temps de réponses peuvent être plus longs.</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_summary">Si activé, les albums favoris seront téléchargés pour l\'écoute hors-ligne</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_summary">Si activé, les titres des artistes favoris seront téléchargés pour l\'écoute hors-ligne.</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_title">Synchronisation des artistes favoris pour écoute hors-ligne</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_summary">Si activé, les albums favoris seront téléchargés pour l\'écoute hors-ligne.</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_title">Synchronisation des albums favoris pour écoute hors-ligne</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_summary">Si activé, les pistes favorites seront téléchargées pour l\'écoute hors-ligne</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_summary">Si activé, les pistes favorites seront téléchargées pour l\'écoute hors-ligne.</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_title">Synchronisation des pistes favorites pour écoute hors-ligne</string>
|
||||
<string name="settings_theme">Thème</string>
|
||||
<string name="settings_title_data">Données</string>
|
||||
<string name="settings_title_general">Géneral</string>
|
||||
<string name="settings_title_playlist">Playlist</string>
|
||||
<string name="settings_title_rating">Note</string>
|
||||
<string name="settings_title_replay_gain">Replay Gain</string>
|
||||
<string name="settings_title_scrobble">Scrobble</string>
|
||||
@@ -369,7 +425,24 @@
|
||||
<string name="share_bottom_sheet_delete">Supprimer le partage</string>
|
||||
<string name="share_bottom_sheet_update">Mettre à jour le partage</string>
|
||||
<string name="share_subtitle_item">Date d\'expiration : %1$s</string>
|
||||
<string name="share_no_expiration">Jamais</string>
|
||||
<string name="share_unsupported_error">Le partage n\'est pas supporté ou pas activé</string>
|
||||
<string name="asset_link_clipboard_label">Lien ressource Tempus</string>
|
||||
<string name="asset_link_label_song">UID titre</string>
|
||||
<string name="asset_link_label_album">UID album</string>
|
||||
<string name="asset_link_label_artist">UID artiste</string>
|
||||
<string name="asset_link_label_playlist">UID playlist</string>
|
||||
<string name="asset_link_label_genre">UID genre</string>
|
||||
<string name="asset_link_label_year">UID année</string>
|
||||
<string name="asset_link_label_unknown">UID ressource</string>
|
||||
<string name="asset_link_error_unsupported">Lien ressource non supportée</string>
|
||||
<string name="asset_link_error_song">Echec d\'accès au titre</string>
|
||||
<string name="asset_link_error_album">Echec d\'accès à l\'album</string>
|
||||
<string name="asset_link_error_artist">Echec d\'accès à l\'artiste</string>
|
||||
<string name="asset_link_error_playlist">Echec d\'accès à la playlist</string>
|
||||
<string name="asset_link_chip_text">%1$s • %2$s</string>
|
||||
<string name="asset_link_copied_toast">%1$s ressource(s) copiée(s) dans le presse-papier</string>
|
||||
<string name="asset_link_debug_toast">Lien ressource : %1$s</string>
|
||||
<string name="share_update_dialog_hint_description">Description</string>
|
||||
<string name="share_update_dialog_hint_expiration_date">Date d\'expiration</string>
|
||||
<string name="share_update_dialog_negative_button">Annuler</string>
|
||||
@@ -400,7 +473,9 @@
|
||||
<string name="starred_sync_dialog_positive_button">Continuer et télécharger</string>
|
||||
<string name="starred_sync_dialog_summary">Le téléchargement des titres favoris pourrait consommer beaucoup de données.</string>
|
||||
<string name="starred_sync_dialog_title">Synchroniser les titres favoris</string>
|
||||
<string name="starred_album_sync_dialog_summary">Le téléchargement des titres favoris pourrait consommer beaucoup de données.</string>
|
||||
<string name="starred_artist_sync_dialog_summary">Le téléchargement des titres d\'artistes favoris pourrait consommer beaucoup de données.</string>
|
||||
<string name="starred_artist_sync_dialog_title">Synchroniser les artistes favoris</string>
|
||||
<string name="starred_album_sync_dialog_summary">Le téléchargement des albums favoris pourrait consommer beaucoup de données.</string>
|
||||
<string name="starred_album_sync_dialog_title">Synchroniser les albums favoris</string>
|
||||
<string name="streaming_cache_storage_dialog_sub_summary">Veuillez redémarrer l\'app pour appliquer les changements.</string>
|
||||
<string name="streaming_cache_storage_dialog_summary">Modifier le chemin de stockage des fichiers mis en cache risque de provoquer la suppression de tous les fichiers précédemment mis en cache dans le nouvel espace de stockage.</string>
|
||||
@@ -436,14 +511,45 @@
|
||||
<string name="undraw_page">unDraw</string>
|
||||
<string name="undraw_thanks">Un grand merci à unDraw, nous n\'aurions pas pu rendre cette application aussi belle sans leurs illustrations.</string>
|
||||
<string name="undraw_url">https://undraw.co/</string>
|
||||
<string name="widget_label">Widget Tempus</string>
|
||||
<string name="widget_not_playing">Pas de lecture</string>
|
||||
<string name="widget_placeholder_subtitle">Ouvrir Tempus</string>
|
||||
<string name="widget_time_elapsed_placeholder">0:00</string>
|
||||
<string name="widget_time_duration_placeholder">0:00</string>
|
||||
<string name="widget_content_desc_album_art">Couverture de l\'album</string>
|
||||
<string name="widget_content_desc_play_pause">Lecture ou pause</string>
|
||||
<string name="widget_content_desc_next">Piste suivante</string>
|
||||
<string name="widget_content_desc_prev">Piste précédente</string>
|
||||
<string name="widget_content_desc_shuffle">Activer/désactiver le mélange</string>
|
||||
<string name="widget_content_desc_repeat">Changer le mode de répétition</string>
|
||||
<plurals name="home_sync_starred_albums_count">
|
||||
<item quantity="one">%d album à synchroniser</item>
|
||||
<item quantity="other">%d albums à synchroniser</item>
|
||||
</plurals>
|
||||
<plurals name="home_sync_starred_artists_count">
|
||||
<item quantity="one">%d artiste à synchroniser</item>
|
||||
<item quantity="other">%d artistes à synchroniser</item>
|
||||
</plurals>
|
||||
<plurals name="songs_download_started">
|
||||
<item quantity="one">Téléchargement de %d titre</item>
|
||||
<item quantity="other">Téléchargement de %d titres</item>
|
||||
</plurals>
|
||||
<string name="equalizer_fragment_title">Égaliseur</string>
|
||||
<string name="equalizer_reset">Réinitialiser</string>
|
||||
<string name="equalizer_enable">Activer</string>
|
||||
<string name="equalizer_not_supported">Non supporté sur cet appareil</string>
|
||||
<string name="settings_app_equalizer">Égaliseur</string>
|
||||
<string name="settings_app_equalizer_summary">Ouvrir l\'égaliseur intégré</string>
|
||||
|
||||
<string name="settings_album_detail">Afficher les détails de l\'album</string>
|
||||
<string name="settings_album_detail_summary">Si activé, affiche les détails de l\'album tels que le genre, le nombre de titres etc. sur l\'écran de l\'album</string>
|
||||
<string name="settings_artist_sort_by_album_count">Trier les artistes par nombre d\'albums</string>
|
||||
<string name="settings_artist_sort_by_album_count_summary">Si activé, les artistes seront triés par ordre des nombres d\'albums. Si désactivé, ils le seront par ordre alphabétique.</string>
|
||||
|
||||
<string name="folder_play_collecting">Collecte des titres du répertoire…</string>
|
||||
<string name="folder_play_playing">Lecture de %d titres</string>
|
||||
<string name="folder_play_no_songs">Aucun titre trouvé dans le répertoire</string>
|
||||
|
||||
<string name="search_sort_title">Trier les recherches récentes par ordre chronologique</string>
|
||||
<string name="search_sort_summary">Si activé, les recherches récentes seront triées par ordre chronologique. Si désactivé, elles le seront par ordre alphabétique.</string>
|
||||
</resources>
|
||||
|
||||
@@ -229,6 +229,8 @@
|
||||
<string name="playlist_chooser_dialog_toast_add_success">Aggiunta di un brano alla playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_failure">Impossibile aggiungere un brano alla playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_all_skipped">Tutte le canzoni sono state saltate perché duplicate</string>
|
||||
<string name="playlist_chooser_dialog_visibility_public">Pubblico</string>
|
||||
<string name="playlist_chooser_dialog_visibility_private">Privato</string>
|
||||
<string name="playlist_counted_tracks">%1$d brani • %2$s</string>
|
||||
<string name="playlist_duration">Durata • %1$s</string>
|
||||
<string name="playlist_editor_dialog_action_delete_toast">Premi a lungo per eliminare</string>
|
||||
|
||||
@@ -239,7 +239,16 @@
|
||||
<item>4</item>
|
||||
<item>8</item>
|
||||
</string-array>
|
||||
|
||||
|
||||
<string-array name="playlist_sort_option_titles">
|
||||
<item>Po nazwie</item>
|
||||
<item>Losowo</item>
|
||||
</string-array>
|
||||
<string-array name="playlist_sort_option_values">
|
||||
<item>ORDER_BY_NAME</item>
|
||||
<item>ORDER_BY_RANDOM</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="skip_min_star_rating_titles">
|
||||
<item>Minimum 0 gwiazdek</item>
|
||||
<item>Minimum 1 gwiazdka</item>
|
||||
@@ -254,4 +263,4 @@
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
<string name="artist_list_page_downloaded">Pobrani wykonawcy</string>
|
||||
<string name="artist_list_page_starred">Wykonawcy oznaczeni gwiazdką</string>
|
||||
<string name="artist_list_page_title">Wykonawcy</string>
|
||||
<string name="artist_no_artist_info_toast">Brak dodatkowych informacji o wykonawcy</string>
|
||||
<string name="artist_page_radio_button">Radio</string>
|
||||
<string name="artist_page_shuffle_button">Odtwarzanie losowe</string>
|
||||
<string name="artist_page_switch_layout_button">Zmień układ</string>
|
||||
@@ -227,6 +228,8 @@
|
||||
<string name="playlist_chooser_dialog_toast_add_success">Dodano piosenki do playlisty</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_failure">Nie udało się dodać piosenek do playlisty</string>
|
||||
<string name="playlist_chooser_dialog_toast_all_skipped">Pominięto wszystkie piosenki jako duplikaty</string>
|
||||
<string name="playlist_chooser_dialog_visibility_public">Publiczna</string>
|
||||
<string name="playlist_chooser_dialog_visibility_private">Prywatna</string>
|
||||
<string name="playlist_counted_tracks">%1$d utworów • %2$s</string>
|
||||
<string name="playlist_duration">Długość • %1$s</string>
|
||||
<string name="playlist_editor_dialog_action_delete_toast">Przytrzymaj aby usunąć</string>
|
||||
@@ -342,6 +345,9 @@
|
||||
<string name="settings_image_size">Rozdzielczość obrazów</string>
|
||||
<string name="settings_language">Język</string>
|
||||
<string name="settings_logout_title">Wyloguj</string>
|
||||
<string name="settings_ping_timeout_title">Timeout pingów serwera</string>
|
||||
<string name="settings_ping_timeout_summary">Timeout lokalnego adresu URL. Domyślnie to 2 sekundy. (Serwer zdalny będzie używał trzykrotności tej wartości maksymalnie do 10 sekund.)</string>
|
||||
<string name="settings_ping_timeout_dialog">Bazowy timeout w sekundach.</string>
|
||||
<string name="settings_max_bitrate_download">Bitrate dla pobierania</string>
|
||||
<string name="settings_max_bitrate_mobile">Bitrate dla danych komórkowych</string>
|
||||
<string name="settings_max_bitrate_wifi">Bitrate dla Wi-Fi</string>
|
||||
@@ -350,6 +356,7 @@
|
||||
<string name="settings_music_directory_summary">Jeżeli włączone, widoczna będzie sekcja z folderami z muzyką. Weź pod uwagę że żeby funkcja nawigacji po folderach działała poprawnie, serwer musi wspierać tę funkcję.</string>
|
||||
<string name="settings_podcast">Pokazuj podcasty</string>
|
||||
<string name="settings_podcast_summary">Jeżeli włączone, widoczna będzie sekcja z podcastami. Zrestartuj aplikację aby, zmiany przyniosły pełny efekt.</string>
|
||||
<string name="settings_playlist_sort">Sortowanie playlist</string>
|
||||
<string name="settings_audio_quality">Pokaż jakość audio</string>
|
||||
<string name="settings_audio_quality_summary">Bitrate i format audio będzie pokazywany dla każdego utworu.</string>
|
||||
<string name="settings_song_rating">Pokaż ocenę piosenek w gwiazdkach</string>
|
||||
|
||||
@@ -164,6 +164,8 @@
|
||||
<string name="playlist_chooser_dialog_title">Adicionar a uma playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_success">Adicionada playlist de reprodução</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_failure">Falha ao adicionar uma playlist de reprodução</string>
|
||||
<string name="playlist_chooser_dialog_visibility_public">Pública</string>
|
||||
<string name="playlist_chooser_dialog_visibility_private">Privada</string>
|
||||
<string name="playlist_counted_tracks">%1$d faixas • %2$s</string>
|
||||
<string name="playlist_duration">Duração • %1$s</string>
|
||||
<string name="playlist_editor_dialog_hint_name">Nome da playlist</string>
|
||||
|
||||
@@ -240,6 +240,16 @@
|
||||
<item>8</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="playlist_sort_option_titles">
|
||||
<item>Name</item>
|
||||
<item>Random</item>
|
||||
</string-array>
|
||||
<string-array name="playlist_sort_option_values">
|
||||
<item>ORDER_BY_NAME</item>
|
||||
<item>ORDER_BY_RANDOM</item>
|
||||
</string-array>
|
||||
|
||||
|
||||
<string-array name="skip_min_star_rating_titles">
|
||||
<item>0 star minimum</item>
|
||||
<item>1 star minimum</item>
|
||||
@@ -254,4 +264,18 @@
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
</string-array>
|
||||
<string-array name="landscape_items_per_row">
|
||||
<item>3"</item>
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
<item>6</item>
|
||||
<item>7</item>
|
||||
</string-array>
|
||||
<string-array name="landscape_items_per_row_values">
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
<item>6</item>
|
||||
<item>7</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -217,6 +217,8 @@
|
||||
<string name="menu_unpin_button">Remove from home screen</string>
|
||||
<string name="menu_sort_year">Year</string>
|
||||
<string name="player_playback_speed">%1$.2fx</string>
|
||||
<string name="playback_speed_dialog_title">Playback Speed</string>
|
||||
<string name="playback_speed_dialog_negative_button">Cancel</string>
|
||||
<string name="player_queue_clean_all_button">Clean play queue</string>
|
||||
<string name="player_queue_save_queue_success">Saved play queue</string>
|
||||
<string name="player_queue_save_to_playlist">Save Queue to Playlist</string>
|
||||
@@ -238,6 +240,8 @@
|
||||
<string name="playlist_chooser_dialog_toast_add_success">Added song(s) to playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_failure">Failed to add song(s) to playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_all_skipped">All songs were skipped as duplicates</string>
|
||||
<string name="playlist_chooser_dialog_visibility_public">Public</string>
|
||||
<string name="playlist_chooser_dialog_visibility_private">Private</string>
|
||||
<string name="playlist_counted_tracks">%1$d tracks • %2$s</string>
|
||||
<string name="playlist_duration">Duration • %1$s</string>
|
||||
<string name="playlist_editor_dialog_action_delete_toast">Long press to delete</string>
|
||||
@@ -343,7 +347,7 @@
|
||||
<string name="settings_github_link">https://github.com/eddyizm/tempus</string>
|
||||
<string name="settings_github_summary">Follow the development</string>
|
||||
<string name="settings_github_title">Github</string>
|
||||
<string name="settings_support_discussion_link">https://github.com/eddyizm/tempus/discussions</string>
|
||||
<string name="settings_support_discussion_link" translatable="false">https://github.com/eddyizm/tempus/discussions</string>
|
||||
<string name="settings_github_update">Updates</string>
|
||||
<string name="settings_github_update_title">Check github for release updates</string>
|
||||
<string name="settings_github_update_summary">If using the github version, by default app will check for new apk release. Toggle to disable automatic github checks</string>
|
||||
@@ -353,6 +357,9 @@
|
||||
<string name="settings_image_size">Set image resolution</string>
|
||||
<string name="settings_language">Language</string>
|
||||
<string name="settings_logout_title">Log out</string>
|
||||
<string name="settings_ping_timeout_title">Server Ping Timeout</string>
|
||||
<string name="settings_ping_timeout_summary">Set Local URL timeout. Default 2 seconds. (Remote server will use this value x3 up to 10 seconds max.)</string>
|
||||
<string name="settings_ping_timeout_dialog">Set base timeout in seconds.</string>
|
||||
<string name="settings_max_bitrate_download">Bitrate for downloads</string>
|
||||
<string name="settings_max_bitrate_mobile">Bitrate in mobile</string>
|
||||
<string name="settings_max_bitrate_wifi">Bitrate in Wi-Fi</string>
|
||||
@@ -361,6 +368,7 @@
|
||||
<string name="settings_music_directory_summary">If enabled, show the music directory section. Please note that for folder navigation to work properly, the server must support this feature.</string>
|
||||
<string name="settings_podcast">Show podcast</string>
|
||||
<string name="settings_podcast_summary">If enabled, show the podcast section. Restart the app for it to take full effect.</string>
|
||||
<string name="settings_playlist_sort">Playlist sorting</string>
|
||||
<string name="settings_audio_quality">Show audio quality</string>
|
||||
<string name="settings_audio_quality_summary">The bitrate and audio format will be shown for each audio track.</string>
|
||||
<string name="settings_song_rating">Show song star rating</string>
|
||||
@@ -397,6 +405,7 @@
|
||||
<string name="settings_summary_transcoding">Priority given to the transcoding mode. If set to \"Direct play\" the bitrate of the file will not be changed.</string>
|
||||
<string name="settings_summary_transcoding_download">Download transcoded media. If enabled, the download endpoint will not be used, but the following settings. \n\n If \"Transcode format for downloads\" is set to \"Direct download\" the bitrate of the file will not be changed.</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">When the file is transcoded on the fly, the client usually does not show the track length. It is possible to request the servers that support the functionality to estimate the duration of the track being played, but the response times may take longer.</string>
|
||||
<string name="settings_summary_landscape_items_per_row">Applies to all album and artist listings. Defaults to 4</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_summary">If enabled, starred artists will be downloaded for offline use.</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_title">Sync starred artists for offline use</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_summary">If enabled, starred albums will be downloaded for offline use.</string>
|
||||
@@ -417,6 +426,8 @@
|
||||
<string name="settings_title_transcoding">Transcoding</string>
|
||||
<string name="settings_title_transcoding_download">Transcoding Download</string>
|
||||
<string name="settings_title_ui">UI</string>
|
||||
<string name="settings_title_ui_landscape_items_per_row">Items per row on landscape</string>
|
||||
<string name="settings_title_ui_landscape_items_per_row_dialog">Number of items per row</string>
|
||||
<string name="settings_transcoded_download">Transcoded download</string>
|
||||
<string name="settings_version_summary" translatable="false">3.1.0</string>
|
||||
<string name="settings_version_title">Version</string>
|
||||
@@ -483,7 +494,7 @@
|
||||
<string name="streaming_cache_storage_dialog_title">Select storage option</string>
|
||||
<string name="streaming_cache_storage_external_dialog_positive_button">External</string>
|
||||
<string name="streaming_cache_storage_internal_dialog_negative_button">Internal</string>
|
||||
<string name="support_url">https://ko-fi.com/eddyizm</string>
|
||||
<string name="support_url" translatable="false">https://ko-fi.com/eddyizm</string>
|
||||
<string name="track_info_album">Album</string>
|
||||
<string name="track_info_artist">Artist</string>
|
||||
<string name="track_info_bit_depth">Bit depth</string>
|
||||
@@ -511,7 +522,7 @@
|
||||
<string name="track_info_year">Year</string>
|
||||
<string name="undraw_page">unDraw</string>
|
||||
<string name="undraw_thanks">A special thanks goes to unDraw without whose illustrations we could not have made this application more beautiful.</string>
|
||||
<string name="undraw_url">https://undraw.co/</string>
|
||||
<string name="undraw_url" translatable="false">https://undraw.co/</string>
|
||||
<string name="widget_label">Tempus Widget</string>
|
||||
<string name="widget_not_playing">Not playing</string>
|
||||
<string name="widget_placeholder_subtitle">Open Tempus</string>
|
||||
|
||||
@@ -17,6 +17,15 @@
|
||||
android:key="scan_library"
|
||||
android:title="@string/settings_scan_title" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="network_ping_timeout_base"
|
||||
android:title="@string/settings_ping_timeout_title"
|
||||
app:summary="@string/settings_ping_timeout_summary"
|
||||
android:dialogTitle="@string/settings_ping_timeout_dialog"
|
||||
android:inputType="number"
|
||||
android:singleLine="true"
|
||||
android:defaultValue="2" />
|
||||
|
||||
<Preference
|
||||
android:key="logout"
|
||||
android:title="@string/settings_logout_title"/>
|
||||
@@ -128,6 +137,15 @@
|
||||
android:summary="@string/search_sort_summary"
|
||||
android:key="sort_search_chronologically" />
|
||||
|
||||
<ListPreference
|
||||
app:defaultValue="4"
|
||||
app:dialogTitle="@string/settings_title_ui_landscape_items_per_row_dialog"
|
||||
app:entries="@array/landscape_items_per_row"
|
||||
app:entryValues="@array/landscape_items_per_row_values"
|
||||
app:key="landscape_items_per_row"
|
||||
android:summary="@string/settings_summary_landscape_items_per_row"
|
||||
app:title="@string/settings_title_ui_landscape_items_per_row" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory app:title="@string/settings_title_playlist">
|
||||
@@ -136,6 +154,14 @@
|
||||
android:defaultValue="false"
|
||||
android:summary="@string/settings_allow_playlist_duplicates_summary"
|
||||
android:key="allow_playlist_duplicates" />
|
||||
<ListPreference
|
||||
app:defaultValue="ORDER_BY_NAME"
|
||||
app:dialogTitle="@string/settings_playlist_sort"
|
||||
app:entries="@array/playlist_sort_option_titles"
|
||||
app:entryValues="@array/playlist_sort_option_values"
|
||||
app:key="home_sort_playlists"
|
||||
app:title="@string/settings_playlist_sort"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
</PreferenceCategory>
|
||||
|
||||
|
||||
|
||||
@@ -12,4 +12,5 @@
|
||||
<locale android:name="pl-PL"/> <!-- Polish -->
|
||||
<locale android:name="tr-TR"/> <!-- Turkish -->
|
||||
<locale android:name="ca"/> <!-- Catalan -->
|
||||
<locale android:name="ro"/> <!-- Romanian -->
|
||||
</locale-config>
|
||||
|
||||
12
fastlane/metadata/android/en-US/changelogs/14.txt
Normal file
12
fastlane/metadata/android/en-US/changelogs/14.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
chore: i18n: Add Romanian translation (including locale_config this time!)
|
||||
chore: French localization update
|
||||
chore(i18n): Update Spanish translation
|
||||
docs: updated readme and added known issues for airsonic work around
|
||||
fix: toast for made for you click indication
|
||||
fix: sort playlist view
|
||||
feat: sort preference for playlists
|
||||
fix: use existing future when adding tracks, dialed random album tracks off in instant mix
|
||||
chore(i18n): Update Polish translation
|
||||
fix: Check for OpenSubsonic extensions also with password authentication, addressing lyric sync
|
||||
feat: Implement duration and seeking for transcodes
|
||||
feat: Playback speed controls for music
|
||||
3
fastlane/metadata/android/en-US/changelogs/15.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/15.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
fix: Proper raw stream detection
|
||||
chore(i18n): Update Spanish translation
|
||||
feat: add configurable timeout
|
||||
2
fastlane/metadata/android/en-US/changelogs/16.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/16.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
fix: Avoid crash when server has no songs
|
||||
fix: updated dialog import to address crashing on android 15
|
||||
3
fastlane/metadata/android/en-US/changelogs/17.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/17.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
fix: missing Replay Gain metadata from .m4a files
|
||||
fix: Improve Synced Lyrics
|
||||
fix: Add selector for playlist visibility
|
||||
4
fastlane/metadata/android/en-US/changelogs/18.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/18.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
fix: Addressing some UI/UX quirks
|
||||
fix: keep observer until data is received on continuousPlay bug
|
||||
fix: album art now displays on android auto
|
||||
feat: improve landscape view and increase items per row on landscape view
|
||||
Reference in New Issue
Block a user