diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c3be5606..67f84832 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -87,26 +87,26 @@ Assemble Release: # We generate a signed package for each commit to develop as well as when making a release. # Since the develop signed apk are not persistent they can be downloaded for around 3 weeks before Gitlab deletes them. Generate Signed APK: - variables: - APK_NAME: ultrasonic-${CI_COMMIT_SHA} stage: Sign APK # We don't need the gradle cache here cache: [] script: - openssl aes-256-cbc -K ${ULTRASONIC_KEYSTORE_KEY} -iv ${ULTRASONIC_KEYSTORE_IV} -in ultrasonic-keystore.enc -out ultrasonic-keystore -d - mkdir -p ${CI_PROJECT_DIR}/ultrasonic-release - - ${ANDROID_HOME}/build-tools/*/zipalign -v 4 ultrasonic/build/outputs/apk/release/ultrasonic-release-unsigned.apk ${CI_PROJECT_DIR}/ultrasonic-release/${APK_NAME}.apk - - ${ANDROID_HOME}/build-tools/*/apksigner sign --verbose --ks ${CI_PROJECT_DIR}/ultrasonic-keystore --ks-pass pass:${ULTRASONIC_KEYSTORE_STOREPASS} --key-pass pass:${ULTRASONIC_KEYSTORE_KEYPASS} ${CI_PROJECT_DIR}/ultrasonic-release/${APK_NAME}.apk - - ${ANDROID_HOME}/build-tools/*/apksigner verify --verbose ${CI_PROJECT_DIR}/ultrasonic-release/${APK_NAME}.apk + - ${ANDROID_HOME}/build-tools/*/zipalign -v 4 ultrasonic/build/outputs/apk/release/ultrasonic-release-unsigned.apk ${CI_PROJECT_DIR}/ultrasonic-release/${PACKAGE_APK} + - ${ANDROID_HOME}/build-tools/*/apksigner sign --verbose --ks ${CI_PROJECT_DIR}/ultrasonic-keystore --ks-pass pass:${ULTRASONIC_KEYSTORE_STOREPASS} --key-pass pass:${ULTRASONIC_KEYSTORE_KEYPASS} ${CI_PROJECT_DIR}/ultrasonic-release/${PACKAGE_APK} + - ${ANDROID_HOME}/build-tools/*/apksigner verify --verbose ${CI_PROJECT_DIR}/ultrasonic-release/${PACKAGE_APK} artifacts: - name: $APK_NAME + name: $PACKAGE_APK paths: - ultrasonic-release/ rules: - - if: $CI_COMMIT_REF_NAME == "develop" && $CI_PROJECT_ID == $ROOT_PROJECT_ID && $CI_PIPELINE_SOURCE != "merge_request_event" + # Run when releasing a new tag - if: $CI_COMMIT_TAG && $CI_PROJECT_ID == $ROOT_PROJECT_ID + # Or when adding a new commit to develop (but never inside merge events) + - if: $CI_COMMIT_REF_NAME == "develop" && $CI_PROJECT_ID == $ROOT_PROJECT_ID && $CI_PIPELINE_SOURCE != "merge_request_event" variables: - APK_NAME: ultrasonic-${CI_COMMIT_TAG} + PACKAGE_APK: ultrasonic-${CI_COMMIT_SHA}.apk Publish Signed APK: @@ -133,10 +133,19 @@ Release: RoboTest: stage: Release image: gcr.io/google.com/cloudsdktool/google-cloud-cli:latest + # We don't need the gradle cache here + cache: [] script: - curl --silent "https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/download-secure-files/-/raw/main/installer" | bash - - gcloud auth activate-service-account --key-file .secure_files/ultrasonic-61089-8ab2ad46c8a8.json - - gcloud firebase test android run --token $FIREBASE_TOKEN --type robo --app ultrasonic-release/${PACKAGE_APK} --device model=Nexus6,version=21,locale=en,orientation=portrait --device model=Nexus7,version=19,locale=fr,orientation=landscape + - gcloud auth activate-service-account --key-file .secure_files/firebase-key.json + - gcloud firebase test android run --project ultrasonic-61089 --type robo --app ultrasonic-release/${PACKAGE_APK} --robo-directives click:button1= --device model=Nexus6,version=21,locale=en,orientation=portrait --device model=Pixel3,version=28,locale=fr,orientation=landscape --device model=Pixel5,version=30,locale=zh,orientation=portrait rules: + # Run when releasing a new tag - if: $CI_COMMIT_TAG && $CI_PROJECT_ID == $ROOT_PROJECT_ID + # or when requested by using [ROBO] inside the commit message and merging to develop + # Would be nice to be able to run it in a MR as well, but currently not possible + # Because it would not have access to the protected keys. + - if: $CI_COMMIT_MESSAGE =~ /^\[ROBO\].*/ && $CI_PROJECT_ID == $ROOT_PROJECT_ID && $CI_COMMIT_REF_NAME == "develop" && $CI_PIPELINE_SOURCE != "merge_request_event" + variables: + PACKAGE_APK: ultrasonic-${CI_COMMIT_SHA}.apk diff --git a/fastlane/metadata/android/en-US/changelogs/124.txt b/fastlane/metadata/android/en-US/changelogs/124.txt new file mode 100644 index 00000000..9850d96c --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/124.txt @@ -0,0 +1,15 @@ +Features: +- Search is accessible through a new icon on the main screen +- Modernize Back Handling +- Reenable R8 Code minification +- Add a "Play Random Songs" shortcut + +Bug fixes: +- Readd the "Star" button to the Now Playing screen +- Fix a rare crash when shuffling playlists with duplicate entries +- Fix a crash when choosing "Play next" on an empty playlist. +- Tracks buttons flash a scrollbar sometimes in Android 13 +- Fix EndlessScrolling in genre listing +- Couldn't delete a track when shuffle was active +- Upgrade material to 1.9.0 + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a18ca95a..816a968e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ navigation = "2.6.0" gradlePlugin = "8.0.2" androidxcore = "1.10.1" ktlint = "0.43.2" -ktlintGradle = "11.4.0" +ktlintGradle = "11.4.2" detekt = "1.23.0" preferences = "1.2.0" media3 = "1.0.2" @@ -15,7 +15,7 @@ androidSupport = "1.6.0" materialDesign = "1.9.0" constraintLayout = "2.1.4" multidex = "2.0.1" -room = "2.5.1" +room = "2.5.2" kotlin = "1.8.22" kotlinxCoroutines = "1.7.1" viewModelKtx = "2.6.1" @@ -30,10 +30,10 @@ picasso = "2.8" junit4 = "4.13.2" junit5 = "5.9.3" -mockito = "5.3.1" +mockito = "5.4.0" mockitoKotlin = "5.0.0" kluent = "1.73" -apacheCodecs = "1.15" +apacheCodecs = "1.16.0" robolectric = "4.10.3" timber = "5.0.1" fastScroll = "2.0.1" diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index a437074b..07af3496 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -9,8 +9,8 @@ android { defaultConfig { applicationId "org.moire.ultrasonic" - versionCode 123 - versionName "4.6.0" + versionCode 124 + versionName "4.6.1-RC" minSdkVersion versions.minSdk targetSdkVersion versions.targetSdk diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/BaseAdapter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/BaseAdapter.kt index fb48151a..bca2e0b5 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/BaseAdapter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/BaseAdapter.kt @@ -26,7 +26,8 @@ import timber.log.Timber * It should be kept generic enough that it can be used a Base for all lists in the app. */ @Suppress("unused", "UNUSED_PARAMETER") -class BaseAdapter : MultiTypeAdapter(), FastScrollRecyclerView.SectionedAdapter { +class BaseAdapter(allowDuplicateEntries: Boolean = false) : + MultiTypeAdapter(), FastScrollRecyclerView.SectionedAdapter { // Update the BoundedTreeSet if selection type is changed internal var selectionType: SelectionType = SelectionType.MULTIPLE @@ -41,7 +42,7 @@ class BaseAdapter : MultiTypeAdapter(), FastScrollRecyclerView private val diffCallback = GenericDiffCallback() init { - setHasStableIds(true) + setHasStableIds(!allowDuplicateEntries) } override fun getItemId(position: Int): Long { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/TrackViewBinder.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/TrackViewBinder.kt index bca305aa..91bcb09d 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/TrackViewBinder.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/TrackViewBinder.kt @@ -43,14 +43,7 @@ class TrackViewBinder( override fun onBindViewHolder(holder: TrackViewHolder, item: Identifiable) { val diffAdapter = adapter as BaseAdapter<*> - val track: Track = when (item) { - is Track -> { - item - } - else -> { - return - } - } + val track = (item as? Track) ?: return // Remove observer before binding holder.observableChecked.removeObservers(lifecycleOwner) @@ -59,7 +52,7 @@ class TrackViewBinder( song = track, checkable = checkable, draggable = draggable, - diffAdapter.isSelected(item.longId) + diffAdapter.isSelected(track.longId) ) holder.itemView.setOnLongClickListener { @@ -110,7 +103,7 @@ class TrackViewBinder( diffAdapter.selectionRevision.observe( lifecycleOwner ) { - val newStatus = diffAdapter.isSelected(item.longId) + val newStatus = diffAdapter.isSelected(track.longId) if (newStatus != holder.check.isChecked) holder.check.isChecked = newStatus } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EntryListFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EntryListFragment.kt index dca68e30..48815367 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EntryListFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EntryListFragment.kt @@ -135,7 +135,6 @@ abstract class EntryListFragment : MultiListFragment() { item.id, append = false, autoPlay = true, - shuffle = false, playNext = false, isArtist = isArtist ) @@ -145,7 +144,6 @@ abstract class EntryListFragment : MultiListFragment() { item.id, append = false, autoPlay = true, - shuffle = true, playNext = true, isArtist = isArtist ) @@ -155,7 +153,6 @@ abstract class EntryListFragment : MultiListFragment() { item.id, append = true, autoPlay = false, - shuffle = false, playNext = false, isArtist = isArtist ) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt index 7f50a870..adcffd95 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt @@ -176,7 +176,7 @@ class PlayerFragment : private val binding get() = _binding!! private val viewAdapter: BaseAdapter by lazy { - BaseAdapter() + BaseAdapter(allowDuplicateEntries = true) } override fun onCreate(savedInstanceState: Bundle?) { @@ -536,8 +536,10 @@ class PlayerFragment : if (update.success == true && update.rating is HeartRating) { if (update.rating.isHeart) { starMenuItem.setIcon(fullStar) + starMenuItem.setTitle(R.string.download_menu_unstar) } else { starMenuItem.setIcon(hollowStar) + starMenuItem.setTitle(R.string.download_menu_star) } } else if (update.success == false) { Toast.makeText(context, "Setting rating failed", Toast.LENGTH_SHORT) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt index cd71ee7c..9b923e95 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt @@ -316,7 +316,6 @@ open class TrackCollectionFragment( append = append, playNext = false, autoPlay = !append, - shuffle = false, playlistName = null, fragment = this ) @@ -616,7 +615,6 @@ open class TrackCollectionFragment( append = true, playNext = true, autoPlay = false, - shuffle = false, playlistName = navArgs.playlistName, fragment = this@TrackCollectionFragment ) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerManager.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerManager.kt index dba110ec..d1ffd067 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerManager.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerManager.kt @@ -429,7 +429,10 @@ class MediaPlayerManager( when (insertionMode) { InsertionMode.CLEAR -> clear() InsertionMode.APPEND -> insertAt = mediaItemCount - InsertionMode.AFTER_CURRENT -> insertAt = currentMediaItemIndex + 1 + InsertionMode.AFTER_CURRENT -> { + // Must never be larger than the count of items (especially when empty) + insertAt = (currentMediaItemIndex + 1).coerceAtMost(mediaItemCount) + } } val mediaItems: List = songs.map { @@ -437,10 +440,13 @@ class MediaPlayerManager( result } - if (shuffle) isShufflePlayEnabled = true Timber.w("Adding ${mediaItems.size} media items") controller?.addMediaItems(insertAt, mediaItems) + // There is a bug in media3 ( https://github.com/androidx/media/issues/480 ), + // so we must first add the tracks, and then enable shuffle + if (shuffle) isShufflePlayEnabled = true + prepare() // Playback doesn't start correctly when the player is in STATE_ENDED. diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt index 1a653a50..55bceca2 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt @@ -98,7 +98,7 @@ class DownloadHandler( isDirectory: Boolean = true, append: Boolean, autoPlay: Boolean, - shuffle: Boolean, + shuffle: Boolean = false, playNext: Boolean, isArtist: Boolean = false ) { @@ -141,7 +141,7 @@ class DownloadHandler( append: Boolean, playNext: Boolean, autoPlay: Boolean, - shuffle: Boolean, + shuffle: Boolean = false, playlistName: String? = null, fragment: Fragment ) { diff --git a/ultrasonic/src/main/res/menu/nowplaying.xml b/ultrasonic/src/main/res/menu/nowplaying.xml index 39126689..d3bc8b59 100644 --- a/ultrasonic/src/main/res/menu/nowplaying.xml +++ b/ultrasonic/src/main/res/menu/nowplaying.xml @@ -12,7 +12,7 @@ + app:showAsAction="ifRoom|collapseActionView" /> \ No newline at end of file diff --git a/ultrasonic/src/main/res/values-es/strings.xml b/ultrasonic/src/main/res/values-es/strings.xml index 0f66c28f..9d4d3466 100644 --- a/ultrasonic/src/main/res/values-es/strings.xml +++ b/ultrasonic/src/main/res/values-es/strings.xml @@ -44,7 +44,7 @@ Reproducir a continuación Reproducir ahora Reproducción aleatoria - Public + Público Guardar Seleccionar todo Título @@ -57,14 +57,14 @@ Marcador añadido a %s. Nada se esta descargando La lista de reproducción esta vacía - El control remoto no esta habilitado. Por favor habilita el modo jukebox en Configuración > Usuarios en tu servidor de Subsonic. + El control remoto no esta habilitado. Por favor habilita el modo gramola (jukebox) en Configuración > Usuarios en tu servidor de Subsonic. Control remoto apagado. La música se reproduce en tu dispositivo. Control remoto no disponible en modo fuera de línea. Control remoto encendido. La música se reproduce en el servidor. Control remoto no soportado. Por favor actualiza tu servidor de Subsonic. Ecualizador - Apagar Jukebox - Encender Jukebox + Apagar gramola + Encender gramola Letras Guardar lista de reproducción Pantalla apagada @@ -87,7 +87,7 @@ Ecualizador Seleccionar preajuste Error - Jukebox por defecto + Gramola por defecto No se encontraron letras Predeterminado del sistema Chino (China) @@ -446,7 +446,7 @@ 100 canciones 50 canciones 1000 canciones - Jukebox + Gramola 500 canciones Funciones soportadas No se puede reanudar la reproducción @@ -454,4 +454,5 @@ Tasa de bits máxima: al fijar una canción de forma permanente Canciones aleatorias Reproducir las canciones aleatoriamente + No me gusta \ No newline at end of file diff --git a/ultrasonic/src/main/res/values-gl/strings.xml b/ultrasonic/src/main/res/values-gl/strings.xml index 86dcf5c1..36cb490a 100644 --- a/ultrasonic/src/main/res/values-gl/strings.xml +++ b/ultrasonic/src/main/res/values-gl/strings.xml @@ -24,4 +24,40 @@ Podcast Buscar Enviar unha mensaxe + Marcadores + Enviar + Imaxe do avatar + Álbum + Comentar + Confirmar + Borrar + Descargar + Detalles + Múltiples xéneros + Nome + OK + Reproducir a continuación + Reproducir agora + Reproducir aleatoriamente + Público + Gardar + Seleccionar todo + Desancorar + ¿Realmente queres desancorar a selección actual\? + Ultrasonic + Título + Artista + Cancelar + Reproducir última + Ancorar + Mostra un cadro de diálogo de confirmación antes de eliminar ou desancorar as cancións + + %d canción seleccionada para ser ancorada + %d cancións seleccionadas para ser ancoradas + + Tasa de bits máxima: ao fixar unha canción de forma permanente + + %d canción desancorada + %d cancións desancoradas + \ No newline at end of file diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index f8d91b62..08db0901 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -346,6 +346,7 @@ Set Bookmark Delete Bookmark Star + Unstar Clear Playlist Shares No shares available on server