Skip to content

Commit d01d707

Browse files
authored
Merge pull request #172 from tobexyz/feat/issue164_2
Feat/issue164 2
2 parents abf31ea + ac048e2 commit d01d707

23 files changed

Lines changed: 489 additions & 170 deletions

yaacc/src/main/java/de/yaacc/browser/ContentListFragment.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,15 +376,13 @@ public void playAllChildsOfParentFrom(DIDLObject item) {
376376
}
377377
ContentDirectoryBrowseResult result = upnpClient.browseSync(new Position(0, item.getParentID(), upnpClient.getProviderDevice().getIdentity().getUdn().getIdentifierString(), item.getTitle()));
378378
if (result == null || (result.getResult() != null && result.getResult().getItems().isEmpty())) {
379-
Log.d(getClass().getName(), "Browse result of parent no direct items found...");
380379
if (result != null && result.getResult() != null && !result.getResult().getContainers().isEmpty()) {
381380
play(upnpClient.initializePlayers(upnpClient.toItemList(result.getResult())));
382381
} else {
383382
play(upnpClient.initializePlayers(item));
384383
}
385384
} else {
386385
List<Item> items = result.getResult() == null ? new ArrayList<>() : result.getResult().getItems();
387-
Log.d(getClass().getName(), "Browse result items: " + items.size());
388386
int index = items.indexOf(item);
389387
if (index > 0) {
390388
//sort selected item to the beginning

yaacc/src/main/java/de/yaacc/player/PlaylistDialogFragment.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333

3434
import java.beans.PropertyChangeEvent;
3535
import java.beans.PropertyChangeListener;
36-
import java.util.Collections;
3736

3837
import de.yaacc.R;
3938
import de.yaacc.Yaacc;
@@ -104,14 +103,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
104103
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
105104
int fromPosition = viewHolder.getAdapterPosition();
106105
int toPosition = target.getAdapterPosition();
107-
if (player.isPlaying() && (viewHolder.getAdapterPosition() <= player.getCurrentItemIndex()
108-
|| target.getAdapterPosition() <= player.getCurrentItemIndex())) {
109-
//do not allow to drag current playing item
110-
return false;
111-
}
112-
Collections.swap(player.getItems(), fromPosition, toPosition);
113-
recyclerView.getAdapter().notifyItemMoved(fromPosition, toPosition);
114-
return true;
106+
return playlistItemAdapter.moveItem(fromPosition, toPosition);
115107
}
116108

117109
@Override

yaacc/src/main/java/de/yaacc/player/PlaylistItemAdapter.java

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import android.content.Context;
44
import android.graphics.Typeface;
5+
import android.util.Log;
6+
import android.view.KeyEvent;
57
import android.view.LayoutInflater;
68
import android.view.View;
79
import android.view.ViewGroup;
@@ -13,6 +15,7 @@
1315
import androidx.recyclerview.widget.RecyclerView;
1416

1517
import java.util.ArrayList;
18+
import java.util.Collections;
1619
import java.util.List;
1720

1821
import de.yaacc.R;
@@ -24,6 +27,7 @@ public class PlaylistItemAdapter extends RecyclerView.Adapter<PlaylistItemAdapte
2427
private final Context context;
2528
private final Player player;
2629
private RecyclerView listView;
30+
private int selectedForMovePosition = -1; // -1 indicates no item is selected for move
2731

2832
public PlaylistItemAdapter(Context ctx, RecyclerView listView, Player player) {
2933
super();
@@ -64,30 +68,93 @@ public void onBindViewHolder(final PlaylistItemAdapter.ViewHolder holder, final
6468
if (player.isPlaying() && listPosition <= player.getCurrentItemIndex()) {
6569
holder.dragIcon.setImageDrawable(ThemeHelper.tintDrawable(context.getResources().getDrawable(R.drawable.ic_baseline_lock_32, context.getTheme()), context.getTheme()));
6670
holder.deleteIcon.setVisibility(View.GONE);
71+
holder.dragIcon.setFocusable(false); // Cannot move locked items
6772
} else {
6873
holder.dragIcon.setImageDrawable(ThemeHelper.tintDrawable(context.getResources().getDrawable(R.drawable.ic_baseline_drag_indicator_32, context.getTheme()), context.getTheme()));
6974
holder.deleteIcon.setVisibility(View.VISIBLE);
75+
holder.dragIcon.setFocusable(true);
7076
}
7177
holder.deleteIcon.setOnClickListener(l -> removeItem(listPosition));
7278
if (player.isPlaying() && player.getCurrentItemIndex() == listPosition) {
7379
holder.name.setTypeface(null, Typeface.BOLD);
7480
holder.name.setText(item.getTitle() + " ▶");
81+
} else {
82+
holder.name.setTypeface(null, Typeface.NORMAL); // Ensure non-playing items are not bold
7583
}
84+
85+
if (listPosition == selectedForMovePosition) {
86+
// Highlight the selected item (e.g., change background color or add a border)
87+
Log.d(getClass().getName(), "Item selected for keyboard move: " + item.getTitle());
88+
holder.itemView.setBackgroundColor(context.getResources().getColor(R.color.design_default_color_secondary));
89+
} else {
90+
holder.itemView.setBackgroundColor(context.getResources().getColor(android.R.color.transparent));
91+
}
92+
93+
holder.dragIcon.setOnKeyListener((v, keyCode, event) -> {
94+
if (event.getAction() == KeyEvent.ACTION_DOWN) {
95+
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
96+
if (selectedForMovePosition == holder.getAdapterPosition()) { // Use holder.getAdapterPosition() for safety
97+
// Deselect item
98+
int previouslySelected = selectedForMovePosition;
99+
selectedForMovePosition = -1;
100+
notifyItemChanged(previouslySelected); // To remove highlight
101+
return true;
102+
} else if (selectedForMovePosition == -1 && !(player.isPlaying() && holder.getAdapterPosition() <= player.getCurrentItemIndex())) {
103+
// Select item for move
104+
selectedForMovePosition = holder.getAdapterPosition();
105+
notifyItemChanged(selectedForMovePosition); // To add highlight
106+
return true;
107+
}
108+
} else if (selectedForMovePosition == holder.getAdapterPosition()) {
109+
if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
110+
moveItem(selectedForMovePosition, selectedForMovePosition - 1);
111+
return true;
112+
} else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
113+
moveItem(selectedForMovePosition, selectedForMovePosition + 1);
114+
return true;
115+
}
116+
}
117+
}
118+
return false;
119+
});
76120
}
77121

