From 0ce5a56051e8838d3a53f28ff6f62968c5e19749 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=93scar=20Garc=C3=ADa=20Amor?= <ogarcia@connectical.com>
Date: Fri, 18 Dec 2020 09:42:24 +0100
Subject: [PATCH 1/7] Update CONTRIBUTING.md

Add a link with more info about sign commits.
---
 CONTRIBUTING.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 63bbb71c..d52377f1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -29,7 +29,7 @@ and test.
 
 ### Pull Request Process
 
-1. Ensure all commits are signed-off.
+1. Ensure [all commits are signed-off](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/about-commit-signature-verification).
 2. Check tests for the new code are added.
 3. Check code style is passing.
 4. Check code static analysis is passing.

From 7af666037d22fbfc794e63c177399e16dbaa958b Mon Sep 17 00:00:00 2001
From: lbonn <bonnans.l@gmail.com>
Date: Fri, 18 Dec 2020 01:20:58 +0100
Subject: [PATCH 2/7] Fall back to path when comparing tracks

Tracks will be sorted by ascending path if neither album, disc and track
id can help.

This is helpful for browsing a loose collection of tracks tagged under
the same album.

Signed-off-by: lbonn <bonnans.l@gmail.com>
---
 .../util/EntryByDiscAndTrackComparator.java           | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/EntryByDiscAndTrackComparator.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/EntryByDiscAndTrackComparator.java
index 3ec2a8c6..bc24dfa6 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/EntryByDiscAndTrackComparator.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/EntryByDiscAndTrackComparator.java
@@ -18,6 +18,8 @@ public class EntryByDiscAndTrackComparator implements Comparator<MusicDirectory.
 		Integer trackY = y.getTrack();
 		String albumX = x.getAlbum();
 		String albumY = y.getAlbum();
+		String pathX = x.getPath();
+		String pathY = y.getPath();
 
 		int albumComparison = compare(albumX, albumY);
 
@@ -33,7 +35,14 @@ public class EntryByDiscAndTrackComparator implements Comparator<MusicDirectory.
 			return discComparison;
 		}
 
-		return compare(trackX == null ? 0 : trackX, trackY == null ? 0 : trackY);
+		int trackComparison = compare(trackX == null ? 0 : trackX, trackY == null ? 0 : trackY);
+
+		if (trackComparison != 0)
+		{
+			return trackComparison;
+		}
+
+		return compare(pathX == null ? "" : pathX, pathY == null ? "" : pathY);
 	}
 
 	private static int compare(long a, long b)

From f43c1072693a9cb7efe7938277bc82bebd5fa2db Mon Sep 17 00:00:00 2001
From: lbonn <bonnans.l@gmail.com>
Date: Mon, 21 Dec 2020 23:32:50 +0100
Subject: [PATCH 3/7] Do not sort special collections

It does not make sense to sort:

- random songs
- recently added albums
- recently played albums
- most played albums
- top rated albums
- random albums

As the order returned by the server is more relevant in these cases

Signed-off-by: lbonn <bonnans.l@gmail.com>
---
 .../activity/SelectAlbumActivity.java         | 21 ++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java
