Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c6981ed19 | ||
|
|
560ac2df68 | ||
|
|
260e6e9daa | ||
|
|
38a1368c76 | ||
|
|
9bf3139bd2 | ||
|
|
d389d1d62a | ||
|
|
e60c0e312c | ||
|
|
dc13beca49 | ||
|
|
6399488819 | ||
|
|
69c1b93d28 | ||
|
|
50c240793a | ||
|
|
80f7783b7d | ||
|
|
259c8b3599 | ||
|
|
ba1295440b | ||
|
|
9e6178037c | ||
|
|
47302815c1 | ||
|
|
c81bfe1457 | ||
|
|
f413b5d498 | ||
|
|
feae1b8bdd | ||
|
|
57bb51fb8d | ||
|
|
0f1364502a | ||
|
|
a508ec0af6 | ||
|
|
a9b264004c | ||
|
|
803b61430f | ||
|
|
25367512c0 | ||
|
|
e3720f4a50 | ||
|
|
1168cc58f4 | ||
|
|
c711b387bb | ||
|
|
a3fe0de233 | ||
|
|
922153837d | ||
|
|
960dd6c76a | ||
|
|
fcb7135c3d | ||
|
|
3102bff729 | ||
|
|
992b16cc1d | ||
|
|
2046872d79 | ||
|
|
7312ae20d1 | ||
|
|
e8f2385928 | ||
|
|
204f9ff4c7 | ||
|
|
17e64f3dd0 | ||
|
|
d09e7e7981 | ||
|
|
95e9d06e47 | ||
|
|
f147c762d5 | ||
|
|
200ce6555b | ||
|
|
b5e5537691 | ||
|
|
3b80725673 | ||
|
|
d7c5be834e | ||
|
|
4a5672fc77 | ||
|
|
34fa2f456e | ||
|
|
663285611b | ||
|
|
a62cc78e18 |
6
.github/workflows/github_release.yml
vendored
@@ -34,13 +34,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Build APK
|
- name: Build APK
|
||||||
id: build
|
id: build
|
||||||
run: bash ./gradlew assembleRelease
|
run: bash ./gradlew assembleTempoRelease
|
||||||
|
|
||||||
- name: Sign APK
|
- name: Sign APK
|
||||||
id: sign_apk
|
id: sign_apk
|
||||||
uses: r0adkll/sign-android-release@v1
|
uses: r0adkll/sign-android-release@v1
|
||||||
with:
|
with:
|
||||||
releaseDirectory: app/build/outputs/apk/release
|
releaseDirectory: app/build/outputs/apk/tempo/release
|
||||||
signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }}
|
signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }}
|
||||||
alias: ${{ secrets.KEY_ALIAS_GITHUB }}
|
alias: ${{ secrets.KEY_ALIAS_GITHUB }}
|
||||||
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
|
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||||
@@ -98,7 +98,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: ${{steps.sign_apk.outputs.signedReleaseFile}}
|
asset_path: ${{steps.sign_apk.outputs.signedReleaseFile}}
|
||||||
asset_name: app-release.apk
|
asset_name: app-tempo-release.apk
|
||||||
asset_content_type: application/zip
|
asset_content_type: application/zip
|
||||||
|
|
||||||
# - name: Upload AAB
|
# - name: Upload AAB
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
<h1 align="center"> Tempo </h1>
|
|
||||||
<br>
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="Tempo" title="Tempo" src="mockup/svg/horizontal_logo.svg" width="250">
|
<img alt="Tempo" title="Tempo" src="mockup/svg/horizontal_logo.svg" width="250">
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -7,11 +7,8 @@ android {
|
|||||||
buildToolsVersion '33.0.0'
|
buildToolsVersion '33.0.0'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'com.cappielloantonio.tempo'
|
|
||||||
minSdkVersion 24
|
minSdkVersion 24
|
||||||
targetSdkVersion 34
|
targetSdkVersion 34
|
||||||
versionCode 5
|
|
||||||
versionName '3.3.0'
|
|
||||||
|
|
||||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||||
|
|
||||||
@@ -25,6 +22,24 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flavorDimensions "default"
|
||||||
|
|
||||||
|
productFlavors {
|
||||||
|
tempo {
|
||||||
|
dimension "default"
|
||||||
|
applicationId 'com.cappielloantonio.tempo'
|
||||||
|
versionCode 13
|
||||||
|
versionName '3.4.6'
|
||||||
|
}
|
||||||
|
|
||||||
|
notquitemy {
|
||||||
|
dimension "default"
|
||||||
|
applicationId "com.cappielloantonio.notquitemy.tempo"
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
@@ -54,51 +69,34 @@ dependencies {
|
|||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
// AndroidX
|
// AndroidX
|
||||||
implementation 'androidx.core:core-ktx:1.10.1'
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.6.0'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.6.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
||||||
implementation 'androidx.room:room-runtime:2.5.1'
|
implementation 'androidx.room:room-runtime:2.5.2'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
|
||||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||||
|
|
||||||
// Google GMS
|
|
||||||
implementation 'com.google.android.gms:play-services-cast-framework:21.3.0'
|
|
||||||
|
|
||||||
// Android Material
|
// Android Material
|
||||||
implementation 'com.google.android.material:material:1.9.0'
|
implementation 'com.google.android.material:material:1.9.0'
|
||||||
|
|
||||||
// SearchBar
|
|
||||||
implementation 'com.paulrybitskyi.persistentsearchview:persistentsearchview:1.1.4'
|
|
||||||
implementation 'com.arthurivanets.adapster:adapster:1.0.13'
|
|
||||||
|
|
||||||
// Glide
|
// Glide
|
||||||
implementation 'com.github.bumptech.glide:glide:4.15.1'
|
implementation 'com.github.bumptech.glide:glide:4.15.1'
|
||||||
implementation 'com.github.bumptech.glide:annotations:4.15.1'
|
implementation 'com.github.bumptech.glide:annotations:4.15.1'
|
||||||
|
|
||||||
// Media3
|
// Media3
|
||||||
implementation 'androidx.media3:media3-session:1.0.2'
|
implementation 'androidx.media3:media3-session:1.1.0'
|
||||||
implementation 'androidx.media3:media3-common:1.0.2'
|
implementation 'androidx.media3:media3-common:1.1.0'
|
||||||
implementation 'androidx.media3:media3-exoplayer:1.0.2'
|
implementation 'androidx.media3:media3-exoplayer:1.1.0'
|
||||||
implementation 'androidx.media3:media3-ui:1.0.2'
|
implementation 'androidx.media3:media3-ui:1.1.0'
|
||||||
implementation 'androidx.media3:media3-cast:1.0.2'
|
tempoImplementation 'androidx.media3:media3-cast:1.1.0'
|
||||||
|
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1'
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1'
|
||||||
annotationProcessor 'androidx.room:room-compiler:2.5.1'
|
annotationProcessor 'androidx.room:room-compiler:2.5.2'
|
||||||
|
|
||||||
// Retrofit
|
// Retrofit
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||||
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.6'
|
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.11'
|
||||||
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
||||||
|
|
||||||
// Crash Report
|
|
||||||
// debugImplementation 'com.balsikandar.android:crashreporter:1.1.0'
|
|
||||||
|
|
||||||
// DB debug
|
|
||||||
// debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
|
|
||||||
}
|
}
|
||||||
@@ -12,8 +12,8 @@ import java.util.List;
|
|||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public interface RecentSearchDao {
|
public interface RecentSearchDao {
|
||||||
@Query("SELECT * FROM recent_search GROUP BY search ORDER BY search DESC LIMIT :limit")
|
@Query("SELECT * FROM recent_search ORDER BY search DESC")
|
||||||
List<String> getRecent(int limit);
|
List<String> getRecent();
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
void insert(RecentSearch search);
|
void insert(RecentSearch search);
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.cappielloantonio.tempo.model
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class ReplayGain(
|
||||||
|
var trackGain: Float = 0f,
|
||||||
|
var albumGain: Float = 0f,
|
||||||
|
)
|
||||||
@@ -17,8 +17,12 @@ public class ScanRepository {
|
|||||||
.enqueue(new Callback<ApiResponse>() {
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getScanStatus() != null) {
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null) {
|
||||||
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
if (response.body().getSubsonicResponse().getError() != null) {
|
||||||
|
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getMessage()));
|
||||||
|
} else if (response.body().getSubsonicResponse().getScanStatus() != null) {
|
||||||
|
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,8 +40,12 @@ public class ScanRepository {
|
|||||||
.enqueue(new Callback<ApiResponse>() {
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getScanStatus() != null) {
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null) {
|
||||||
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
if (response.body().getSubsonicResponse().getError() != null) {
|
||||||
|
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getMessage()));
|
||||||
|
} else if (response.body().getSubsonicResponse().getScanStatus() != null) {
|
||||||
|
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.cappielloantonio.tempo.repository;
|
package com.cappielloantonio.tempo.repository;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
@@ -11,6 +13,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
|||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.SearchResult2;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
|
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -24,7 +27,28 @@ import retrofit2.Response;
|
|||||||
public class SearchingRepository {
|
public class SearchingRepository {
|
||||||
private final RecentSearchDao recentSearchDao = AppDatabase.getInstance().recentSearchDao();
|
private final RecentSearchDao recentSearchDao = AppDatabase.getInstance().recentSearchDao();
|
||||||
|
|
||||||
public MutableLiveData<SearchResult3> search(String query) {
|
public MutableLiveData<SearchResult2> search2(String query) {
|
||||||
|
MutableLiveData<SearchResult2> result = new MutableLiveData<>();
|
||||||
|
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getSearchingClient()
|
||||||
|
.search3(query, 20, 20, 20)
|
||||||
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
result.setValue(response.body().getSubsonicResponse().getSearchResult2());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<SearchResult3> search3(String query) {
|
||||||
MutableLiveData<SearchResult3> result = new MutableLiveData<>();
|
MutableLiveData<SearchResult3> result = new MutableLiveData<>();
|
||||||
|
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
@@ -78,6 +102,8 @@ public class SearchingRepository {
|
|||||||
LinkedHashSet<String> hashSet = new LinkedHashSet<>(newSuggestions);
|
LinkedHashSet<String> hashSet = new LinkedHashSet<>(newSuggestions);
|
||||||
ArrayList<String> suggestionsWithoutDuplicates = new ArrayList<>(hashSet);
|
ArrayList<String> suggestionsWithoutDuplicates = new ArrayList<>(hashSet);
|
||||||
|
|
||||||
|
Log.d("suggestionsWithoutDuplicates", suggestionsWithoutDuplicates.toString());
|
||||||
|
|
||||||
suggestions.setValue(suggestionsWithoutDuplicates);
|
suggestions.setValue(suggestionsWithoutDuplicates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,10 +129,10 @@ public class SearchingRepository {
|
|||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getRecentSearchSuggestion(int limit) {
|
public List<String> getRecentSearchSuggestion() {
|
||||||
List<String> recent = new ArrayList<>();
|
List<String> recent = new ArrayList<>();
|
||||||
|
|
||||||
RecentThreadSafe suggestionsThread = new RecentThreadSafe(recentSearchDao, limit);
|
RecentThreadSafe suggestionsThread = new RecentThreadSafe(recentSearchDao);
|
||||||
Thread thread = new Thread(suggestionsThread);
|
Thread thread = new Thread(suggestionsThread);
|
||||||
thread.start();
|
thread.start();
|
||||||
|
|
||||||
@@ -152,17 +178,15 @@ public class SearchingRepository {
|
|||||||
|
|
||||||
private static class RecentThreadSafe implements Runnable {
|
private static class RecentThreadSafe implements Runnable {
|
||||||
private final RecentSearchDao recentSearchDao;
|
private final RecentSearchDao recentSearchDao;
|
||||||
private final int limit;
|
|
||||||
private List<String> recent = new ArrayList<>();
|
private List<String> recent = new ArrayList<>();
|
||||||
|
|
||||||
public RecentThreadSafe(RecentSearchDao recentSearchDao, int limit) {
|
public RecentThreadSafe(RecentSearchDao recentSearchDao) {
|
||||||
this.recentSearchDao = recentSearchDao;
|
this.recentSearchDao = recentSearchDao;
|
||||||
this.limit = limit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
recent = recentSearchDao.getRecent(limit);
|
recent = recentSearchDao.getRecent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getRecent() {
|
public List<String> getRecent() {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public class SongRepository {
|
|||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
List<Child> songs = new ArrayList<>();
|
List<Child> songs = new ArrayList<>();
|
||||||
|
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null) {
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null && response.body().getSubsonicResponse().getRandomSongs().getSongs() != null) {
|
||||||
songs.addAll(response.body().getSubsonicResponse().getRandomSongs().getSongs());
|
songs.addAll(response.body().getSubsonicResponse().getRandomSongs().getSongs());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class SystemRepository {
|
|||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
||||||
if (response.body() != null) {
|
if (response.body() != null) {
|
||||||
if (response.body().getSubsonicResponse().getStatus().equals(ResponseStatus.FAILED)) {
|
if (response.body().getSubsonicResponse().getStatus().equals(ResponseStatus.FAILED)) {
|
||||||
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getCode().getValue() + " - " + response.body().getSubsonicResponse().getError().getMessage()));
|
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getCode() + " - " + response.body().getSubsonicResponse().getError().getMessage()));
|
||||||
} else if (response.body().getSubsonicResponse().getStatus().equals(ResponseStatus.OK)) {
|
} else if (response.body().getSubsonicResponse().getStatus().equals(ResponseStatus.OK)) {
|
||||||
String password = response.raw().request().url().queryParameter("p");
|
String password = response.raw().request().url().queryParameter("p");
|
||||||
String token = response.raw().request().url().queryParameter("t");
|
String token = response.raw().request().url().queryParameter("t");
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class RetrofitClient(subsonic: Subsonic) {
|
|||||||
init {
|
init {
|
||||||
retrofit = Retrofit.Builder()
|
retrofit = Retrofit.Builder()
|
||||||
.baseUrl(subsonic.url)
|
.baseUrl(subsonic.url)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create()))
|
||||||
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
|
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
|
||||||
.client(getOkHttpClient())
|
.client(getOkHttpClient())
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ import androidx.annotation.Keep
|
|||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
class Error {
|
class Error {
|
||||||
var code: ErrorCode? = null
|
var code: Int? = null
|
||||||
var message: String? = null
|
var message: String? = null
|
||||||
}
|
}
|
||||||
@@ -141,6 +141,10 @@ public class MainActivity extends BaseActivity {
|
|||||||
handler.postDelayed(runnable, 100);
|
handler.postDelayed(runnable, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void expandBottomSheet() {
|
||||||
|
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||||
|
}
|
||||||
|
|
||||||
public void setBottomSheetDraggableState(Boolean isDraggable) {
|
public void setBottomSheetDraggableState(Boolean isDraggable) {
|
||||||
bottomSheetBehavior.setDraggable(isDraggable);
|
bottomSheetBehavior.setDraggable(isDraggable);
|
||||||
}
|
}
|
||||||
@@ -247,6 +251,7 @@ public class MainActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void goToLogin() {
|
private void goToLogin() {
|
||||||
|
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||||
setBottomNavigationBarVisibility(false);
|
setBottomNavigationBarVisibility(false);
|
||||||
setBottomSheetVisibility(false);
|
setBottomSheetVisibility(false);
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,12 @@ package com.cappielloantonio.tempo.ui.activity.base;
|
|||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.provider.Settings;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
@@ -19,11 +16,11 @@ import androidx.media3.exoplayer.offline.DownloadService;
|
|||||||
import androidx.media3.session.MediaBrowser;
|
import androidx.media3.session.MediaBrowser;
|
||||||
import androidx.media3.session.SessionToken;
|
import androidx.media3.session.SessionToken;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.R;
|
|
||||||
import com.cappielloantonio.tempo.service.DownloaderService;
|
import com.cappielloantonio.tempo.service.DownloaderService;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
import com.cappielloantonio.tempo.util.UIUtil;
|
import com.cappielloantonio.tempo.ui.dialog.BatteryOptimizationDialog;
|
||||||
import com.google.android.gms.cast.framework.CastContext;
|
import com.cappielloantonio.tempo.util.Flavors;
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.google.android.material.elevation.SurfaceColors;
|
import com.google.android.material.elevation.SurfaceColors;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
@@ -36,8 +33,10 @@ public class BaseActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
initializeCastContext();
|
Flavors.initializeCastContext(this);
|
||||||
initializeDownloader();
|
initializeDownloader();
|
||||||
|
checkBatteryOptimization();
|
||||||
|
checkPermission();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -47,13 +46,6 @@ public class BaseActivity extends AppCompatActivity {
|
|||||||
initializeBrowser();
|
initializeBrowser();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
checkBatteryOptimization();
|
|
||||||
checkPermission();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
releaseBrowser();
|
releaseBrowser();
|
||||||
@@ -61,7 +53,7 @@ public class BaseActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkBatteryOptimization() {
|
private void checkBatteryOptimization() {
|
||||||
if (detectBatteryOptimization()) {
|
if (detectBatteryOptimization() && Boolean.TRUE.equals(Preferences.askForOptimization())) {
|
||||||
showBatteryOptimizationDialog();
|
showBatteryOptimizationDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,19 +73,8 @@ public class BaseActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showBatteryOptimizationDialog() {
|
private void showBatteryOptimizationDialog() {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
BatteryOptimizationDialog dialog = new BatteryOptimizationDialog();
|
||||||
builder.setMessage(R.string.activity_battery_optimizations_summary)
|
dialog.show(getSupportFragmentManager(), null);
|
||||||
.setTitle(R.string.activity_battery_optimizations_title)
|
|
||||||
.setNegativeButton(R.string.activity_negative_button, null)
|
|
||||||
.setPositiveButton(R.string.activity_neutral_button, (dialog, id) -> openPowerSettings())
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void openPowerSettings() {
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeBrowser() {
|
private void initializeBrowser() {
|
||||||
@@ -116,10 +97,6 @@ public class BaseActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeCastContext() {
|
|
||||||
if (UIUtil.isCastApiAvailable(this)) CastContext.getSharedInstance(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setNavigationBarColor() {
|
private void setNavigationBarColor() {
|
||||||
getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 10));
|
getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 10));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
|
|||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
|
|
||||||
if (children.get(getBindingAdapterPosition()).isDir()) {
|
if (children.get(getBindingAdapterPosition()).isDir()) {
|
||||||
bundle.putParcelable(Constants.MUSIC_DIRECTORY_OBJECT, children.get(getBindingAdapterPosition()));
|
bundle.putString(Constants.MUSIC_DIRECTORY_ID, children.get(getBindingAdapterPosition()).getId());
|
||||||
click.onMusicDirectoryClick(bundle);
|
click.onMusicDirectoryClick(bundle);
|
||||||
} else {
|
} else {
|
||||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(children));
|
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(children));
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
|||||||
|
|
||||||
public void onClick() {
|
public void onClick() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.MUSIC_INDEX_OBJECT, artists.get(getBindingAdapterPosition()));
|
bundle.putString(Constants.MUSIC_DIRECTORY_ID, artists.get(getBindingAdapterPosition()).getId());
|
||||||
click.onMusicIndexClick(bundle);
|
click.onMusicIndexClick(bundle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setItems(List<Child> songs) {
|
public void setItems(List<Child> songs) {
|
||||||
this.songs = songs;
|
this.songs = songs != null ? songs : Collections.emptyList();
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.cappielloantonio.tempo.ui.dialog;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.provider.Settings;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.R;
|
||||||
|
import com.cappielloantonio.tempo.databinding.DialogBatteryOptimizationBinding;
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
|
public class BatteryOptimizationDialog extends DialogFragment {
|
||||||
|
private static final String TAG = "BatteryOptimizationDialog";
|
||||||
|
|
||||||
|
private DialogBatteryOptimizationBinding bind;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
bind = DialogBatteryOptimizationBinding.inflate(getLayoutInflater());
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
|
||||||
|
builder.setView(bind.getRoot())
|
||||||
|
.setTitle(R.string.activity_battery_optimizations_title)
|
||||||
|
.setNeutralButton(R.string.battery_optimization_neutral_button, (dialog, id) -> Preferences.dontAskForOptimization())
|
||||||
|
.setNegativeButton(R.string.battery_optimization_negative_button, null)
|
||||||
|
.setPositiveButton(R.string.battery_optimization_positive_button, (dialog, id) -> openPowerSettings());
|
||||||
|
|
||||||
|
AlertDialog popup = builder.create();
|
||||||
|
|
||||||
|
popup.setCancelable(false);
|
||||||
|
popup.setCanceledOnTouchOutside(false);
|
||||||
|
|
||||||
|
return popup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
bind = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openPowerSettings() {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -116,6 +116,11 @@ public class ServerSignupDialog extends DialogFragment {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!server.matches("^https?://(.*)")) {
|
||||||
|
bind.serverTextView.setError(getString(R.string.error_server_prefix));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import androidx.lifecycle.ViewModelProvider;
|
|||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.session.MediaBrowser;
|
import androidx.media3.session.MediaBrowser;
|
||||||
import androidx.media3.session.SessionToken;
|
import androidx.media3.session.SessionToken;
|
||||||
|
import androidx.navigation.Navigation;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
@@ -20,17 +21,12 @@ import com.cappielloantonio.tempo.databinding.FragmentDirectoryBinding;
|
|||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
import com.cappielloantonio.tempo.service.MediaManager;
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Artist;
|
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.MusicDirectoryAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.MusicDirectoryAdapter;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.viewmodel.DirectoryViewModel;
|
import com.cappielloantonio.tempo.viewmodel.DirectoryViewModel;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class DirectoryFragment extends Fragment implements ClickCallback {
|
public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||||
private static final String TAG = "DirectoryFragment";
|
private static final String TAG = "DirectoryFragment";
|
||||||
@@ -52,9 +48,7 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
|||||||
directoryViewModel = new ViewModelProvider(requireActivity()).get(DirectoryViewModel.class);
|
directoryViewModel = new ViewModelProvider(requireActivity()).get(DirectoryViewModel.class);
|
||||||
|
|
||||||
initAppBar();
|
initAppBar();
|
||||||
initButtons();
|
|
||||||
initDirectoryListView();
|
initDirectoryListView();
|
||||||
init();
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
@@ -77,17 +71,6 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
|||||||
bind = null;
|
bind = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
|
||||||
Artist artist = getArguments().getParcelable(Constants.MUSIC_INDEX_OBJECT);
|
|
||||||
|
|
||||||
if (artist != null) {
|
|
||||||
directoryViewModel.setMusicDirectoryId(artist.getId());
|
|
||||||
directoryViewModel.setMusicDirectoryName(artist.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
directoryViewModel.loadMusicDirectory(getViewLifecycleOwner());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initAppBar() {
|
private void initAppBar() {
|
||||||
activity.setSupportActionBar(bind.toolbar);
|
activity.setSupportActionBar(bind.toolbar);
|
||||||
|
|
||||||
@@ -96,39 +79,10 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
|||||||
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
|
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bind != null)
|
if (bind != null) {
|
||||||
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
||||||
|
bind.directoryBackImageView.setOnClickListener(v -> activity.navController.navigateUp());
|
||||||
if (bind != null)
|
}
|
||||||
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
|
|
||||||
if ((bind.directoryInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
|
|
||||||
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
|
|
||||||
if (directory != null) {
|
|
||||||
bind.toolbar.setTitle(directory.getName());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
bind.toolbar.setTitle(R.string.empty_string);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
|
|
||||||
if (directory != null) {
|
|
||||||
bind.directoryTitleLabel.setText(directory.getName());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initButtons() {
|
|
||||||
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
|
|
||||||
if (directory != null && directory.getParentId() != null && !Objects.equals(directory.getParentId(), "-1")) {
|
|
||||||
bind.directoryBackImageView.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
bind.directoryBackImageView.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bind.directoryBackImageView.setOnClickListener(v -> directoryViewModel.goBack());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initDirectoryListView() {
|
private void initDirectoryListView() {
|
||||||
@@ -137,13 +91,18 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
|||||||
|
|
||||||
musicDirectoryAdapter = new MusicDirectoryAdapter(this);
|
musicDirectoryAdapter = new MusicDirectoryAdapter(this);
|
||||||
bind.directoryRecyclerView.setAdapter(musicDirectoryAdapter);
|
bind.directoryRecyclerView.setAdapter(musicDirectoryAdapter);
|
||||||
|
directoryViewModel.loadMusicDirectory(getArguments().getString(Constants.MUSIC_DIRECTORY_ID)).observe(getViewLifecycleOwner(), directory -> {
|
||||||
|
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
|
||||||
|
if ((bind.directoryInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
|
||||||
|
bind.toolbar.setTitle(directory.getName());
|
||||||
|
} else {
|
||||||
|
bind.toolbar.setTitle(R.string.empty_string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
|
bind.directoryTitleLabel.setText(directory.getName());
|
||||||
if (directory != null) {
|
|
||||||
musicDirectoryAdapter.setItems(directory.getChildren());
|
musicDirectoryAdapter.setItems(directory.getChildren());
|
||||||
} else {
|
|
||||||
musicDirectoryAdapter.setItems(Collections.emptyList());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,11 +121,6 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMusicDirectoryClick(Bundle bundle) {
|
public void onMusicDirectoryClick(Bundle bundle) {
|
||||||
Child child = bundle.getParcelable(Constants.MUSIC_DIRECTORY_OBJECT);
|
Navigation.findNavController(requireView()).navigate(R.id.directoryFragment, bundle);
|
||||||
|
|
||||||
if (child != null) {
|
|
||||||
directoryViewModel.setMusicDirectoryId(child.getId());
|
|
||||||
directoryViewModel.setMusicDirectoryName(child.getTitle());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,6 @@ package com.cappielloantonio.tempo.ui.fragment;
|
|||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
@@ -28,7 +25,7 @@ import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
|||||||
import com.cappielloantonio.tempo.ui.adapter.DownloadHorizontalAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.DownloadHorizontalAdapter;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
|
import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
|
||||||
import com.google.android.gms.cast.framework.CastButtonFactory;
|
import com.google.android.material.appbar.MaterialToolbar;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -43,18 +40,7 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
|||||||
|
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
@Override
|
private MaterialToolbar materialToolbar;
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
|
||||||
inflater.inflate(R.menu.main_page_menu, menu);
|
|
||||||
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
@@ -97,22 +83,11 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
|||||||
bind = null;
|
bind = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
|
||||||
if (item.getItemId() == R.id.action_search) {
|
|
||||||
activity.navController.navigate(R.id.action_downloadFragment_to_searchFragment);
|
|
||||||
return true;
|
|
||||||
} else if (item.getItemId() == R.id.action_settings) {
|
|
||||||
activity.navController.navigate(R.id.action_downloadFragment_to_settingsFragment);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initAppBar() {
|
private void initAppBar() {
|
||||||
activity.setSupportActionBar(bind.toolbar);
|
materialToolbar = bind.getRoot().findViewById(R.id.toolbar);
|
||||||
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
|
||||||
|
activity.setSupportActionBar(materialToolbar);
|
||||||
|
Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initDownloadedSongView() {
|
private void initDownloadedSongView() {
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ package com.cappielloantonio.tempo.ui.fragment;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
@@ -18,7 +15,9 @@ import com.cappielloantonio.tempo.databinding.FragmentHomeBinding;
|
|||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.fragment.pager.HomePager;
|
import com.cappielloantonio.tempo.ui.fragment.pager.HomePager;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.google.android.gms.cast.framework.CastButtonFactory;
|
import com.google.android.material.appbar.AppBarLayout;
|
||||||
|
import com.google.android.material.appbar.MaterialToolbar;
|
||||||
|
import com.google.android.material.tabs.TabLayout;
|
||||||
import com.google.android.material.tabs.TabLayoutMediator;
|
import com.google.android.material.tabs.TabLayoutMediator;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -30,18 +29,9 @@ public class HomeFragment extends Fragment {
|
|||||||
private FragmentHomeBinding bind;
|
private FragmentHomeBinding bind;
|
||||||
private MainActivity activity;
|
private MainActivity activity;
|
||||||
|
|
||||||
@Override
|
private MaterialToolbar materialToolbar;
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
private AppBarLayout appBarLayout;
|
||||||
super.onCreate(savedInstanceState);
|
private TabLayout tabLayout;
|
||||||
setHasOptionsMenu(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
|
||||||
inflater.inflate(R.menu.main_page_menu, menu);
|
|
||||||
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
@@ -73,22 +63,18 @@ public class HomeFragment extends Fragment {
|
|||||||
bind = null;
|
bind = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
|
||||||
if (item.getItemId() == R.id.action_search) {
|
|
||||||
activity.navController.navigate(R.id.action_homeFragment_to_searchFragment);
|
|
||||||
return true;
|
|
||||||
} else if (item.getItemId() == R.id.action_settings) {
|
|
||||||
activity.navController.navigate(R.id.action_homeFragment_to_settingsFragment);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initAppBar() {
|
private void initAppBar() {
|
||||||
activity.setSupportActionBar(bind.toolbar);
|
appBarLayout = bind.getRoot().findViewById(R.id.toolbar_fragment);
|
||||||
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
materialToolbar = bind.getRoot().findViewById(R.id.toolbar);
|
||||||
|
|
||||||
|
activity.setSupportActionBar(materialToolbar);
|
||||||
|
Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
||||||
|
|
||||||
|
tabLayout = new TabLayout(requireContext());
|
||||||
|
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
|
||||||
|
tabLayout.setTabMode(TabLayout.MODE_FIXED);
|
||||||
|
|
||||||
|
appBarLayout.addView(tabLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initHomePager() {
|
private void initHomePager() {
|
||||||
@@ -106,13 +92,13 @@ public class HomeFragment extends Fragment {
|
|||||||
bind.homeViewPager.setOffscreenPageLimit(3);
|
bind.homeViewPager.setOffscreenPageLimit(3);
|
||||||
bind.homeViewPager.setUserInputEnabled(false);
|
bind.homeViewPager.setUserInputEnabled(false);
|
||||||
|
|
||||||
new TabLayoutMediator(bind.homeTabLayout, bind.homeViewPager,
|
new TabLayoutMediator(tabLayout, bind.homeViewPager,
|
||||||
(tab, position) -> {
|
(tab, position) -> {
|
||||||
tab.setText(pager.getPageTitle(position));
|
tab.setText(pager.getPageTitle(position));
|
||||||
// tab.setIcon(pager.getPageIcon(position));
|
// tab.setIcon(pager.getPageIcon(position));
|
||||||
}
|
}
|
||||||
).attach();
|
).attach();
|
||||||
|
|
||||||
bind.homeTabLayout.setVisibility(Preferences.isPodcastSectionVisible() || Preferences.isRadioSectionVisible() ? View.VISIBLE : View.GONE);
|
tabLayout.setVisibility(Preferences.isPodcastSectionVisible() || Preferences.isRadioSectionVisible() ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.os.Bundle;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -138,6 +139,15 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bind.discoveryTextViewClickable.setOnClickListener(v -> {
|
||||||
|
homeViewModel.getRandomShuffleSample().observe(getViewLifecycleOwner(), songs -> {
|
||||||
|
if (songs.size() > 0) {
|
||||||
|
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||||
|
activity.setBottomSheetInPeek(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
bind.similarTracksTextViewRefreshable.setOnLongClickListener(v -> {
|
bind.similarTracksTextViewRefreshable.setOnLongClickListener(v -> {
|
||||||
homeViewModel.refreshSimilarSongSample(getViewLifecycleOwner());
|
homeViewModel.refreshSimilarSongSample(getViewLifecycleOwner());
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ package com.cappielloantonio.tempo.ui.fragment;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
@@ -29,8 +26,9 @@ import com.cappielloantonio.tempo.ui.adapter.MusicFolderAdapter;
|
|||||||
import com.cappielloantonio.tempo.ui.adapter.PlaylistHorizontalAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.PlaylistHorizontalAdapter;
|
||||||
import com.cappielloantonio.tempo.ui.dialog.PlaylistEditorDialog;
|
import com.cappielloantonio.tempo.ui.dialog.PlaylistEditorDialog;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.cappielloantonio.tempo.viewmodel.LibraryViewModel;
|
import com.cappielloantonio.tempo.viewmodel.LibraryViewModel;
|
||||||
import com.google.android.gms.cast.framework.CastButtonFactory;
|
import com.google.android.material.appbar.MaterialToolbar;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@@ -46,21 +44,9 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||||||
private AlbumAdapter albumAdapter;
|
private AlbumAdapter albumAdapter;
|
||||||
private ArtistAdapter artistAdapter;
|
private ArtistAdapter artistAdapter;
|
||||||
private GenreAdapter genreAdapter;
|
private GenreAdapter genreAdapter;
|
||||||
|
|
||||||
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
|
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
|
||||||
|
|
||||||
@Override
|
private MaterialToolbar materialToolbar;
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
|
||||||
inflater.inflate(R.menu.main_page_menu, menu);
|
|
||||||
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
@@ -100,19 +86,6 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||||||
bind = null;
|
bind = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
|
||||||
if (item.getItemId() == R.id.action_search) {
|
|
||||||
activity.navController.navigate(R.id.action_libraryFragment_to_searchFragment);
|
|
||||||
return true;
|
|
||||||
} else if (item.getItemId() == R.id.action_settings) {
|
|
||||||
activity.navController.navigate(R.id.action_libraryFragment_to_settingsFragment);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init() {
|
private void init() {
|
||||||
bind.albumCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_albumCatalogueFragment));
|
bind.albumCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_albumCatalogueFragment));
|
||||||
bind.artistCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_artistCatalogueFragment));
|
bind.artistCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_artistCatalogueFragment));
|
||||||
@@ -142,11 +115,18 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initAppBar() {
|
private void initAppBar() {
|
||||||
activity.setSupportActionBar(bind.toolbar);
|
materialToolbar = bind.getRoot().findViewById(R.id.toolbar);
|
||||||
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
|
||||||
|
activity.setSupportActionBar(materialToolbar);
|
||||||
|
Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initMusicFolderView() {
|
private void initMusicFolderView() {
|
||||||
|
if (!Preferences.isMusicDirectorySectionVisible()) {
|
||||||
|
bind.libraryMusicFolderSector.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bind.musicFolderRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.musicFolderRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
bind.musicFolderRecyclerView.setHasFixedSize(true);
|
bind.musicFolderRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
|||||||
import com.cappielloantonio.tempo.service.MediaManager;
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.PlayQueue;
|
import com.cappielloantonio.tempo.subsonic.models.PlayQueue;
|
||||||
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerVerticalPager;
|
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerVerticalPager;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
@@ -57,6 +58,7 @@ public class PlayerBottomSheetFragment extends Fragment {
|
|||||||
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
||||||
|
|
||||||
customizeBottomSheetBackground();
|
customizeBottomSheetBackground();
|
||||||
|
customizeBottomSheetAction();
|
||||||
initViewPager();
|
initViewPager();
|
||||||
setHeaderBookmarksButton();
|
setHeaderBookmarksButton();
|
||||||
|
|
||||||
@@ -87,6 +89,10 @@ public class PlayerBottomSheetFragment extends Fragment {
|
|||||||
bind.playerHeaderLayout.getRoot().setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 2));
|
bind.playerHeaderLayout.getRoot().setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void customizeBottomSheetAction() {
|
||||||
|
bind.playerHeaderLayout.getRoot().setOnClickListener(view -> ((MainActivity) requireActivity()).expandBottomSheet());
|
||||||
|
}
|
||||||
|
|
||||||
private void initViewPager() {
|
private void initViewPager() {
|
||||||
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
|
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
|
||||||
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setAdapter(new PlayerControllerVerticalPager(this));
|
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setAdapter(new PlayerControllerVerticalPager(this));
|
||||||
|
|||||||
@@ -2,9 +2,15 @@ package com.cappielloantonio.tempo.ui.fragment;
|
|||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -28,17 +34,15 @@ import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
|
|||||||
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
|
||||||
import com.cappielloantonio.tempo.viewmodel.SearchViewModel;
|
import com.cappielloantonio.tempo.viewmodel.SearchViewModel;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.paulrybitskyi.persistentsearchview.adapters.model.SuggestionItem;
|
|
||||||
import com.paulrybitskyi.persistentsearchview.listeners.OnSuggestionChangeListener;
|
|
||||||
import com.paulrybitskyi.persistentsearchview.utils.SuggestionCreationUtil;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class SearchFragment extends Fragment implements ClickCallback {
|
public class SearchFragment extends Fragment implements ClickCallback {
|
||||||
|
private static final String TAG = "SearchFragment";
|
||||||
|
|
||||||
private FragmentSearchBinding bind;
|
private FragmentSearchBinding bind;
|
||||||
private MainActivity activity;
|
private MainActivity activity;
|
||||||
private SearchViewModel searchViewModel;
|
private SearchViewModel searchViewModel;
|
||||||
@@ -60,6 +64,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
|||||||
|
|
||||||
initSearchResultView();
|
initSearchResultView();
|
||||||
initSearchView();
|
initSearchView();
|
||||||
|
inputFocus();
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
@@ -70,12 +75,6 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
|||||||
initializeMediaBrowser();
|
initializeMediaBrowser();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
inputFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
releaseMediaBrowser();
|
releaseMediaBrowser();
|
||||||
@@ -118,69 +117,100 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initSearchView() {
|
private void initSearchView() {
|
||||||
if (isQueryValid(searchViewModel.getQuery())) {
|
setRecentSuggestions();
|
||||||
search(searchViewModel.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
bind.persistentSearchView.setInputQuery(searchViewModel.getQuery());
|
bind.searchView
|
||||||
setSuggestions();
|
.getEditText()
|
||||||
|
.setOnEditorActionListener((textView, actionId, keyEvent) -> {
|
||||||
|
String query = bind.searchView.getText().toString();
|
||||||
|
|
||||||
bind.persistentSearchView.setOnSearchQueryChangeListener((searchView, oldQuery, newQuery) -> {
|
if (isQueryValid(query)) {
|
||||||
if (!newQuery.trim().equals("") && newQuery.trim().length() > 1) {
|
search(query);
|
||||||
searchViewModel.getSearchSuggestion(newQuery).observe(getViewLifecycleOwner(), suggestions -> searchView.setSuggestions(SuggestionCreationUtil.asRegularSearchSuggestions(MusicUtil.getReadableStrings(suggestions)), false));
|
return true;
|
||||||
} else {
|
}
|
||||||
setSuggestions();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bind.persistentSearchView.setOnSuggestionChangeListener(new OnSuggestionChangeListener() {
|
return false;
|
||||||
@Override
|
});
|
||||||
public void onSuggestionPicked(SuggestionItem suggestion) {
|
|
||||||
search(suggestion.getItemModel().getText());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
bind.searchView
|
||||||
public void onSuggestionRemoved(SuggestionItem suggestion) {
|
.getEditText()
|
||||||
}
|
.addTextChangedListener(new TextWatcher() {
|
||||||
});
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
|
||||||
|
|
||||||
bind.persistentSearchView.setOnSearchConfirmedListener((searchView, query) -> {
|
}
|
||||||
if (isQueryValid(query)) {
|
|
||||||
searchView.collapse();
|
|
||||||
search(query);
|
|
||||||
} else {
|
|
||||||
Toast.makeText(requireContext(), getString(R.string.search_info_minimum_characters), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bind.persistentSearchView.setOnSuggestionChangeListener(new OnSuggestionChangeListener() {
|
@Override
|
||||||
@Override
|
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
|
||||||
public void onSuggestionPicked(SuggestionItem suggestion) {
|
if (start + count > 1) {
|
||||||
search(suggestion.getItemModel().getText());
|
setSearchSuggestions(charSequence.toString());
|
||||||
}
|
} else {
|
||||||
|
setRecentSuggestions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuggestionRemoved(SuggestionItem suggestion) {
|
public void afterTextChanged(Editable editable) {
|
||||||
searchViewModel.deleteRecentSearch(suggestion.getItemModel().getText());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bind.persistentSearchView.setOnClearInputBtnClickListener(v -> searchViewModel.setQuery(""));
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSuggestions() {
|
public void setRecentSuggestions() {
|
||||||
bind.persistentSearchView.setSuggestions(SuggestionCreationUtil.asRecentSearchSuggestions(searchViewModel.getRecentSearchSuggestion()), false);
|
bind.searchViewSuggestionContainer.removeAllViews();
|
||||||
|
|
||||||
|
for (String suggestion : searchViewModel.getRecentSearchSuggestion()) {
|
||||||
|
View view = LayoutInflater.from(bind.searchViewSuggestionContainer.getContext()).inflate(R.layout.item_search_suggestion, bind.searchViewSuggestionContainer, false);
|
||||||
|
|
||||||
|
ImageView leadingImageView = view.findViewById(R.id.search_suggestion_icon);
|
||||||
|
TextView titleView = view.findViewById(R.id.search_suggestion_title);
|
||||||
|
ImageView tailingImageView = view.findViewById(R.id.search_suggestion_delete_icon);
|
||||||
|
|
||||||
|
leadingImageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_history, null));
|
||||||
|
titleView.setText(suggestion);
|
||||||
|
|
||||||
|
view.setOnClickListener(v -> search(suggestion));
|
||||||
|
|
||||||
|
tailingImageView.setOnClickListener(v -> {
|
||||||
|
searchViewModel.deleteRecentSearch(suggestion);
|
||||||
|
setRecentSuggestions();
|
||||||
|
});
|
||||||
|
|
||||||
|
bind.searchViewSuggestionContainer.addView(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSearchSuggestions(String query) {
|
||||||
|
searchViewModel.getSearchSuggestion(query).observe(getViewLifecycleOwner(), suggestions -> {
|
||||||
|
bind.searchViewSuggestionContainer.removeAllViews();
|
||||||
|
|
||||||
|
for (String suggestion : suggestions) {
|
||||||
|
View view = LayoutInflater.from(bind.searchViewSuggestionContainer.getContext()).inflate(R.layout.item_search_suggestion, bind.searchViewSuggestionContainer, false);
|
||||||
|
|
||||||
|
ImageView leadingImageView = view.findViewById(R.id.search_suggestion_icon);
|
||||||
|
TextView titleView = view.findViewById(R.id.search_suggestion_title);
|
||||||
|
ImageView tailingImageView = view.findViewById(R.id.search_suggestion_delete_icon);
|
||||||
|
|
||||||
|
leadingImageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_search, null));
|
||||||
|
titleView.setText(suggestion);
|
||||||
|
tailingImageView.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
view.setOnClickListener(v -> search(suggestion));
|
||||||
|
|
||||||
|
bind.searchViewSuggestionContainer.addView(view);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void search(String query) {
|
public void search(String query) {
|
||||||
searchViewModel.setQuery(query);
|
searchViewModel.setQuery(query);
|
||||||
|
bind.searchBar.setText(query);
|
||||||
bind.persistentSearchView.setInputQuery(query);
|
bind.searchView.hide();
|
||||||
performSearch(query);
|
performSearch(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performSearch(String query) {
|
private void performSearch(String query) {
|
||||||
searchViewModel.search(query).observe(getViewLifecycleOwner(), result -> {
|
searchViewModel.search3(query).observe(getViewLifecycleOwner(), result -> {
|
||||||
if (bind != null) {
|
if (bind != null) {
|
||||||
if (result.getArtists() != null) {
|
if (result.getArtists() != null) {
|
||||||
bind.searchArtistSector.setVisibility(!result.getArtists().isEmpty() ? View.VISIBLE : View.GONE);
|
bind.searchArtistSector.setVisibility(!result.getArtists().isEmpty() ? View.VISIBLE : View.GONE);
|
||||||
@@ -212,11 +242,12 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isQueryValid(String query) {
|
private boolean isQueryValid(String query) {
|
||||||
|
Log.d(TAG, "isQueryValid()");
|
||||||
return !query.equals("") && query.trim().length() > 2;
|
return !query.equals("") && query.trim().length() > 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void inputFocus() {
|
private void inputFocus() {
|
||||||
bind.persistentSearchView.expand();
|
bind.searchView.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeMediaBrowser() {
|
private void initializeMediaBrowser() {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import androidx.media3.common.util.UnstableApi;
|
|||||||
import androidx.preference.ListPreference;
|
import androidx.preference.ListPreference;
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.BuildConfig;
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.helper.ThemeHelper;
|
import com.cappielloantonio.tempo.helper.ThemeHelper;
|
||||||
import com.cappielloantonio.tempo.interfaces.ScanCallback;
|
import com.cappielloantonio.tempo.interfaces.ScanCallback;
|
||||||
@@ -69,6 +70,8 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
|
findPreference("version").setSummary(BuildConfig.VERSION_NAME);
|
||||||
|
|
||||||
findPreference("logout").setOnPreferenceClickListener(preference -> {
|
findPreference("logout").setOnPreferenceClickListener(preference -> {
|
||||||
activity.quit();
|
activity.quit();
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ object Constants {
|
|||||||
const val MUSIC_FOLDER_OBJECT = "MUSIC_FOLDER_OBJECT"
|
const val MUSIC_FOLDER_OBJECT = "MUSIC_FOLDER_OBJECT"
|
||||||
const val MUSIC_DIRECTORY_OBJECT = "MUSIC_DIRECTORY_OBJECT"
|
const val MUSIC_DIRECTORY_OBJECT = "MUSIC_DIRECTORY_OBJECT"
|
||||||
const val MUSIC_INDEX_OBJECT = "MUSIC_DIRECTORY_OBJECT"
|
const val MUSIC_INDEX_OBJECT = "MUSIC_DIRECTORY_OBJECT"
|
||||||
|
const val MUSIC_DIRECTORY_ID = "MUSIC_DIRECTORY_ID"
|
||||||
|
|
||||||
const val ALBUM_RECENTLY_PLAYED = "ALBUM_RECENTLY_PLAYED"
|
const val ALBUM_RECENTLY_PLAYED = "ALBUM_RECENTLY_PLAYED"
|
||||||
const val ALBUM_MOST_PLAYED = "ALBUM_MOST_PLAYED"
|
const val ALBUM_MOST_PLAYED = "ALBUM_MOST_PLAYED"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ object Preferences {
|
|||||||
private const val TOKEN = "token"
|
private const val TOKEN = "token"
|
||||||
private const val SALT = "salt"
|
private const val SALT = "salt"
|
||||||
private const val LOW_SECURITY = "low_security"
|
private const val LOW_SECURITY = "low_security"
|
||||||
|
private const val BATTERY_OPTIMIZATION = "battery_optimization"
|
||||||
private const val SERVER_ID = "server_id"
|
private const val SERVER_ID = "server_id"
|
||||||
private const val PLAYBACK_SPEED = "playback_speed"
|
private const val PLAYBACK_SPEED = "playback_speed"
|
||||||
private const val SKIP_SILENCE = "skip_silence"
|
private const val SKIP_SILENCE = "skip_silence"
|
||||||
@@ -29,6 +30,8 @@ object Preferences {
|
|||||||
private const val ROUNDED_CORNER_SIZE = "rounded_corner_size"
|
private const val ROUNDED_CORNER_SIZE = "rounded_corner_size"
|
||||||
private const val PODCAST_SECTION_VISIBILITY = "podcast_section_visibility"
|
private const val PODCAST_SECTION_VISIBILITY = "podcast_section_visibility"
|
||||||
private const val RADIO_SECTION_VISIBILITY = "radio_section_visibility"
|
private const val RADIO_SECTION_VISIBILITY = "radio_section_visibility"
|
||||||
|
private const val MUSIC_DIRECTORY_SECTION_VISIBILITY = "music_directory_section_visibility"
|
||||||
|
private const val REPLAY_GAIN_MODE = "replay_gain_mode"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getServer(): String? {
|
fun getServer(): String? {
|
||||||
@@ -100,6 +103,16 @@ object Preferences {
|
|||||||
App.getInstance().preferences.edit().putString(SERVER_ID, serverId).apply()
|
App.getInstance().preferences.edit().putString(SERVER_ID, serverId).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun askForOptimization(): Boolean? {
|
||||||
|
return App.getInstance().preferences.getBoolean(BATTERY_OPTIMIZATION, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun dontAskForOptimization() {
|
||||||
|
App.getInstance().preferences.edit().putBoolean(BATTERY_OPTIMIZATION, false).apply()
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getPlaybackSpeed(): Float {
|
fun getPlaybackSpeed(): Float {
|
||||||
return App.getInstance().preferences.getFloat(PLAYBACK_SPEED, 1f)
|
return App.getInstance().preferences.getFloat(PLAYBACK_SPEED, 1f)
|
||||||
@@ -229,4 +242,14 @@ object Preferences {
|
|||||||
fun setRadioSectionHidden() {
|
fun setRadioSectionHidden() {
|
||||||
App.getInstance().preferences.edit().putBoolean(RADIO_SECTION_VISIBILITY, false).apply()
|
App.getInstance().preferences.edit().putBoolean(RADIO_SECTION_VISIBILITY, false).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun isMusicDirectorySectionVisible(): Boolean {
|
||||||
|
return App.getInstance().preferences.getBoolean(MUSIC_DIRECTORY_SECTION_VISIBILITY, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getReplayGainMode(): String? {
|
||||||
|
return App.getInstance().preferences.getString(REPLAY_GAIN_MODE, "disabled")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package com.cappielloantonio.tempo.util;
|
||||||
|
|
||||||
|
import androidx.media3.common.Metadata;
|
||||||
|
import androidx.media3.common.Tracks;
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.model.ReplayGain;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class ReplayGainUtil {
|
||||||
|
private static final String[] tags = {"REPLAYGAIN_TRACK_GAIN", "REPLAYGAIN_ALBUM_GAIN", "R128_TRACK_GAIN", "R128_ALBUM_GAIN"};
|
||||||
|
|
||||||
|
public static void setReplayGain(ExoPlayer player, Tracks tracks) {
|
||||||
|
List<Metadata> metadata = getMetadata(tracks);
|
||||||
|
List<ReplayGain> gains = getReplayGains(metadata);
|
||||||
|
|
||||||
|
applyReplayGain(player, gains);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Metadata> getMetadata(Tracks tracks) {
|
||||||
|
List<Metadata> metadata = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < tracks.getGroups().size(); i++) {
|
||||||
|
Tracks.Group group = tracks.getGroups().get(i);
|
||||||
|
|
||||||
|
for (int j = 0; j < group.getMediaTrackGroup().length; j++) {
|
||||||
|
metadata.add(group.getTrackFormat(j).metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ReplayGain> getReplayGains(List<Metadata> metadata) {
|
||||||
|
List<ReplayGain> gains = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < metadata.size(); i++) {
|
||||||
|
for (int j = 0; j < metadata.get(i).length(); j++) {
|
||||||
|
Metadata.Entry entry = metadata.get(i).get(j);
|
||||||
|
|
||||||
|
if (checkReplayGain(entry)) {
|
||||||
|
ReplayGain replayGain = setReplayGains(entry);
|
||||||
|
gains.add(replayGain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gains;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean checkReplayGain(Metadata.Entry entry) {
|
||||||
|
for (String tag : tags) {
|
||||||
|
if (entry.toString().contains(tag)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ReplayGain setReplayGains(Metadata.Entry entry) {
|
||||||
|
ReplayGain replayGain = new ReplayGain();
|
||||||
|
|
||||||
|
if (entry.toString().contains(tags[0])) {
|
||||||
|
replayGain.setTrackGain(parseReplayGainTag(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.toString().contains(tags[1])) {
|
||||||
|
replayGain.setAlbumGain(parseReplayGainTag(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.toString().contains(tags[2])) {
|
||||||
|
replayGain.setTrackGain(parseReplayGainTag(entry) / 256f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.toString().contains(tags[3])) {
|
||||||
|
replayGain.setAlbumGain(parseReplayGainTag(entry) / 256f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return replayGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Float parseReplayGainTag(Metadata.Entry entry) {
|
||||||
|
try {
|
||||||
|
return Float.parseFloat(entry.toString().replaceAll("[^\\d.]", ""));
|
||||||
|
} catch (NumberFormatException exception) {
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyReplayGain(ExoPlayer player, List<ReplayGain> gains) {
|
||||||
|
if (Objects.equals(Preferences.getReplayGainMode(), "disabled") || gains.size() == 0) {
|
||||||
|
setReplayGain(player, 0f);
|
||||||
|
} else if (Objects.equals(Preferences.getReplayGainMode(), "track")) {
|
||||||
|
setReplayGain(player, gains.get(0).getTrackGain() != 0f ? gains.get(0).getTrackGain() : gains.get(0).getAlbumGain());
|
||||||
|
} else if (Objects.equals(Preferences.getReplayGainMode(), "album")) {
|
||||||
|
setReplayGain(player, gains.get(0).getAlbumGain() != 0f ? gains.get(0).getAlbumGain() : gains.get(0).getTrackGain());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setReplayGain(ExoPlayer player, float gain) {
|
||||||
|
player.setVolume((float) Math.pow(10f, gain / 20f));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,9 +7,6 @@ import android.graphics.drawable.InsetDrawable;
|
|||||||
|
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||||
|
|
||||||
import com.google.android.gms.common.ConnectionResult;
|
|
||||||
import com.google.android.gms.common.GoogleApiAvailability;
|
|
||||||
|
|
||||||
public class UIUtil {
|
public class UIUtil {
|
||||||
public static int getSpanCount(int itemCount, int maxSpan) {
|
public static int getSpanCount(int itemCount, int maxSpan) {
|
||||||
int itemSize = itemCount == 0 ? 1 : itemCount;
|
int itemSize = itemCount == 0 ? 1 : itemCount;
|
||||||
@@ -21,10 +18,6 @@ public class UIUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isCastApiAvailable(Context context) {
|
|
||||||
return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DividerItemDecoration getDividerItemDecoration(Context context) {
|
public static DividerItemDecoration getDividerItemDecoration(Context context) {
|
||||||
int[] ATTRS = new int[]{android.R.attr.listDivider};
|
int[] ATTRS = new int[]{android.R.attr.listDivider};
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import android.app.Application;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.repository.DirectoryRepository;
|
import com.cappielloantonio.tempo.repository.DirectoryRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Directory;
|
import com.cappielloantonio.tempo.subsonic.models.Directory;
|
||||||
@@ -14,34 +12,13 @@ import com.cappielloantonio.tempo.subsonic.models.Directory;
|
|||||||
public class DirectoryViewModel extends AndroidViewModel {
|
public class DirectoryViewModel extends AndroidViewModel {
|
||||||
private final DirectoryRepository directoryRepository;
|
private final DirectoryRepository directoryRepository;
|
||||||
|
|
||||||
private MutableLiveData<String> id = new MutableLiveData<>(null);
|
|
||||||
private MutableLiveData<String> name = new MutableLiveData<>(null);
|
|
||||||
|
|
||||||
private MutableLiveData<Directory> directory = new MutableLiveData<>(null);
|
|
||||||
|
|
||||||
public DirectoryViewModel(@NonNull Application application) {
|
public DirectoryViewModel(@NonNull Application application) {
|
||||||
super(application);
|
super(application);
|
||||||
|
|
||||||
directoryRepository = new DirectoryRepository();
|
directoryRepository = new DirectoryRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Directory> getDirectory() {
|
public LiveData<Directory> loadMusicDirectory(String id) {
|
||||||
return directory;
|
return directoryRepository.getMusicDirectory(id);
|
||||||
}
|
|
||||||
|
|
||||||
public void setMusicDirectoryId(String id) {
|
|
||||||
this.id.setValue(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMusicDirectoryName(String name) {
|
|
||||||
this.name.setValue(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadMusicDirectory(LifecycleOwner owner) {
|
|
||||||
this.id.observe(owner, id -> directoryRepository.getMusicDirectory(id).observe(owner, directory -> this.directory.setValue(directory)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void goBack() {
|
|
||||||
this.id.setValue(this.directory.getValue().getParentId());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,10 @@ public class HomeViewModel extends AndroidViewModel {
|
|||||||
return dicoverSongSample;
|
return dicoverSongSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LiveData<List<Child>> getRandomShuffleSample() {
|
||||||
|
return songRepository.getRandomSample(100, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
public LiveData<List<Chronology>> getGridSongSample(LifecycleOwner owner) {
|
public LiveData<List<Chronology>> getGridSongSample(LifecycleOwner owner) {
|
||||||
String server = Preferences.getServerId();
|
String server = Preferences.getServerId();
|
||||||
chronologyRepository.getLastWeek(server).observe(owner, thisGridTopSong::postValue);
|
chronologyRepository.getLastWeek(server).observe(owner, thisGridTopSong::postValue);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.lifecycle.LiveData;
|
|||||||
|
|
||||||
import com.cappielloantonio.tempo.model.RecentSearch;
|
import com.cappielloantonio.tempo.model.RecentSearch;
|
||||||
import com.cappielloantonio.tempo.repository.SearchingRepository;
|
import com.cappielloantonio.tempo.repository.SearchingRepository;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.SearchResult2;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
|
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -38,8 +39,12 @@ public class SearchViewModel extends AndroidViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<SearchResult3> search(String title) {
|
public LiveData<SearchResult2> search2(String title) {
|
||||||
return searchingRepository.search(title);
|
return searchingRepository.search2(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<SearchResult3> search3(String title) {
|
||||||
|
return searchingRepository.search3(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insertNewSearch(String search) {
|
public void insertNewSearch(String search) {
|
||||||
@@ -56,7 +61,7 @@ public class SearchViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
public List<String> getRecentSearchSuggestion() {
|
public List<String> getRecentSearchSuggestion() {
|
||||||
ArrayList<String> suggestions = new ArrayList<>();
|
ArrayList<String> suggestions = new ArrayList<>();
|
||||||
suggestions.addAll(searchingRepository.getRecentSearchSuggestion(5));
|
suggestions.addAll(searchingRepository.getRecentSearchSuggestion());
|
||||||
|
|
||||||
return suggestions;
|
return suggestions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="?attr/colorControlNormal"
|
android:viewportWidth="960"
|
||||||
android:viewportWidth="24"
|
android:viewportHeight="960">
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="@color/titleTextColor"
|
||||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
|
android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/>
|
||||||
</vector>
|
</vector>
|
||||||
9
app/src/main/res/drawable/ic_history.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/titleTextColor"
|
||||||
|
android:pathData="M480,840Q342,840 239.5,748.5Q137,657 122,520L204,520Q218,624 296.5,692Q375,760 480,760Q597,760 678.5,678.5Q760,597 760,480Q760,363 678.5,281.5Q597,200 480,200Q411,200 351,232Q291,264 250,320L360,320L360,400L120,400L120,160L200,160L200,254Q251,190 324.5,155Q398,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480Q840,555 811.5,620.5Q783,686 734.5,734.5Q686,783 620.5,811.5Q555,840 480,840ZM592,648L440,496L440,280L520,280L520,464L648,592L592,648Z"/>
|
||||||
|
</vector>
|
||||||
25
app/src/main/res/layout/dialog_battery_optimization.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/battery_optimization_primary"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:text="@string/activity_battery_optimizations_summary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/battery_optimization_secondary"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:autoLink="web"
|
||||||
|
android:text="@string/activity_battery_optimizations_conclusion" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -188,6 +188,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
|
android:nestedScrollingEnabled="false"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:paddingTop="8dp" />
|
android:paddingTop="8dp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -4,40 +4,11 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<fragment
|
||||||
android:id="@+id/appbar"
|
android:id="@+id/toolbar_fragment"
|
||||||
|
android:name="com.cappielloantonio.tempo.ui.fragment.ToolbarFragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/colorSurface"
|
|
||||||
app:layout_scrollFlags="scroll|enterAlways|snap">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="28dp"
|
|
||||||
android:layout_height="28dp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:background="@drawable/ic_toolbar_tempo" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/HeadlineMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="8dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:text="@string/app_name" />
|
|
||||||
</LinearLayout>
|
|
||||||
</com.google.android.material.appbar.MaterialToolbar>
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/loading_progress_bar"
|
android:id="@+id/loading_progress_bar"
|
||||||
@@ -121,6 +92,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
|
android:nestedScrollingEnabled="false"
|
||||||
android:paddingBottom="8dp" />
|
android:paddingBottom="8dp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -6,47 +6,11 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".ui.fragment.HomeFragment">
|
tools:context=".ui.fragment.HomeFragment">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<fragment
|
||||||
android:id="@+id/appbar"
|
android:id="@+id/toolbar_fragment"
|
||||||
|
android:name="com.cappielloantonio.tempo.ui.fragment.ToolbarFragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/colorSurface"
|
|
||||||
app:layout_scrollFlags="scroll|enterAlways|snap">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="28dp"
|
|
||||||
android:layout_height="28dp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:background="@drawable/ic_toolbar_tempo" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/HeadlineMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="8dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:text="@string/app_name" />
|
|
||||||
</LinearLayout>
|
|
||||||
</com.google.android.material.appbar.MaterialToolbar>
|
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
|
||||||
android:id="@+id/homeTabLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:tabGravity="fill"
|
|
||||||
app:tabMode="fixed" />
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
android:id="@+id/homeViewPager"
|
android:id="@+id/homeViewPager"
|
||||||
|
|||||||
@@ -113,16 +113,35 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<!-- Label and button -->
|
||||||
android:id="@+id/discovery_text_view_refreshable"
|
<LinearLayout
|
||||||
style="@style/TitleLarge"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="8dp"
|
||||||
android:paddingTop="16dp"
|
android:paddingTop="16dp"
|
||||||
android:paddingEnd="16dp"
|
android:paddingEnd="8dp">
|
||||||
android:text="@string/home_title_discovery" />
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/discovery_text_view_refreshable"
|
||||||
|
style="@style/TitleLarge"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/home_title_discovery" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/discovery_text_view_clickable"
|
||||||
|
style="@style/TitleMedium"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/home_title_discovery_shuffle_all_button" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- slideview -->
|
<!-- slideview -->
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
|||||||
@@ -4,40 +4,11 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<fragment
|
||||||
android:id="@+id/appbar"
|
android:id="@+id/toolbar_fragment"
|
||||||
|
android:name="com.cappielloantonio.tempo.ui.fragment.ToolbarFragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/colorSurface"
|
|
||||||
app:layout_scrollFlags="scroll|enterAlways|snap">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="28dp"
|
|
||||||
android:layout_height="28dp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:background="@drawable/ic_toolbar_tempo" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/HeadlineMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="8dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:text="@string/app_name" />
|
|
||||||
</LinearLayout>
|
|
||||||
</com.google.android.material.appbar.MaterialToolbar>
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:id="@+id/fragment_library_nested_scroll_view"
|
android:id="@+id/fragment_library_nested_scroll_view"
|
||||||
@@ -75,6 +46,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
|
android:nestedScrollingEnabled="false"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="16dp"
|
||||||
android:paddingTop="8dp"
|
android:paddingTop="8dp"
|
||||||
@@ -308,6 +280,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
|
android:nestedScrollingEnabled="false"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:paddingTop="8dp"
|
android:paddingTop="8dp"
|
||||||
android:paddingBottom="8dp" />
|
android:paddingBottom="8dp" />
|
||||||
|
|||||||
@@ -1,54 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<com.paulrybitskyi.persistentsearchview.PersistentSearchView
|
|
||||||
android:id="@+id/persistentSearchView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:paddingStart="12dp"
|
|
||||||
android:paddingEnd="12dp"
|
|
||||||
app:areSuggestionsDisabled="false"
|
|
||||||
app:cardBackgroundColor="?attr/colorSurface"
|
|
||||||
app:cardCornerRadius="0dp"
|
|
||||||
app:cardElevation="0dp"
|
|
||||||
app:clearInputButtonDrawable="@drawable/ic_close"
|
|
||||||
app:dividerColor="@color/dividerColor"
|
|
||||||
app:isClearInputButtonEnabled="true"
|
|
||||||
app:isDismissableOnTouchOutside="true"
|
|
||||||
app:isProgressBarEnabled="true"
|
|
||||||
app:isVoiceInputButtonEnabled="false"
|
|
||||||
app:leftButtonDrawable="@drawable/ic_search"
|
|
||||||
app:progressBarColor="?attr/colorOnSurface"
|
|
||||||
app:queryInputBarIconColor="@color/searchPlaceholderColor"
|
|
||||||
app:queryInputCursorColor="?attr/colorOnSurface"
|
|
||||||
app:queryInputHint="@string/search_hint"
|
|
||||||
app:queryInputHintColor="@color/searchPlaceholderColor"
|
|
||||||
app:queryInputTextColor="@color/searchPlaceholderColor"
|
|
||||||
app:shouldDimBehind="false"
|
|
||||||
app:suggestionRecentSearchIconColor="@color/searchColor"
|
|
||||||
app:suggestionSearchSuggestionIconColor="@color/searchColor"
|
|
||||||
app:suggestionSelectedTextColor="?attr/colorPrimary"
|
|
||||||
app:suggestionTextColor="@color/searchColor"
|
|
||||||
app:suggestionIconColor="@color/searchColor" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/search_bar_divider"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0.75dp"
|
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:layout_marginEnd="12dp"
|
|
||||||
android:layout_below="@+id/persistentSearchView"
|
|
||||||
android:background="?attr/colorSurfaceVariant" />
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:id="@+id/search_result_nested_scroll_view"
|
android:id="@+id/search_result_nested_scroll_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_below="@id/search_bar_divider">
|
app:layout_behavior="@string/searchbar_scrolling_view_behavior">
|
||||||
|
|
||||||
<!-- Search result -->
|
<!-- Search result -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -56,6 +16,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="64dp"
|
||||||
android:paddingBottom="@dimen/global_padding_bottom">
|
android:paddingBottom="@dimen/global_padding_bottom">
|
||||||
|
|
||||||
<!-- Artist -->
|
<!-- Artist -->
|
||||||
@@ -150,4 +111,39 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
</RelativeLayout>
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.search.SearchBar
|
||||||
|
android:id="@+id/search_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/search_hint">
|
||||||
|
|
||||||
|
</com.google.android.material.search.SearchBar>
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.search.SearchView
|
||||||
|
android:id="@+id/search_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:hint="@string/search_hint"
|
||||||
|
app:layout_anchor="@id/search_bar">
|
||||||
|
|
||||||
|
<!-- Content goes here (ScrollView, RecyclerView, etc.). -->
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scrollbars="none"
|
||||||
|
android:paddingBottom="@dimen/global_padding_bottom">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/search_view_suggestion_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"/>
|
||||||
|
</ScrollView>
|
||||||
|
</com.google.android.material.search.SearchView>
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
36
app/src/main/res/layout/fragment_toolbar.xml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.appbar.AppBarLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/appbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/colorSurface"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlways|snap">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="28dp"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:background="@drawable/ic_toolbar_tempo" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/HeadlineMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/app_name" />
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.material.appbar.MaterialToolbar>
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
48
app/src/main/res/layout/item_search_suggestion.xml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/search_suggestion_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_history"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/search_suggestion_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginHorizontal="12dp"
|
||||||
|
android:text="@string/label_placeholder"
|
||||||
|
android:textAppearance="?attr/textAppearanceBody2"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/search_suggestion_icon"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/search_suggestion_delete_icon"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/search_suggestion_icon"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/search_suggestion_icon" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/search_suggestion_delete_icon"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_close"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/search_suggestion_icon"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/search_suggestion_title"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/search_suggestion_icon"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
36
app/src/main/res/layout/layout_toolbar.xml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.appbar.AppBarLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/appbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/colorSurface"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlways|snap">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="28dp"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:background="@drawable/ic_toolbar_tempo" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/HeadlineMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/app_name" />
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.material.appbar.MaterialToolbar>
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
@@ -286,13 +286,16 @@
|
|||||||
android:id="@+id/directoryFragment"
|
android:id="@+id/directoryFragment"
|
||||||
android:name="com.cappielloantonio.tempo.ui.fragment.DirectoryFragment"
|
android:name="com.cappielloantonio.tempo.ui.fragment.DirectoryFragment"
|
||||||
android:label="DirectoryFragment"
|
android:label="DirectoryFragment"
|
||||||
tools:layout="@layout/fragment_directory" />
|
tools:layout="@layout/fragment_directory">
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_directoryFragment_to_directoryFragment"
|
||||||
|
app:destination="@id/directoryFragment" />
|
||||||
|
</fragment>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/indexFragment"
|
android:id="@+id/indexFragment"
|
||||||
android:name="com.cappielloantonio.tempo.ui.fragment.IndexFragment"
|
android:name="com.cappielloantonio.tempo.ui.fragment.IndexFragment"
|
||||||
android:label="IndexFragment"
|
android:label="IndexFragment"
|
||||||
tools:layout="@layout/fragment_index">
|
tools:layout="@layout/fragment_index">
|
||||||
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_indexFragment_to_directoryFragment"
|
android:id="@+id/action_indexFragment_to_directoryFragment"
|
||||||
app:destination="@id/directoryFragment" />
|
app:destination="@id/directoryFragment" />
|
||||||
|
|||||||
@@ -141,4 +141,15 @@
|
|||||||
<item>12</item>
|
<item>12</item>
|
||||||
<item>6</item>
|
<item>6</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="replay_gain_titles">
|
||||||
|
<item>Disabled</item>
|
||||||
|
<item>Track preferred</item>
|
||||||
|
<item>Album preferred</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="replay_gain_values">
|
||||||
|
<item>disabled</item>
|
||||||
|
<item>track</item>
|
||||||
|
<item>album</item>
|
||||||
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="activity_battery_optimizations_summary">Please disable battery optimizations for media playback while the screen is off.</string>
|
<string name="activity_battery_optimizations_summary">Please disable battery optimizations for media playback while the screen is off.</string>
|
||||||
|
<string name="activity_battery_optimizations_conclusion">If in trouble visit https://dontkillmyapp.com. It provides detailed instructions on how to disable any power-saving features that may affect app\'s performance.</string>
|
||||||
<string name="activity_battery_optimizations_title">Battery Optimizations</string>
|
<string name="activity_battery_optimizations_title">Battery Optimizations</string>
|
||||||
<string name="activity_info_offline_mode">Offline mode</string>
|
<string name="activity_info_offline_mode">Offline mode</string>
|
||||||
<string name="activity_negative_button">Ignore</string>
|
<string name="battery_optimization_negative_button">Ignore</string>
|
||||||
<string name="activity_neutral_button">Disable</string>
|
<string name="battery_optimization_positive_button">Disable</string>
|
||||||
|
<string name="battery_optimization_neutral_button">Don\'t ask again</string>
|
||||||
<string name="album_bottom_sheet_add_to_queue">Add to queue</string>
|
<string name="album_bottom_sheet_add_to_queue">Add to queue</string>
|
||||||
<string name="album_bottom_sheet_download_all">Download all</string>
|
<string name="album_bottom_sheet_download_all">Download all</string>
|
||||||
<string name="album_bottom_sheet_go_to_artist">Go to artist</string>
|
<string name="album_bottom_sheet_go_to_artist">Go to artist</string>
|
||||||
@@ -52,6 +54,7 @@
|
|||||||
<string name="download_title_section">Downloads</string>
|
<string name="download_title_section">Downloads</string>
|
||||||
<string name="empty_string" />
|
<string name="empty_string" />
|
||||||
<string name="error_required">Required</string>
|
<string name="error_required">Required</string>
|
||||||
|
<string name="error_server_prefix">http or https prefix required</string>
|
||||||
<string name="exo_download_notification_channel_name">Downloads</string>
|
<string name="exo_download_notification_channel_name">Downloads</string>
|
||||||
<string name="filter_info_selection">Select two or more filters</string>
|
<string name="filter_info_selection">Select two or more filters</string>
|
||||||
<string name="filter_title">Filter</string>
|
<string name="filter_title">Filter</string>
|
||||||
@@ -74,6 +77,7 @@
|
|||||||
<string name="home_title_discovery">Discovery</string>
|
<string name="home_title_discovery">Discovery</string>
|
||||||
<string name="home_title_recently_added">Recently added</string>
|
<string name="home_title_recently_added">Recently added</string>
|
||||||
<string name="home_title_recently_added_see_all_button">See all</string>
|
<string name="home_title_recently_added_see_all_button">See all</string>
|
||||||
|
<string name="home_title_discovery_shuffle_all_button">Shuffle all</string>
|
||||||
<string name="home_title_starred_albums">★ Starred albums</string>
|
<string name="home_title_starred_albums">★ Starred albums</string>
|
||||||
<string name="home_title_starred_albums_see_all_button">See all</string>
|
<string name="home_title_starred_albums_see_all_button">See all</string>
|
||||||
<string name="home_title_starred_artists">★ Starred artists</string>
|
<string name="home_title_starred_artists">★ Starred artists</string>
|
||||||
@@ -181,6 +185,8 @@
|
|||||||
<string name="settings_max_bitrate_wifi">Bitrate in Wi-Fi</string>
|
<string name="settings_max_bitrate_wifi">Bitrate in Wi-Fi</string>
|
||||||
<string name="settings_max_bitrate_mobile">Bitrate in mobile</string>
|
<string name="settings_max_bitrate_mobile">Bitrate in mobile</string>
|
||||||
<string name="settings_media_cache">Size of media file cache</string>
|
<string name="settings_media_cache">Size of media file cache</string>
|
||||||
|
<string name="settings_music_directory">Show music directories</string>
|
||||||
|
<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_queue_syncing_title">Sync play queue for this user</string>
|
<string name="settings_queue_syncing_title">Sync play queue for this user</string>
|
||||||
<string name="settings_queue_syncing_countdown">Sync timer</string>
|
<string name="settings_queue_syncing_countdown">Sync timer</string>
|
||||||
<string name="settings_queue_syncing_summary">If enabled, the user will have the ability to save their play queue and will have the ability to load state when opening the application.</string>
|
<string name="settings_queue_syncing_summary">If enabled, the user will have the ability to save their play queue and will have the ability to load state when opening the application.</string>
|
||||||
@@ -188,11 +194,13 @@
|
|||||||
<string name="settings_podcast_summary">If enabled, show the podcast section.</string>
|
<string name="settings_podcast_summary">If enabled, show the podcast section.</string>
|
||||||
<string name="settings_radio">Show radio</string>
|
<string name="settings_radio">Show radio</string>
|
||||||
<string name="settings_radio_summary">If enabled, show the radio section.</string>
|
<string name="settings_radio_summary">If enabled, show the radio section.</string>
|
||||||
|
<string name="settings_replay_gain">Set replay gain mode</string>
|
||||||
<string name="settings_rounded_corner">Rounded corners</string>
|
<string name="settings_rounded_corner">Rounded corners</string>
|
||||||
<string name="settings_rounded_corner_summary">If enabled, sets a curvature angle for all rendered covers. The changes will take effect on restart.</string>
|
<string name="settings_rounded_corner_summary">If enabled, sets a curvature angle for all rendered covers. The changes will take effect on restart.</string>
|
||||||
<string name="settings_rounded_corner_size">Corners size</string>
|
<string name="settings_rounded_corner_size">Corners size</string>
|
||||||
<string name="settings_rounded_corner_size_summary">Sets the magnitude of the curvature angle.</string>
|
<string name="settings_rounded_corner_size_summary">Sets the magnitude of the curvature angle.</string>
|
||||||
<string name="settings_scan_title">Scan library</string>
|
<string name="settings_scan_title">Scan library</string>
|
||||||
|
<string name="settings_summary_replay_gain">Replay gain is a feature that allows you to adjust the volume level of audio tracks for a consistent listening experience. This setting is only effective if the track contains the necessary metadata.</string>
|
||||||
<string name="settings_summary_syncing">Returns the state of the play queue for this user. This includes the tracks in the play queue, the currently playing track, and the position within this track. The server must support this feature.</string>
|
<string name="settings_summary_syncing">Returns the state of the play queue for this user. This includes the tracks in the play queue, the currently playing track, and the position within this track. The server must support this feature.</string>
|
||||||
<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">Priority given to the transcoding mode. If set to \"Direct play\" the bitrate of the file will not be changed.</string>
|
||||||
<string name="settings_sync_starred_tracks_for_offline_use_summary">If enabled, starred tracks will be downloaded for offline use.</string>
|
<string name="settings_sync_starred_tracks_for_offline_use_summary">If enabled, starred tracks will be downloaded for offline use.</string>
|
||||||
@@ -200,6 +208,7 @@
|
|||||||
<string name="settings_theme">Theme</string>
|
<string name="settings_theme">Theme</string>
|
||||||
<string name="settings_title_data">Data</string>
|
<string name="settings_title_data">Data</string>
|
||||||
<string name="settings_title_general">General</string>
|
<string name="settings_title_general">General</string>
|
||||||
|
<string name="settings_title_replay_gain">Replay Gain</string>
|
||||||
<string name="settings_title_syncing">Syncing</string>
|
<string name="settings_title_syncing">Syncing</string>
|
||||||
<string name="settings_title_transcoding">Transcoding</string>
|
<string name="settings_title_transcoding">Transcoding</string>
|
||||||
<string name="settings_title_ui">UI</string>
|
<string name="settings_title_ui">UI</string>
|
||||||
@@ -252,4 +261,6 @@
|
|||||||
<string name="menu_sort_name">Name</string>
|
<string name="menu_sort_name">Name</string>
|
||||||
<string name="menu_sort_random">Random</string>
|
<string name="menu_sort_random">Random</string>
|
||||||
<string name="description_empty_title">No description available</string>
|
<string name="description_empty_title">No description available</string>
|
||||||
|
<!-- TODO: Remove or change this placeholder text -->
|
||||||
|
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -51,6 +51,12 @@
|
|||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:summary="@string/settings_radio_summary"
|
android:summary="@string/settings_radio_summary"
|
||||||
android:key="radio_section_visibility" />
|
android:key="radio_section_visibility" />
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
android:title="@string/settings_music_directory"
|
||||||
|
android:defaultValue="true"
|
||||||
|
android:summary="@string/settings_music_directory_summary"
|
||||||
|
android:key="music_directory_section_visibility" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory app:title="@string/settings_title_data">
|
<PreferenceCategory app:title="@string/settings_title_data">
|
||||||
@@ -133,6 +139,22 @@
|
|||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory app:title="@string/settings_title_replay_gain">
|
||||||
|
<Preference
|
||||||
|
app:selectable="false"
|
||||||
|
app:summary="@string/settings_summary_replay_gain" />
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
app:defaultValue="disabled"
|
||||||
|
app:dialogTitle="@string/settings_replay_gain"
|
||||||
|
app:entries="@array/replay_gain_titles"
|
||||||
|
app:entryValues="@array/replay_gain_values"
|
||||||
|
app:key="replay_gain_mode"
|
||||||
|
app:title="@string/settings_replay_gain"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory app:title="@string/settings_title_syncing">
|
<PreferenceCategory app:title="@string/settings_title_syncing">
|
||||||
<Preference
|
<Preference
|
||||||
app:selectable="false"
|
app:selectable="false"
|
||||||
@@ -141,7 +163,7 @@
|
|||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
android:title="@string/settings_queue_syncing_title"
|
android:title="@string/settings_queue_syncing_title"
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:summary="@string/settings_sync_starred_tracks_for_offline_use_summary"
|
android:summary="@string/settings_queue_syncing_summary"
|
||||||
android:key="queue_syncing" />
|
android:key="queue_syncing" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
@@ -160,6 +182,7 @@
|
|||||||
app:summary="@string/settings_about_summary" />
|
app:summary="@string/settings_about_summary" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
|
android:key="version"
|
||||||
app:summary="@string/settings_version_summary"
|
app:summary="@string/settings_version_summary"
|
||||||
app:title="@string/settings_version_title" />
|
app:title="@string/settings_version_title" />
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,242 @@
|
|||||||
|
package com.cappielloantonio.tempo.service
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||||
|
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
import android.app.TaskStackBuilder
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.media3.common.*
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
|
import androidx.media3.exoplayer.source.TrackGroupArray
|
||||||
|
import androidx.media3.exoplayer.trackselection.TrackSelectionArray
|
||||||
|
import androidx.media3.session.*
|
||||||
|
import androidx.media3.session.MediaSession.ControllerInfo
|
||||||
|
import com.cappielloantonio.tempo.R
|
||||||
|
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
||||||
|
import com.cappielloantonio.tempo.util.Constants
|
||||||
|
import com.cappielloantonio.tempo.util.DownloadUtil
|
||||||
|
import com.cappielloantonio.tempo.util.ReplayGainUtil
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.google.common.util.concurrent.Futures
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
|
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
class MediaService : MediaLibraryService() {
|
||||||
|
private val librarySessionCallback = CustomMediaLibrarySessionCallback()
|
||||||
|
|
||||||
|
private lateinit var player: ExoPlayer
|
||||||
|
private lateinit var mediaLibrarySession: MediaLibrarySession
|
||||||
|
private lateinit var customCommands: List<CommandButton>
|
||||||
|
|
||||||
|
private var customLayout = ImmutableList.of<CommandButton>()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON =
|
||||||
|
"android.media3.session.demo.SHUFFLE_ON"
|
||||||
|
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
|
||||||
|
"android.media3.session.demo.SHUFFLE_OFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
|
||||||
|
initializeCustomCommands()
|
||||||
|
initializePlayer()
|
||||||
|
initializeMediaLibrarySession()
|
||||||
|
initializePlayerListener()
|
||||||
|
|
||||||
|
setPlayer(player)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetSession(controllerInfo: ControllerInfo): MediaLibrarySession {
|
||||||
|
return mediaLibrarySession
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
releasePlayer()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class CustomMediaLibrarySessionCallback : MediaLibrarySession.Callback {
|
||||||
|
|
||||||
|
override fun onConnect(
|
||||||
|
session: MediaSession,
|
||||||
|
controller: ControllerInfo
|
||||||
|
): MediaSession.ConnectionResult {
|
||||||
|
val connectionResult = super.onConnect(session, controller)
|
||||||
|
val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon()
|
||||||
|
|
||||||
|
customCommands.forEach { commandButton ->
|
||||||
|
// TODO: Aggiungere i comandi personalizzati
|
||||||
|
// commandButton.sessionCommand?.let { availableSessionCommands.add(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return MediaSession.ConnectionResult.accept(
|
||||||
|
availableSessionCommands.build(),
|
||||||
|
connectionResult.availablePlayerCommands
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPostConnect(session: MediaSession, controller: ControllerInfo) {
|
||||||
|
if (!customLayout.isEmpty() && controller.controllerVersion != 0) {
|
||||||
|
ignoreFuture(mediaLibrarySession.setCustomLayout(controller, customLayout))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCustomCommand(
|
||||||
|
session: MediaSession,
|
||||||
|
controller: ControllerInfo,
|
||||||
|
customCommand: SessionCommand,
|
||||||
|
args: Bundle
|
||||||
|
): ListenableFuture<SessionResult> {
|
||||||
|
if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) {
|
||||||
|
player.shuffleModeEnabled = true
|
||||||
|
customLayout = ImmutableList.of(customCommands[1])
|
||||||
|
session.setCustomLayout(customLayout)
|
||||||
|
} else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) {
|
||||||
|
player.shuffleModeEnabled = false
|
||||||
|
customLayout = ImmutableList.of(customCommands[0])
|
||||||
|
session.setCustomLayout(customLayout)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAddMediaItems(
|
||||||
|
mediaSession: MediaSession,
|
||||||
|
controller: ControllerInfo,
|
||||||
|
mediaItems: List<MediaItem>
|
||||||
|
): ListenableFuture<List<MediaItem>> {
|
||||||
|
val updatedMediaItems = mediaItems.map {
|
||||||
|
it.buildUpon()
|
||||||
|
.setUri(it.requestMetadata.mediaUri)
|
||||||
|
.setMediaMetadata(it.mediaMetadata)
|
||||||
|
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
return Futures.immediateFuture(updatedMediaItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializeCustomCommands() {
|
||||||
|
customCommands =
|
||||||
|
listOf(
|
||||||
|
getShuffleCommandButton(
|
||||||
|
SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY)
|
||||||
|
),
|
||||||
|
getShuffleCommandButton(
|
||||||
|
SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
customLayout = ImmutableList.of(customCommands[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializePlayer() {
|
||||||
|
player = ExoPlayer.Builder(this)
|
||||||
|
.setRenderersFactory(getRenderersFactory())
|
||||||
|
.setMediaSourceFactory(getMediaSourceFactory())
|
||||||
|
.setAudioAttributes(AudioAttributes.DEFAULT, true)
|
||||||
|
.setHandleAudioBecomingNoisy(true)
|
||||||
|
.setWakeMode(C.WAKE_MODE_NETWORK)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializeMediaLibrarySession() {
|
||||||
|
val sessionActivityPendingIntent =
|
||||||
|
TaskStackBuilder.create(this).run {
|
||||||
|
addNextIntent(Intent(this@MediaService, MainActivity::class.java))
|
||||||
|
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaLibrarySession =
|
||||||
|
MediaLibrarySession.Builder(this, player, librarySessionCallback)
|
||||||
|
.setSessionActivity(sessionActivityPendingIntent)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
if (!customLayout.isEmpty()) {
|
||||||
|
mediaLibrarySession.setCustomLayout(customLayout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializePlayerListener() {
|
||||||
|
player.addListener(object : Player.Listener {
|
||||||
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||||
|
if (mediaItem == null) return
|
||||||
|
|
||||||
|
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
|
||||||
|
MediaManager.setLastPlayedTimestamp(mediaItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTracksChanged(tracks: Tracks) {
|
||||||
|
ReplayGainUtil.setReplayGain(player, tracks)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||||
|
if (!isPlaying) {
|
||||||
|
MediaManager.setPlayingPausedTimestamp(
|
||||||
|
player.currentMediaItem,
|
||||||
|
player.currentPosition
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPositionDiscontinuity(
|
||||||
|
oldPosition: Player.PositionInfo,
|
||||||
|
newPosition: Player.PositionInfo,
|
||||||
|
reason: Int
|
||||||
|
) {
|
||||||
|
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
|
||||||
|
|
||||||
|
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
|
||||||
|
if (oldPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
|
||||||
|
MediaManager.scrobble(oldPosition.mediaItem)
|
||||||
|
MediaManager.saveChronology(oldPosition.mediaItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
|
||||||
|
MediaManager.setLastPlayedTimestamp(newPosition.mediaItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setPlayer(player: Player) {
|
||||||
|
mediaLibrarySession.player = player
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun releasePlayer() {
|
||||||
|
player.release()
|
||||||
|
mediaLibrarySession.release()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("PrivateResource")
|
||||||
|
private fun getShuffleCommandButton(sessionCommand: SessionCommand): CommandButton {
|
||||||
|
val isOn = sessionCommand.customAction == CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON
|
||||||
|
return CommandButton.Builder()
|
||||||
|
.setDisplayName(
|
||||||
|
getString(
|
||||||
|
if (isOn) R.string.exo_controls_shuffle_on_description
|
||||||
|
else R.string.exo_controls_shuffle_off_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setSessionCommand(sessionCommand)
|
||||||
|
.setIconResId(if (isOn) R.drawable.exo_icon_shuffle_off else R.drawable.exo_icon_shuffle_on)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ignoreFuture(customLayout: ListenableFuture<SessionResult>) {
|
||||||
|
/* Do nothing. */
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false)
|
||||||
|
|
||||||
|
private fun getMediaSourceFactory() =
|
||||||
|
DefaultMediaSourceFactory(this).setDataSourceFactory(DownloadUtil.getDataSourceFactory(this))
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.cappielloantonio.tempo.ui.fragment;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.R;
|
||||||
|
import com.cappielloantonio.tempo.databinding.FragmentToolbarBinding;
|
||||||
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
public class ToolbarFragment extends Fragment {
|
||||||
|
private static final String TAG = "ToolbarFragment";
|
||||||
|
|
||||||
|
private FragmentToolbarBinding bind;
|
||||||
|
private MainActivity activity;
|
||||||
|
|
||||||
|
public ToolbarFragment() {
|
||||||
|
// Required empty public constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
inflater.inflate(R.menu.main_page_menu, menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
activity = (MainActivity) getActivity();
|
||||||
|
|
||||||
|
bind = FragmentToolbarBinding.inflate(inflater, container, false);
|
||||||
|
View view = bind.getRoot();
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.action_search) {
|
||||||
|
activity.navController.navigate(R.id.searchFragment);
|
||||||
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.action_settings) {
|
||||||
|
activity.navController.navigate(R.id.settingsFragment);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.cappielloantonio.tempo.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public class Flavors {
|
||||||
|
public static void initializeCastContext(Context context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
225
app/src/notquitemy/res/drawable/ic_splash_logo.xml
Normal file
201
app/src/notquitemy/res/drawable/ic_toolbar_tempo.xml
Normal file
15
app/src/notquitemy/res/menu/main_page_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"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_search"
|
||||||
|
android:icon="@drawable/ic_search"
|
||||||
|
android:title="@string/menu_search_button"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_settings"
|
||||||
|
android:icon="@drawable/ic_settings"
|
||||||
|
android:title="@string/menu_settings_button"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
</menu>
|
||||||
6
app/src/notquitemy/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||||
|
</adaptive-icon>
|
||||||
BIN
app/src/notquitemy/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
app/src/notquitemy/res/mipmap-hdpi/ic_launcher_background.png
Normal file
|
After Width: | Height: | Size: 857 B |
BIN
app/src/notquitemy/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
app/src/notquitemy/res/mipmap-hdpi/ic_launcher_monochrome.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
app/src/notquitemy/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/notquitemy/res/mipmap-mdpi/ic_launcher_background.png
Normal file
|
After Width: | Height: | Size: 463 B |
BIN
app/src/notquitemy/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
app/src/notquitemy/res/mipmap-mdpi/ic_launcher_monochrome.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
app/src/notquitemy/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
app/src/notquitemy/res/mipmap-xhdpi/ic_launcher_background.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/notquitemy/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
app/src/notquitemy/res/mipmap-xhdpi/ic_launcher_monochrome.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
app/src/notquitemy/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
app/src/notquitemy/res/mipmap-xxhdpi/ic_launcher_background.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
app/src/notquitemy/res/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
app/src/notquitemy/res/mipmap-xxhdpi/ic_launcher_monochrome.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
app/src/notquitemy/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
app/src/notquitemy/res/mipmap-xxxhdpi/ic_launcher_background.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
app/src/notquitemy/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
app/src/notquitemy/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
@@ -18,8 +18,10 @@ import com.cappielloantonio.tempo.R
|
|||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
||||||
import com.cappielloantonio.tempo.util.Constants
|
import com.cappielloantonio.tempo.util.Constants
|
||||||
import com.cappielloantonio.tempo.util.DownloadUtil
|
import com.cappielloantonio.tempo.util.DownloadUtil
|
||||||
import com.cappielloantonio.tempo.util.UIUtil
|
import com.cappielloantonio.tempo.util.ReplayGainUtil
|
||||||
import com.google.android.gms.cast.framework.CastContext
|
import com.google.android.gms.cast.framework.CastContext
|
||||||
|
import com.google.android.gms.common.ConnectionResult
|
||||||
|
import com.google.android.gms.common.GoogleApiAvailability
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.common.util.concurrent.Futures
|
import com.google.common.util.concurrent.Futures
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
@@ -117,6 +119,9 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
|||||||
browser: ControllerInfo,
|
browser: ControllerInfo,
|
||||||
params: LibraryParams?
|
params: LibraryParams?
|
||||||
): ListenableFuture<LibraryResult<MediaItem>> {
|
): ListenableFuture<LibraryResult<MediaItem>> {
|
||||||
|
if (params != null && params.isRecent) {
|
||||||
|
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_NOT_SUPPORTED))
|
||||||
|
}
|
||||||
return Futures.immediateFuture(LibraryResult.ofItem(MediaItemTree.getRootItem(), params))
|
return Futures.immediateFuture(LibraryResult.ofItem(MediaItemTree.getRootItem(), params))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +211,9 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initializeCastPlayer() {
|
private fun initializeCastPlayer() {
|
||||||
if (UIUtil.isCastApiAvailable(this)) {
|
if (GoogleApiAvailability.getInstance()
|
||||||
|
.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
|
||||||
|
) {
|
||||||
castPlayer = CastPlayer(CastContext.getSharedInstance(this))
|
castPlayer = CastPlayer(CastContext.getSharedInstance(this))
|
||||||
castPlayer.setSessionAvailabilityListener(this)
|
castPlayer.setSessionAvailabilityListener(this)
|
||||||
}
|
}
|
||||||
@@ -234,11 +241,15 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
|||||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||||
if (mediaItem == null) return
|
if (mediaItem == null) return
|
||||||
|
|
||||||
if(reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
|
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
|
||||||
MediaManager.setLastPlayedTimestamp(mediaItem)
|
MediaManager.setLastPlayedTimestamp(mediaItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onTracksChanged(tracks: Tracks) {
|
||||||
|
ReplayGainUtil.setReplayGain(player, tracks)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||||
if (!isPlaying) {
|
if (!isPlaying) {
|
||||||
MediaManager.setPlayingPausedTimestamp(
|
MediaManager.setPlayingPausedTimestamp(
|
||||||
@@ -255,7 +266,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
|||||||
) {
|
) {
|
||||||
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
|
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
|
||||||
|
|
||||||
if(reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
|
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
|
||||||
if (oldPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
|
if (oldPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
|
||||||
MediaManager.scrobble(oldPosition.mediaItem)
|
MediaManager.scrobble(oldPosition.mediaItem)
|
||||||
MediaManager.saveChronology(oldPosition.mediaItem)
|
MediaManager.saveChronology(oldPosition.mediaItem)
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.cappielloantonio.tempo.ui.fragment;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.R;
|
||||||
|
import com.cappielloantonio.tempo.databinding.FragmentToolbarBinding;
|
||||||
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
|
import com.google.android.gms.cast.framework.CastButtonFactory;
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
public class ToolbarFragment extends Fragment {
|
||||||
|
private static final String TAG = "ToolbarFragment";
|
||||||
|
|
||||||
|
private FragmentToolbarBinding bind;
|
||||||
|
private MainActivity activity;
|
||||||
|
|
||||||
|
public ToolbarFragment() {
|
||||||
|
// Required empty public constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
inflater.inflate(R.menu.main_page_menu, menu);
|
||||||
|
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
activity = (MainActivity) getActivity();
|
||||||
|
|
||||||
|
bind = FragmentToolbarBinding.inflate(inflater, container, false);
|
||||||
|
View view = bind.getRoot();
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.action_search) {
|
||||||
|
activity.navController.navigate(R.id.searchFragment);
|
||||||
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.action_settings) {
|
||||||
|
activity.navController.navigate(R.id.settingsFragment);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.cappielloantonio.tempo.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.google.android.gms.cast.framework.CastContext;
|
||||||
|
import com.google.android.gms.common.ConnectionResult;
|
||||||
|
import com.google.android.gms.common.GoogleApiAvailability;
|
||||||
|
|
||||||
|
public class Flavors {
|
||||||
|
public static void initializeCastContext(Context context) {
|
||||||
|
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS)
|
||||||
|
CastContext.getSharedInstance(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,13 +13,6 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven {
|
|
||||||
url 'https://jitpack.io'
|
|
||||||
}
|
|
||||||
maven {
|
|
||||||
url 'https://oss.sonatype.org/content/repositories/snapshots'
|
|
||||||
}
|
|
||||||
jcenter()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ org.gradle.jvmargs=-Xmx2048m
|
|||||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
# Automatically convert third-party libraries to use AndroidX
|
# Automatically convert third-party libraries to use AndroidX
|
||||||
android.enableJetifier=true
|
android.enableJetifier=false
|
||||||
android.defaults.buildfeatures.buildconfig=true
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
android.nonTransitiveRClass=false
|
android.nonTransitiveRClass=false
|
||||||
android.nonFinalResIds=false
|
android.nonFinalResIds=false
|
||||||