78-
private void removeItem(int listPosition) {
79-
if (player.getItems().size() > listPosition && listPosition > 0) {
80-
player.getItems().remove(listPosition);
81-
setItems(player.getItems(), listPosition);
122+
public boolean moveItem(int fromPosition, int toPosition) {
123+
if (player.isPlaying() && (fromPosition <= player.getCurrentItemIndex() || toPosition <= player.getCurrentItemIndex())) {
124+
return false;
82125
}
126+
if (fromPosition < 0 || fromPosition >= items.size() || toPosition < 0 || toPosition >= items.size()) {
127+
return false;
128+
}
129+
130+
Collections.swap(player.getItems(), fromPosition, toPosition);
131+
Collections.swap(items, fromPosition, toPosition);
132+
notifyItemMoved(fromPosition, toPosition);
133+
134+
// Update selectedForMovePosition if the moved item was the one selected for keyboard move
135+
if (selectedForMovePosition == fromPosition) {
136+
selectedForMovePosition = toPosition;
137+
}
138+
// No notifyItemChanged(fromPosition) or notifyItemChanged(toPosition) here
139+
140+
if (listView != null) {
141+
listView.scrollToPosition(toPosition);
142+
}
143+
return true;
83144
}
84145

85-
public void setItems(List<PlayableItem> items, int removedPosition) {
86-
this.items.clear();
87-
this.items.addAll(items);
88-
notifyItemRemoved(removedPosition);
89-
int itemChangedCount = this.items.size() - removedPosition;
90-
notifyItemRangeChanged(removedPosition, itemChangedCount);
146+
147+
private void removeItem(int listPosition) {
148+
if (player.getItems().size() > listPosition && listPosition >= 0 && !(player.isPlaying() && listPosition <= player.getCurrentItemIndex())) {
149+
player.getItems().remove(listPosition);
150+
// Update local items list to reflect removal before notifying adapter
151+
PlayableItem removedItem = items.remove(listPosition);
152+
notifyItemRemoved(listPosition);
153+
// notifyItemRangeChanged is important if item positions change relative to others
154+
if (listPosition < items.size()) {
155+
notifyItemRangeChanged(listPosition, items.size() - listPosition);
156+
}
157+
}
91158
}
92159

93160
static class ViewHolder extends RecyclerView.ViewHolder {
@@ -99,6 +166,7 @@ static class ViewHolder extends RecyclerView.ViewHolder {
99166
public ViewHolder(@NonNull View itemView) {
100167
super(itemView);
101168
dragIcon = itemView.findViewById(R.id.playlistItemDragIcon);
169+
dragIcon.setFocusable(true); // Make the drag icon focusable
102170
deleteIcon = itemView.findViewById(R.id.playlistItemDeleteIcon);
103171
name = itemView.findViewById(R.id.playlistItemName);
104172
}

yaacc/src/main/java/de/yaacc/upnp/UpnpClient.java

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -613,8 +613,15 @@ public ContentDirectoryBrowseResult browseSync(Device<?, ?, ?> device, String ob
613613
getControlPoint().execute(actionCallback);
614614
while (actionCallback.getStatus() == Status.LOADING && actionCallback.getUpnpFailure() == null) {
615615
//FIXME implement maybe async model?
616+
try {
617+
TimeUnit.MILLISECONDS.sleep(100);
618+
} catch (InterruptedException e) {
619+
Log.e(getClass().getName(), "InterruptedException", e);
620+
}
621+
}
622+
if (actionCallback.getUpnpFailure() != null) {
623+
Log.e(getClass().getName(), "UPnP failure: " + actionCallback.getUpnpFailure());
616624
}
617-
618625
}
619626

620627
if (preferences.getBoolean(getContext().getString(R.string.settings_browse_thumbnails_coverlookup_chkbx), false)) {
@@ -880,13 +887,17 @@ public List<Player> getCurrentPlayers(AvTransport transport) {
880887
* @return all items included in the content
881888
**/
882889
public List<Item> toItemList(DIDLContent didlContent) {
883-
List<Item> items = new ArrayList<>();
890+
884891
if (didlContent == null) {
885-
return items;
892+
return new ArrayList<>();
886893
}
894+
List<Item> items = new ArrayList<>();
887895
items.addAll(didlContent.getItems());
896+
int itemCount = items.size();
888897
for (Container c : didlContent.getContainers()) {
889-
items.addAll(toItemList(c));
898+
List<Item> containerItems = toItemList(c, itemCount);
899+
itemCount += containerItems.size();
900+
items.addAll(containerItems);
890901
}
891902
return items;
892903
}
@@ -898,13 +909,26 @@ public List<Item> toItemList(DIDLContent didlContent) {
898909
* @return the list of cling items
899910
*/
900911
public List<Item> toItemList(DIDLObject didlObject) {
912+
return toItemList(didlObject, 0);
913+
}
914+
915+
private List<Item> toItemList(DIDLObject didlObject, int currentResultSize) {
916+
if (currentResultSize >= Integer.parseInt(getPreferences().getString(getContext().getString(R.string.settings_browse_max_results_key), "1000"))) {
917+
return new ArrayList<>();
918+
}
901919
List<Item> items = new ArrayList<>();
902920
if (didlObject instanceof Container) {
903921
DIDLContent content = loadContainer((Container) didlObject);
904922
if (content != null) {
905923
items.addAll(content.getItems());
924+
int itemCount = currentResultSize + items.size();
925+
if (itemCount >= Integer.parseInt(getPreferences().getString(getContext().getString(R.string.settings_browse_max_results_key), "1000"))) {
926+
return items;
927+
}
906928
for (Container includedContainer : content.getContainers()) {
907-
items.addAll(toItemList(includedContainer));
929+
List<Item> containerItems = toItemList(includedContainer, itemCount);
930+
itemCount += containerItems.size();
931+
items.addAll(containerItems);
908932
}
909933
}
910934
} else if (didlObject instanceof Item) {
@@ -1510,7 +1534,7 @@ public DeviceDetails getDetails() {
15101534
}
15111535

15121536
public void addToPlaylist(DIDLObject item) {
1513-
List<Item> itemList = toItemList(item);
1537+
List<Item> itemList = toItemList(item, 3);
15141538

15151539
if (getCurrentPlayers().stream().noneMatch(p -> getReceiverDevices().stream()
15161540
.map(d -> d.getIdentity().getUdn().getIdentifierString())

yaacc/src/main/java/de/yaacc/upnp/server/TreeNode.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
/*
2+
*
3+
* Copyright (C) 2025 Tobias Schoene www.yaacc.de
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU General Public License
7+
* as published by the Free Software Foundation; either version 3
8+
* of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program; if not, write to the Free Software
17+
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18+
*/
119
package de.yaacc.upnp.server;
220

321

yaacc/src/main/java/de/yaacc/upnp/server/TreeNodeManager.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
/*
2+
*
3+
* Copyright (C) 2025 Tobias Schoene www.yaacc.de
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU General Public License
7+
* as published by the Free Software Foundation; either version 3
8+
* of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program; if not, write to the Free Software
17+
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18+
*/
119
package de.yaacc.upnp.server;
220

321
import java.util.LinkedList;

yaacc/src/main/java/de/yaacc/upnp/server/TreeViewAdapter.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
/*
2+
*
3+
* Copyright (C) 2025 Tobias Schoene www.yaacc.de
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU General Public License
7+
* as published by the Free Software Foundation; either version 3
8+
* of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program; if not, write to the Free Software
17+
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18+
*/
119
package de.yaacc.upnp.server;
220

321
import android.annotation.SuppressLint;

yaacc/src/main/java/de/yaacc/upnp/server/TreeViewHolder.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
/*
2+
*
3+
* Copyright (C) 2025 Tobias Schoene www.yaacc.de
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU General Public License
7+
* as published by the Free Software Foundation; either version 3
8+
* of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program; if not, write to the Free Software
17+
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18+
*/
119
package de.yaacc.upnp.server;
220

321

yaacc/src/main/java/de/yaacc/upnp/server/TreeViewHolderFactory.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
/*
2+
*
3+
* Copyright (C) 2025 Tobias Schoene www.yaacc.de
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU General Public License
7+
* as published by the Free Software Foundation; either version 3
8+
* of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program; if not, write to the Free Software
17+
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18+
*/
119
package de.yaacc.upnp.server;
220

321
import android.view.View;

yaacc/src/main/java/de/yaacc/upnp/server/YaaccUpnpServerControlActivity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2013 Tobias Schoene www.yaacc.de
2+
* Copyright (C) 2025 Tobias Schoene www.yaacc.de
33
*
44
* This program is free software; you can redistribute it and/or
55
* modify it under the terms of the GNU General Public License

0 commit comments

Comments
 (0)