index b146794f..aaaaeb96 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java
@@ -806,6 +806,11 @@ public class SelectAlbumActivity extends SubsonicTabActivity
 
 		new LoadTask()
 		{
+			@Override
+			protected boolean sortableCollection() {
+				return false;
+			}
+
 			@Override
 			protected MusicDirectory load(MusicService service) throws Exception
 			{
@@ -882,6 +887,16 @@ public class SelectAlbumActivity extends SubsonicTabActivity
 
 		new LoadTask()
 		{
+			@Override
+			protected boolean sortableCollection() {
+				if (albumListType.equals("newest") || albumListType.equals("random") ||
+						albumListType.equals("highest") || albumListType.equals("recent") ||
+						albumListType.equals("frequent")) {
+					return false;
+				}
+				return true;
+			}
+
 			@Override
 			protected MusicDirectory load(MusicService service) throws Exception
 			{
@@ -1096,6 +1111,10 @@ public class SelectAlbumActivity extends SubsonicTabActivity
 
 		protected abstract MusicDirectory load(MusicService service) throws Exception;
 
+		protected boolean sortableCollection() {
+			return true;
+		}
+
 		@Override
 		protected Pair<MusicDirectory, Boolean> doInBackground() throws Throwable
 		{
@@ -1111,7 +1130,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity
 			MusicDirectory musicDirectory = result.getFirst();
 			List<MusicDirectory.Entry> entries = musicDirectory.getChildren();
 
-			if (Util.getShouldSortByDisc(SelectAlbumActivity.this))
+			if (sortableCollection() && Util.getShouldSortByDisc(SelectAlbumActivity.this))
 			{
 				Collections.sort(entries, new EntryByDiscAndTrackComparator());
 			}

From b7bb0851dbfcbf2989a03ba268065e40c7a47ed9 Mon Sep 17 00:00:00 2001
From: Nite <shunite@gmail.com>
Date: Tue, 12 Jan 2021 16:51:31 +0100
Subject: [PATCH 4/7] Fixed Genres list

---
 .../main/java/org/moire/ultrasonic/view/ArtistAdapter.java    | 4 ++--
 .../src/main/java/org/moire/ultrasonic/view/GenreAdapter.java | 4 ++--
 ...artist_search_list_item.xml => generic_text_list_item.xml} | 0
 3 files changed, 4 insertions(+), 4 deletions(-)
 rename ultrasonic/src/main/res/layout/{artist_search_list_item.xml => generic_text_list_item.xml} (100%)

diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/ArtistAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/ArtistAdapter.java
index 1102d15a..b1a7d42b 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/ArtistAdapter.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/ArtistAdapter.java
@@ -49,7 +49,7 @@ public class ArtistAdapter extends ArrayAdapter<Artist> implements SectionIndexe
 
 	public ArtistAdapter(Context context, List<Artist> artists)
 	{
-		super(context, R.layout.artist_list_item, artists);
+		super(context, R.layout.generic_text_list_item, artists);
 
 		layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 
@@ -81,7 +81,7 @@ public class ArtistAdapter extends ArrayAdapter<Artist> implements SectionIndexe
     ) {
         View rowView = convertView;
         if (rowView == null) {
-            rowView = layoutInflater.inflate(R.layout.artist_search_list_item, parent, false);
+            rowView = layoutInflater.inflate(R.layout.generic_text_list_item, parent, false);
         }
         ((TextView) rowView).setText(getItem(position).getName());
 
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/GenreAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/GenreAdapter.java
index f3c7e34a..f21d827d 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/GenreAdapter.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/GenreAdapter.java
@@ -48,7 +48,7 @@ public class GenreAdapter extends ArrayAdapter<Genre> implements SectionIndexer
 
 	public GenreAdapter(Context context, List<Genre> genres)
 	{
-		super(context, R.layout.artist_list_item, genres);
+		super(context, R.layout.generic_text_list_item, genres);
 
         layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 
@@ -75,7 +75,7 @@ public class GenreAdapter extends ArrayAdapter<Genre> implements SectionIndexer
     public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
         View rowView = convertView;
         if (rowView == null) {
-            rowView = layoutInflater.inflate(R.layout.artist_list_item, parent, false);
+            rowView = layoutInflater.inflate(R.layout.generic_text_list_item, parent, false);
         }
 
         ((TextView) rowView).setText(getItem(position).getName());
diff --git a/ultrasonic/src/main/res/layout/artist_search_list_item.xml b/ultrasonic/src/main/res/layout/generic_text_list_item.xml
similarity index 100%
rename from ultrasonic/src/main/res/layout/artist_search_list_item.xml
rename to ultrasonic/src/main/res/layout/generic_text_list_item.xml

From 90090ba8707ba69f8e372b95326c5bfd0984d6b1 Mon Sep 17 00:00:00 2001
From: Nite <shunite@gmail.com>
Date: Thu, 14 Jan 2021 09:30:23 +0100
Subject: [PATCH 5/7] Changed how the NowPlaying view is displayed and hidden

---
 .../activity/SubsonicTabActivity.java         | 28 +++++++++++--------
 1 file changed, 16 insertions(+), 12 deletions(-)

diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java
index 637e2bc4..e16c886e 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java
@@ -170,6 +170,9 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen
 			restart();
 		}
 
+		// This must be filled here because onCreate is called before the derived objects would call setContentView
+		getNowPlayingView();
+
 		if (!nowPlayingHidden)
 		{
 			showNowPlaying();
@@ -242,6 +245,19 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen
 		return destroyed;
 	}
 
+	private void getNowPlayingView()
+	{
+		if (nowPlayingView == null)
+		{
+			try {
+				nowPlayingView = findViewById(R.id.now_playing);
+			}
+			catch (Exception exception) {
+				Timber.w(exception, "An exception has occurred while trying to get the nowPlayingView by findViewById");
+			}
+		}
+	}
+
 	public void showNowPlaying()
 	{
 		this.runOnUiThread(new Runnable()
@@ -260,8 +276,6 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen
 							return null;
 						}
 
-						nowPlayingView = findViewById(R.id.now_playing);
-
 						if (nowPlayingView != null)
 						{
 							PlayerState playerState = mediaPlayerControllerLazy.getValue().getPlayerState();
@@ -307,11 +321,6 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen
 			return;
 		}
 
-		if (nowPlayingView == null)
-		{
-			nowPlayingView = findViewById(R.id.now_playing);
-		}
-
 		if (nowPlayingView != null)
 		{
 			try
@@ -407,11 +416,6 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen
 	{
 		try
 		{
-			if (nowPlayingView == null)
-			{
-				nowPlayingView = findViewById(R.id.now_playing);
-			}
-
 			if (nowPlayingView != null)
 			{
 				setVisibilityOnUiThread(nowPlayingView, View.GONE);

From 4fdab06271008644e359ae51959a01d9a23f46ff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=93scar=20Garc=C3=ADa=20Amor?= <ogarcia@connectical.com>
Date: Tue, 19 Jan 2021 08:33:57 +0100
Subject: [PATCH 6/7] Added big fat warning about #310 in README.md
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Óscar García Amor <ogarcia@connectical.com>
---
 README.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/README.md b/README.md
index 30e14864..fe85fd26 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,12 @@ Ultrasonic is free and open-source music streaming Android client for [Subsonic]
 We currently don't have that much time to spend developing Subsonic, so any
 contributions or active developers are always welcomed.
 
+### BIG FAT WARNING
+
+At this moment all new features and enhancements are blocked by #310, this
+means that no new PR will be accepted until this issue is closed. Obviously
+any PR to fix #310 are welcomed and celebrated.
+
 ## Download
 
 App is available to download at following stores:

From bda1deae2bdc2a55ba9cecd928440c0c1ecb677a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=93scar=20Garc=C3=ADa=20Amor?= <ogarcia@connectical.com>
Date: Tue, 19 Jan 2021 08:52:13 +0100
Subject: [PATCH 7/7] Bump version to 2.18.0
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Óscar García Amor <ogarcia@connectical.com>
---
 ultrasonic/build.gradle                       |  4 +-
 ultrasonic/src/main/res/values-fr/strings.xml | 61 +++++++++++++++++++
 2 files changed, 63 insertions(+), 2 deletions(-)

diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle
index 4701f7df..2c1fe2bf 100644
--- a/ultrasonic/build.gradle
+++ b/ultrasonic/build.gradle
@@ -9,8 +9,8 @@ android {
 
     defaultConfig {
         applicationId "org.moire.ultrasonic"
-        versionCode 86
-        versionName "2.17.2"
+        versionCode 88
+        versionName "2.18.0"
 
         minSdkVersion versions.minSdk
         targetSdkVersion versions.targetSdk
diff --git a/ultrasonic/src/main/res/values-fr/strings.xml b/ultrasonic/src/main/res/values-fr/strings.xml
index 317bd76b..5555ec0e 100644
--- a/ultrasonic/src/main/res/values-fr/strings.xml
+++ b/ultrasonic/src/main/res/values-fr/strings.xml
@@ -3,6 +3,7 @@
 
     <string name="background_task.loading">Chargement&#8230;</string>
     <string name="background_task.network_error">Une erreur réseau est survenue. Veuillez vérifier l\'adresse du serveur ou réessayer plus tard.</string>
+    <string name="background_task.unsupported_api">L’API v%1$s du serveur ne supporte pas cette fonction.</string>
     <string name="background_task.no_network">Cette application requiert un accès au réseau. Veuillez activer le Wi-Fi ou le réseau mobile.</string>
     <string name="background_task.not_found">Ressources introuvables. Veuillez vérifier l\'adresse du serveur.</string>
     <string name="background_task.parse_error">Réponse incorrecte. Veuillez vérifier l\'adresse du serveur.</string>
@@ -313,6 +314,8 @@
     <string name="settings.use_folder_for_album_artist_summary">Dossier de niveau supérieur devient le nom de l\'artiste de l\'album</string>
     <string name="settings.use_id3">Naviguer en utilisant ID3 Tags</string>
     <string name="settings.use_id3_summary">Utiliser ID3 tag à la place du système de fichier basique</string>
+    <string name="settings.show_artist_picture">Afficher l’image de l’artiste dans la liste</string>
+    <string name="settings.show_artist_picture_summary">Affiche l’image de l’artiste dans la liste des artistes si celle-ci est disponible</string>
     <string name="settings.video_title">Vidéo</string>
     <string name="settings.video_player">Lecteur vidéo</string>
     <string name="settings.view_refresh">Actualisation de la vue</string>
@@ -382,6 +385,8 @@
     <string name="settings.show_all_songs_by_artist_summary">Ajouter une nouvelle entrée de l\'affichage de l\'artiste pour accéder à toutes les titres pour un artiste</string>
     <string name="download.menu_show_artist">Afficher l\'artiste</string>
     <string name="settings.scan_media">Scan Media After Download</string>
+    <string name="settings.scan_media_summary">Balayer automatiquement les médias après téléchargement</string>
+    <string name="settings.image_loader_concurrency">Chargements d’images simultanés</string>
     <string name="settings.image_loader_concurrency_1">1</string>
     <string name="settings.image_loader_concurrency_2">2</string>
     <string name="settings.image_loader_concurrency_3">3</string>
@@ -394,7 +399,59 @@
     <string name="settings.image_loader_concurrency_10">10</string>
     <string name="settings.image_loader_concurrency_11">11</string>
     <string name="settings.image_loader_concurrency_12">12</string>
+    <string name="albumArt">albumArt</string>
+    <string name="common_multiple_years">Années multiples</string>
+    <string name="settings.playback.resume_on_bluetooth_device">Reprendre lorsqu’un appareil Bluetooth se connecte</string>
+    <string name="settings.playback.pause_on_bluetooth_device">Mettre en pause lorsqu’un appareil Bluetooth se déconnecte</string>
+    <string name="settings.playback.bluetooth_all">Tous les appareils Bluetooth</string>
+    <string name="settings.playback.bluetooth_a2dp">Seulement les appareils audio (A2DP)</string>
+    <string name="settings.playback.bluetooth_disabled">Désactivé</string>
+    <string name="settings.playback.single_button_bluetooth_device">Bouton unique Lecture/Pause en Bluetooth</string>
+    <string name="settings.playback.single_button_bluetooth_device_summary">Activer cela peut aider sur les anciens appareils Bluetooth lorsque Lecture/Pause ne fonctionne pas correctement</string>
+    <string name="settings.debug.title">Paramètres de debug</string>
+    <string name="settings.debug.log_to_file">Enregistrer les logs de debug dans des fichiers</string>
+    <string name="settings.debug.log_path">Les fichiers de log sont disponibles dans %1$s/%2$s</string>
+    <string name="settings.debug.log_summary">Il y a %1$s fichiers de logs prenant %2$s MB  d’espace dans le répertoire %3$s. Souhaitez-vous les conserver ?</string>
+    <string name="settings.debug.log_keep">Conserver les fichiers</string>
+    <string name="settings.debug.log_delete">Supprimer les fichiers</string>
+    <string name="settings.debug.log_deleted">Fichiers de log supprimés</string>
+
+    <string name="permissions.access_error">Ultrasonic ne peut pas accéder au cache. Le répertoire de cache a été réinitialisé sur le chemin par défaut.</string>
+    <string name="permissions.message_box_title">Attention</string>
+    <string name="permissions.permission_missing">Ultrasonic requiert les droits de lecture/écriture sur le répertoire de cache. Le répertoire de cache a été réinitialisé sur le chemin par défaut.</string>
+    <string name="permissions.rationale_title">Demande de permission</string>
+    <string name="permissions.rationale_description_failed">Ultrasonic requiert les droits de lecture/écriture sur le répertoire de cache. Veuillez autoriser Ultrasonic à accéder au système de fichiers.</string>
+    <string name="permissions.permanent_denial_title">Permissions refusées de manière permanente</string>
+    <string name="permissions.permanent_denial_description">Ultrasonic requiert les droits de lecture/écriture sur le répertoire de cache. Vous pouvez les activer dans les paramètres Android de l’application. Si vous rejetez cette permission, le répertoire par défaut sera utilisé pour le cache.</string>
+    <string name="permissions.open_settings">Ouvrir les paramètres</string>
+    <string name="permissions.rationale_description_initial">Afin de pouvoir modifier le répertoire de cache, Ultrasonic requiert les droits de lecture/écriture sur le système de fichiers.</string>
+
+    <string name="filepicker.select_folder">Sélectionner un dossier</string>
+    <string name="filepicker.create_folder">Créer un dossier</string>
+    <string name="filepicker.create_folder_failed">Impossible de créer un dossier</string>
+    <string name="filepicker.internal">%1$s (Interne)</string>
+    <string name="filepicker.default_app_folder">Répertoire par défaut de l’application : %1$s (Mémoire externe)</string>
+    <string name="filepicker.enter_folder_name">Saisir le nom du dossier</string>
+    <string name="filepicker.create">Créer</string>
+    <string name="filepicker.name_invalid">Veuillez entrer un nom de dossier valide</string>
+    <string name="filepicker.already_exists">Ce dossier existe déjà.\nVeuillez donner un autre nom</string>
+    <string name="filepicker.select">Sélectionner</string>
+    <string name="filepicker.default">Utiliser la valeur par défaut</string>
+    <string name="filepicker.available_drives">Emplacements de stockage disponibles :</string>
+
+    <string name="server_selector.label">Serveurs configurés</string>
+    <string name="server_selector.delete_confirmation">Êtes-vous sûr de vouloir supprimer ce serveur ?</string>
+    <string name="server_editor.label">Édition du serveur</string>
     <string name="server_editor.new_label">Ajouter un serveur</string>
+    <string name="server_editor.leave_confirmation">Êtes-vous sûr de vouloir quitter et perdre vos modifications ?</string>
+    <string name="server_editor.required">Ce champ est requis</string>
+    <string name="server_menu.edit">Éditer</string>
+    <string name="server_menu.delete">Supprimer</string>
+    <string name="server_menu.move_up">Déplacer vers le haut</string>
+    <string name="server_menu.move_down">Déplacer vers le bas</string>
+    <string name="server_editor.authentication">Authentification</string>
+    <string name="server_editor.advanced">Paramètres avancés</string>
+
     <plurals name="select_album_n_songs">
         <item quantity="one">%d titre</item>
         <item quantity="other">%d titres</item>
@@ -403,6 +460,10 @@
         <item quantity="one">%d titre sélectionnée pour être épinglé.</item>
         <item quantity="other">%d titres sélectionnée pour être épinglé.</item>
     </plurals>
+    <plurals name="select_album_n_songs_downloaded">
+        <item quantity="one">1 titre sélectionné pour être téléchargé.</item>
+        <item quantity="other">%d titres sélectionnés pour être téléchargés.</item>
+    </plurals>
     <plurals name="select_album_n_songs_unpinned">
         <item quantity="one">%d titre sélectionné pour être dégoupillé.</item>
         <item quantity="other">%d titres sélectionnés pour être dégoupillé.</item>