mirror of
https://github.com/izzy2lost/PSX2.git
synced 2026-07-05 15:18:36 -07:00
added sort game options
This commit is contained in:
@@ -112,11 +112,21 @@ public class CoversAdapter extends RecyclerView.Adapter<CoversAdapter.VH> {
|
||||
}
|
||||
}
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (onItemClick != null) onItemClick.onClick(real);
|
||||
if (onItemClick != null) {
|
||||
int pos = holder.getBindingAdapterPosition();
|
||||
if (pos == RecyclerView.NO_POSITION) pos = position; // fallback
|
||||
int count2 = titles.length;
|
||||
int realNow = (count2 == 0) ? 0 : (pos % count2);
|
||||
onItemClick.onClick(realNow);
|
||||
}
|
||||
});
|
||||
holder.itemView.setOnLongClickListener(v -> {
|
||||
if (onItemLongClick != null) {
|
||||
onItemLongClick.onLongClick(real);
|
||||
int pos = holder.getBindingAdapterPosition();
|
||||
if (pos == RecyclerView.NO_POSITION) pos = position; // fallback
|
||||
int count2 = titles.length;
|
||||
int realNow = (count2 == 0) ? 0 : (pos % count2);
|
||||
onItemLongClick.onLongClick(realNow);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -14,6 +14,9 @@ import android.widget.Toast;
|
||||
import android.os.Build;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -24,6 +27,9 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.PagerSnapHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import java.util.Locale;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
public class GamesCoverDialogFragment extends DialogFragment {
|
||||
private boolean didInitialNudge = false;
|
||||
@@ -37,11 +43,34 @@ public class GamesCoverDialogFragment extends DialogFragment {
|
||||
private String[] uris;
|
||||
private String[] coverUrls;
|
||||
private String[] localPaths;
|
||||
private String[] origTitles;
|
||||
private String[] origUris;
|
||||
private String[] origCoverUrls;
|
||||
private String[] origLocalPaths;
|
||||
private RecyclerView rv;
|
||||
private LinearLayoutManager llm;
|
||||
private PagerSnapHelper snapHelper;
|
||||
private int lastRvW = -1, lastRvH = -1;
|
||||
private boolean pendingResnap = false;
|
||||
private int lastItemWidthPx = 0;
|
||||
private RecyclerView rvLetters;
|
||||
private LinearLayoutManager llmLetters;
|
||||
private PagerSnapHelper lettersSnapHelper;
|
||||
private LettersAdapter lettersAdapter;
|
||||
private java.util.ArrayList<Character> letters = new java.util.ArrayList<>();
|
||||
private Character pendingLetterJump = null;
|
||||
private static final int SORT_ALPHA = 0;
|
||||
private static final int SORT_RECENT = 1;
|
||||
private int sortMode = SORT_ALPHA;
|
||||
private String query = null;
|
||||
// Simpler grouping/sort helpers (revert)
|
||||
private static char firstLetter(String t) {
|
||||
if (t == null) return '#';
|
||||
String s = t.trim();
|
||||
if (s.isEmpty()) return '#';
|
||||
char c = Character.toUpperCase(s.charAt(0));
|
||||
return Character.isLetter(c) ? c : '#';
|
||||
}
|
||||
|
||||
public interface OnGameSelectedListener {
|
||||
void onGameSelected(String gameUri);
|
||||
@@ -98,6 +127,55 @@ public class GamesCoverDialogFragment extends DialogFragment {
|
||||
// Snap to center item
|
||||
snapHelper = new PagerSnapHelper();
|
||||
snapHelper.attachToRecyclerView(rv);
|
||||
// Letters row setup
|
||||
rvLetters = root.findViewById(R.id.recycler_letters);
|
||||
if (rvLetters != null) {
|
||||
rvLetters.setHasFixedSize(true);
|
||||
llmLetters = new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false);
|
||||
rvLetters.setLayoutManager(llmLetters);
|
||||
rvLetters.setClipToPadding(false);
|
||||
int basePad = (int)(24*getResources().getDisplayMetrics().density);
|
||||
rvLetters.setPadding(basePad, 0, basePad, 0);
|
||||
lettersSnapHelper = new PagerSnapHelper();
|
||||
lettersSnapHelper.attachToRecyclerView(rvLetters);
|
||||
// Add spacing between letters for off-screen effect
|
||||
final int letterSpace = (int)(12 * getResources().getDisplayMetrics().density);
|
||||
rvLetters.addItemDecoration(new RecyclerView.ItemDecoration() {
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull android.graphics.Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
|
||||
int pos = parent.getChildAdapterPosition(view);
|
||||
if (pos == RecyclerView.NO_POSITION) return;
|
||||
outRect.left = letterSpace;
|
||||
outRect.right = letterSpace;
|
||||
}
|
||||
});
|
||||
rvLetters.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||
super.onScrolled(recyclerView, dx, dy);
|
||||
applyLetterTransforms(recyclerView);
|
||||
}
|
||||
@Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||
super.onScrollStateChanged(recyclerView, newState);
|
||||
if (newState == RecyclerView.SCROLL_STATE_IDLE) applyLetterTransforms(recyclerView);
|
||||
}
|
||||
});
|
||||
// Initial snap + dynamic side padding after first layout to keep center item centered
|
||||
rvLetters.getViewTreeObserver().addOnGlobalLayoutListener(new android.view.ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override public void onGlobalLayout() {
|
||||
int w = rvLetters.getWidth();
|
||||
if (w > 0) {
|
||||
float d = getResources().getDisplayMetrics().density;
|
||||
int itemPx = (int) (72 * d); // item_letter width
|
||||
int letterSpace = (int) (12 * d);
|
||||
int totalItem = itemPx + 2 * letterSpace;
|
||||
int side = Math.max((int)(24 * d), (w - totalItem) / 2);
|
||||
rvLetters.setPadding(side, 0, side, 0);
|
||||
}
|
||||
rvLetters.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
rvLetters.post(() -> { resnapLetters(); applyLetterTransforms(rvLetters); });
|
||||
}
|
||||
});
|
||||
}
|
||||
// Scale/alpha transform based on distance from center
|
||||
rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
@@ -142,6 +220,14 @@ public class GamesCoverDialogFragment extends DialogFragment {
|
||||
localPaths[i] = new java.io.File(getCoversDir(), serial + ".png").getAbsolutePath();
|
||||
}
|
||||
|
||||
// cache originals for sorting/filtering
|
||||
origTitles = Arrays.copyOf(titles, titles.length);
|
||||
origUris = Arrays.copyOf(uris, uris.length);
|
||||
origCoverUrls = Arrays.copyOf(coverUrls, coverUrls.length);
|
||||
origLocalPaths = Arrays.copyOf(localPaths, localPaths.length);
|
||||
// restore sort pref if any
|
||||
sortMode = prefs.getInt("covers_sort_mode", SORT_ALPHA);
|
||||
|
||||
adapter = new CoversAdapter(requireContext(), titles, coverUrls, localPaths, R.layout.item_coverflow,
|
||||
position -> {
|
||||
if (listener != null && position >= 0 && position < uris.length) {
|
||||
@@ -155,6 +241,30 @@ public class GamesCoverDialogFragment extends DialogFragment {
|
||||
}
|
||||
});
|
||||
rv.setAdapter(adapter);
|
||||
// Build letters list if row present (RecyclerView variant)
|
||||
if (rvLetters != null) buildLettersAndBind();
|
||||
|
||||
// Hook sort/search buttons if present
|
||||
View btnSortV = root.findViewById(R.id.btn_sort);
|
||||
if (btnSortV instanceof com.google.android.material.button.MaterialButton) {
|
||||
com.google.android.material.button.MaterialButton btnSort = (com.google.android.material.button.MaterialButton) btnSortV;
|
||||
updateSortButtonUi(btnSort);
|
||||
btnSort.setOnClickListener(v -> {
|
||||
sortMode = (sortMode == SORT_ALPHA) ? SORT_RECENT : SORT_ALPHA;
|
||||
requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
|
||||
.edit().putInt("covers_sort_mode", sortMode).apply();
|
||||
applyFilterAndSort();
|
||||
updateSortButtonUi(btnSort);
|
||||
});
|
||||
}
|
||||
View btnSearch = root.findViewById(R.id.btn_search);
|
||||
if (btnSearch != null) {
|
||||
btnSearch.setOnClickListener(v -> showSearchDialog());
|
||||
}
|
||||
// apply initial sort/filter if needed
|
||||
if (sortMode != SORT_ALPHA || (query != null && !query.isEmpty())) {
|
||||
applyFilterAndSort();
|
||||
}
|
||||
// One-time tiny nudge to force snap/transform on some devices
|
||||
rv.post(() -> {
|
||||
if (!isAdded() || didInitialNudge) return;
|
||||
@@ -192,11 +302,19 @@ public class GamesCoverDialogFragment extends DialogFragment {
|
||||
String t = TitleResolver.resolveTitleForUri(requireContext(), uris[i], titles[i]);
|
||||
if (t != null && !t.isEmpty() && i < titles.length && !t.equals(titles[i])) {
|
||||
titles[i] = t;
|
||||
if (origTitles != null && i < origTitles.length) origTitles[i] = t;
|
||||
changed = true;
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
if (changed && isAdded()) requireActivity().runOnUiThread(() -> adapter.notifyDataSetChanged());
|
||||
if (changed && isAdded()) requireActivity().runOnUiThread(() -> {
|
||||
if (sortMode != SORT_ALPHA || (query != null && !query.isEmpty())) {
|
||||
applyFilterAndSort();
|
||||
} else {
|
||||
adapter.notifyDataSetChanged();
|
||||
if (rvLetters != null) buildLettersAndBind();
|
||||
}
|
||||
});
|
||||
}).start();
|
||||
|
||||
// Dynamically size items based on RecyclerView size and orientation
|
||||
@@ -219,7 +337,18 @@ public class GamesCoverDialogFragment extends DialogFragment {
|
||||
int widthFromHeight = (int) (availableH * ratio);
|
||||
int widthFromWidth = (int) (rvW * (landscape ? 0.35f : 0.55f));
|
||||
int itemWidth = Math.max(160, Math.min(widthFromHeight, widthFromWidth));
|
||||
lastItemWidthPx = itemWidth;
|
||||
adapter.setItemWidthPx(itemWidth);
|
||||
// Center vertically in portrait by adjusting top/bottom padding
|
||||
if (!landscape) {
|
||||
int imageH = (int) (itemWidth / (567f / 878f));
|
||||
int titlePx = (int) ((titleDp + extraDp) * density);
|
||||
int contentH = imageH + titlePx;
|
||||
int desiredPad = Math.max(vertPad, Math.max(0, (rvH - contentH) / 2));
|
||||
rv.setPadding(sidePad, desiredPad, sidePad, desiredPad);
|
||||
} else {
|
||||
rv.setPadding(sidePad, vertPad, sidePad, vertPad);
|
||||
}
|
||||
// If user is scrolling, defer resnap until idle to avoid fighting gesture
|
||||
if (rv.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
resnapToCenter(rv);
|
||||
@@ -238,6 +367,267 @@ public class GamesCoverDialogFragment extends DialogFragment {
|
||||
return root;
|
||||
}
|
||||
|
||||
private void buildLettersAndBind() {
|
||||
letters.clear();
|
||||
boolean hasHash = false;
|
||||
boolean[] present = new boolean[26];
|
||||
if (titles != null) {
|
||||
for (String t : titles) {
|
||||
if (t == null) continue;
|
||||
char c = firstLetter(t);
|
||||
if (c >= 'A' && c <= 'Z') present[c - 'A'] = true;
|
||||
else hasHash = true;
|
||||
}
|
||||
}
|
||||
if (hasHash) letters.add('#');
|
||||
for (int i = 0; i < 26; i++) if (present[i]) letters.add((char)('A'+i));
|
||||
if (rvLetters != null) {
|
||||
lettersAdapter = new LettersAdapter(letters, this::onLetterTapped);
|
||||
rvLetters.setAdapter(lettersAdapter);
|
||||
// Anchor to a large middle position for infinite wrap-around
|
||||
int n = letters.size();
|
||||
if (n > 0) {
|
||||
int center = (1 << 29);
|
||||
int startPos = center - (center % n);
|
||||
llmLetters.scrollToPosition(startPos);
|
||||
}
|
||||
rvLetters.post(() -> { resnapLetters(); applyLetterTransforms(rvLetters); });
|
||||
}
|
||||
}
|
||||
|
||||
private void applyLetterTransforms(@NonNull RecyclerView recyclerView) {
|
||||
int rvCenterX = (recyclerView.getLeft() + recyclerView.getRight()) / 2;
|
||||
final float maxScale = 1.0f;
|
||||
final float minScale = 0.85f;
|
||||
final float maxAlpha = 1.0f;
|
||||
final float minAlpha = 0.6f;
|
||||
for (int i = 0; i < recyclerView.getChildCount(); i++) {
|
||||
View child = recyclerView.getChildAt(i);
|
||||
int childCenterX = (child.getLeft() + child.getRight()) / 2;
|
||||
float dx = Math.abs(childCenterX - rvCenterX);
|
||||
float norm = Math.min(1f, dx / (recyclerView.getWidth() * 0.5f));
|
||||
float scale = maxScale - (maxScale - minScale) * norm;
|
||||
float alpha = maxAlpha - (maxAlpha - minAlpha) * norm;
|
||||
child.setScaleX(scale);
|
||||
child.setScaleY(scale);
|
||||
child.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
|
||||
private void buildLetterIndexLinear(@NonNull LinearLayout container) {
|
||||
container.removeAllViews();
|
||||
// Build present letters with normalization
|
||||
java.util.LinkedHashSet<Character> set = new java.util.LinkedHashSet<>();
|
||||
if (titles != null) {
|
||||
for (String t : titles) {
|
||||
char c = firstLetter(t);
|
||||
if (c == '#') { set.add('#'); }
|
||||
else if (c >= 'A' && c <= 'Z') set.add(c);
|
||||
}
|
||||
}
|
||||
float d = getResources().getDisplayMetrics().density;
|
||||
int padH = (int) (14 * d);
|
||||
int padV = (int) (6 * d);
|
||||
for (Character ch : set) {
|
||||
TextView tv = new TextView(requireContext());
|
||||
tv.setText(String.valueOf(ch));
|
||||
tv.setTextSize(18);
|
||||
tv.setTextColor(getResources().getColor(R.color.brand_primary));
|
||||
tv.setPadding(padH, padV, padH, padV);
|
||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
lp.setMargins((int)(8*d), 0, (int)(8*d), 0); // extra spacing
|
||||
tv.setLayoutParams(lp);
|
||||
tv.setOnClickListener(v -> onLetterTapped(ch));
|
||||
container.addView(tv);
|
||||
}
|
||||
}
|
||||
|
||||
private void onLetterTapped(char letter) {
|
||||
android.util.Log.d("LetterTap", "Letter tapped: " + letter + ", sortMode: " + sortMode + ", titles.length: " + (titles != null ? titles.length : 0));
|
||||
// Force A–Z sorting for predictable letter navigation
|
||||
if (sortMode != SORT_ALPHA) {
|
||||
android.util.Log.d("LetterTap", "Switching to ALPHA sort mode");
|
||||
sortMode = SORT_ALPHA;
|
||||
requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
|
||||
.edit().putInt("covers_sort_mode", sortMode).apply();
|
||||
pendingLetterJump = letter;
|
||||
applyFilterAndSort();
|
||||
} else {
|
||||
android.util.Log.d("LetterTap", "Already in ALPHA mode, jumping to letter");
|
||||
// Jump to first index for that letter in the current (alphabetical) list
|
||||
jumpToLetter(letter);
|
||||
}
|
||||
// Center letters row on selected
|
||||
if (rvLetters != null && letters != null && !letters.isEmpty()) {
|
||||
int idx = letters.indexOf(letter);
|
||||
if (idx >= 0) {
|
||||
centerLettersOnIndex(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void centerLettersOnIndex(int idx) {
|
||||
if (rvLetters == null || llmLetters == null) return;
|
||||
View snapView = lettersSnapHelper != null ? lettersSnapHelper.findSnapView(llmLetters) : null;
|
||||
int centerPos = (snapView != null) ? rvLetters.getChildAdapterPosition(snapView) : llmLetters.findFirstVisibleItemPosition();
|
||||
if (centerPos == RecyclerView.NO_POSITION) centerPos = 0;
|
||||
int n = letters != null ? letters.size() : 0;
|
||||
if (n <= 0) return;
|
||||
int centerIdx = centerPos % n;
|
||||
int forward = (idx - centerIdx + n) % n;
|
||||
int backward = (centerIdx - idx + n) % n;
|
||||
int delta = (forward <= backward) ? forward : -backward;
|
||||
llmLetters.scrollToPosition(centerPos + delta);
|
||||
rvLetters.post(() -> { resnapLetters(); applyLetterTransforms(rvLetters); });
|
||||
}
|
||||
|
||||
private void resnapLetters() {
|
||||
if (rvLetters == null || llmLetters == null || lettersSnapHelper == null) return;
|
||||
View snap = lettersSnapHelper.findSnapView(llmLetters);
|
||||
if (snap == null) return;
|
||||
int[] dist = lettersSnapHelper.calculateDistanceToFinalSnap(llmLetters, snap);
|
||||
if (dist != null && (dist[0] != 0 || dist[1] != 0)) rvLetters.scrollBy(dist[0], dist[1]);
|
||||
}
|
||||
|
||||
private void updateSortButtonUi(@NonNull com.google.android.material.button.MaterialButton btn) {
|
||||
btn.setIconResource(R.drawable.sort_24px);
|
||||
boolean alpha = (sortMode == SORT_ALPHA);
|
||||
btn.setText(alpha ? "A–Z" : "RECENT");
|
||||
btn.setContentDescription(alpha ? "Sort A–Z" : "Sort Recent");
|
||||
}
|
||||
|
||||
private void showSearchDialog() {
|
||||
final EditText input = new EditText(requireContext());
|
||||
input.setHint("Search games");
|
||||
int pad = (int) (16 * getResources().getDisplayMetrics().density);
|
||||
input.setPadding(pad, pad, pad, pad);
|
||||
new com.google.android.material.dialog.MaterialAlertDialogBuilder(requireContext(),
|
||||
com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog)
|
||||
.setTitle("Search")
|
||||
.setView(input)
|
||||
.setNegativeButton("Clear", (d, w) -> {
|
||||
query = null;
|
||||
applyFilterAndSort();
|
||||
})
|
||||
.setPositiveButton("Apply", (d, w) -> {
|
||||
query = input.getText() != null ? input.getText().toString().trim() : null;
|
||||
if (query != null && query.isEmpty()) query = null;
|
||||
applyFilterAndSort();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void jumpToLetter(char letterRaw) {
|
||||
android.util.Log.d("LetterJump", "jumpToLetter called with: " + letterRaw);
|
||||
if (titles == null || titles.length == 0) {
|
||||
android.util.Log.d("LetterJump", "No titles available");
|
||||
return;
|
||||
}
|
||||
char letter = Character.toUpperCase(letterRaw);
|
||||
int target = -1;
|
||||
for (int i = 0; i < titles.length; i++) {
|
||||
char c = firstLetter(titles[i]);
|
||||
if ((letter == '#') ? (c == '#') : (c == letter)) {
|
||||
target = i;
|
||||
android.util.Log.d("LetterJump", "Found target at index " + i + " for letter " + letter + ", title: " + titles[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (target >= 0) {
|
||||
android.util.Log.d("LetterJump", "Scrolling to index: " + target);
|
||||
scrollToIndex(target);
|
||||
} else {
|
||||
android.util.Log.d("LetterJump", "No target found for letter: " + letter);
|
||||
}
|
||||
}
|
||||
|
||||
private void scrollToIndex(int idx) {
|
||||
if (llm == null || rv == null || titles == null || titles.length == 0) return;
|
||||
View snap = snapHelper != null ? snapHelper.findSnapView(llm) : null;
|
||||
int centerPos = (snap != null) ? rv.getChildAdapterPosition(snap) : llm.findFirstVisibleItemPosition();
|
||||
if (centerPos == RecyclerView.NO_POSITION) centerPos = 0;
|
||||
int n = titles.length;
|
||||
int centerIdx = centerPos % n;
|
||||
int forward = (idx - centerIdx + n) % n;
|
||||
int backward = (centerIdx - idx + n) % n;
|
||||
int delta = (forward <= backward) ? forward : -backward;
|
||||
llm.scrollToPosition(centerPos + delta);
|
||||
rv.post(() -> { resnapToCenter(rv); applyCoverflowTransforms(rv); });
|
||||
}
|
||||
|
||||
private void applyFilterAndSort() {
|
||||
int n = origTitles != null ? origTitles.length : 0;
|
||||
ArrayList<Integer> idxs = new ArrayList<>(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (query == null || query.isEmpty()) {
|
||||
idxs.add(i);
|
||||
} else {
|
||||
String t = origTitles[i] != null ? origTitles[i] : "";
|
||||
if (t.toLowerCase(Locale.ROOT).contains(query.toLowerCase(Locale.ROOT))) idxs.add(i);
|
||||
}
|
||||
}
|
||||
SharedPreferences prefs = requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE);
|
||||
if (sortMode == SORT_RECENT) {
|
||||
idxs.sort((a, b) -> {
|
||||
long ta = prefs.getLong("last_played:" + origUris[a], 0L);
|
||||
long tb = prefs.getLong("last_played:" + origUris[b], 0L);
|
||||
if (ta == tb) return origTitles[a].compareToIgnoreCase(origTitles[b]);
|
||||
return Long.compare(tb, ta);
|
||||
});
|
||||
} else {
|
||||
idxs.sort(Comparator.comparing(i -> {
|
||||
String t = origTitles[i];
|
||||
return t == null ? "" : t.toLowerCase(Locale.ROOT);
|
||||
}));
|
||||
}
|
||||
titles = new String[idxs.size()];
|
||||
uris = new String[idxs.size()];
|
||||
coverUrls = new String[idxs.size()];
|
||||
localPaths = new String[idxs.size()];
|
||||
for (int k = 0; k < idxs.size(); k++) {
|
||||
int i = idxs.get(k);
|
||||
titles[k] = origTitles[i];
|
||||
uris[k] = origUris[i];
|
||||
coverUrls[k] = origCoverUrls[i];
|
||||
localPaths[k] = origLocalPaths[i];
|
||||
}
|
||||
adapter = new CoversAdapter(requireContext(), titles, coverUrls, localPaths, R.layout.item_coverflow,
|
||||
position -> {
|
||||
if (listener != null && position >= 0 && position < uris.length) {
|
||||
listener.onGameSelected(uris[position]);
|
||||
dismissAllowingStateLoss();
|
||||
}
|
||||
},
|
||||
position -> {
|
||||
if (position >= 0 && position < uris.length) {
|
||||
showGameSettings(titles[position], uris[position]);
|
||||
}
|
||||
});
|
||||
rv.setAdapter(adapter);
|
||||
if (lastItemWidthPx > 0) adapter.setItemWidthPx(lastItemWidthPx);
|
||||
int n2 = titles.length;
|
||||
if (n2 > 0) {
|
||||
int center = (1 << 29);
|
||||
int startPos = center - (center % n2);
|
||||
llm.scrollToPosition(startPos);
|
||||
}
|
||||
rv.post(() -> { resnapToCenter(rv); applyCoverflowTransforms(rv); });
|
||||
if (rvLetters != null) {
|
||||
buildLettersAndBind();
|
||||
if (pendingLetterJump != null) {
|
||||
final char l = pendingLetterJump;
|
||||
pendingLetterJump = null;
|
||||
rv.postDelayed(() -> {
|
||||
jumpToLetter(l);
|
||||
if (letters != null) {
|
||||
int idx = letters.indexOf(l);
|
||||
if (idx >= 0) centerLettersOnIndex(idx);
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyCoverflowTransforms(@NonNull RecyclerView recyclerView) {
|
||||
int rvCenterX = (recyclerView.getLeft() + recyclerView.getRight()) / 2;
|
||||
boolean landscape = getResources().getConfiguration().orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE;
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.izzy2lost.psx2;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LettersAdapter extends RecyclerView.Adapter<LettersAdapter.VH> {
|
||||
public interface OnClick {
|
||||
void onClick(char letter);
|
||||
}
|
||||
|
||||
private final List<Character> letters;
|
||||
private final OnClick onClick;
|
||||
|
||||
public LettersAdapter(List<Character> letters, OnClick onClick) {
|
||||
this.letters = letters;
|
||||
this.onClick = onClick;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
// Ensure uniqueness across the infinite range by including position
|
||||
return position;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_letter, parent, false);
|
||||
return new VH(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull VH holder, int position) {
|
||||
int n = letters != null ? letters.size() : 0;
|
||||
if (n == 0) return;
|
||||
int real = position % n;
|
||||
char ch = letters.get(real);
|
||||
holder.text.setText(String.valueOf(ch));
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
android.util.Log.d("LettersAdapter", "Letter clicked: " + ch + " at position " + position);
|
||||
if (onClick != null) {
|
||||
onClick.onClick(ch);
|
||||
} else {
|
||||
android.util.Log.d("LettersAdapter", "onClick is null!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return (letters != null && !letters.isEmpty()) ? Integer.MAX_VALUE : 0;
|
||||
}
|
||||
|
||||
static class VH extends RecyclerView.ViewHolder {
|
||||
final TextView text;
|
||||
VH(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
text = itemView.findViewById(R.id.text_letter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,6 +246,11 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
}
|
||||
}
|
||||
|
||||
// Enable immersive mode to hide navigation and status bars
|
||||
private void enableImmersiveMode() {
|
||||
hideStatusBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
@@ -297,6 +302,11 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
if (!TextUtils.isEmpty(gameUri)) {
|
||||
// Avoid any pre-VM native calls here; just set the game and launch.
|
||||
m_szGamefile = gameUri;
|
||||
// Record last played timestamp for sorting
|
||||
try {
|
||||
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
|
||||
prefs.edit().putLong("last_played:" + gameUri, System.currentTimeMillis()).apply();
|
||||
} catch (Throwable ignored) {}
|
||||
restartEmuThread();
|
||||
}
|
||||
}
|
||||
@@ -372,7 +382,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
setContentView(R.layout.activity_main);
|
||||
hideStatusBar();
|
||||
enableImmersiveMode();
|
||||
|
||||
// Setup back button handler
|
||||
setupBackPressedHandler();
|
||||
@@ -397,14 +407,10 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
// Ensure consistent ripple across all MaterialButtons
|
||||
tintAllMaterialButtonOutlines();
|
||||
|
||||
// Settings are already applied in Initialize() -> loadAndApplySettings()
|
||||
// No need to call applySavedSettings() again here
|
||||
|
||||
// Apply orientation-specific constraints once at startup
|
||||
int currentOrientation = getResources().getConfiguration().orientation;
|
||||
applyConstraintsForOrientation(currentOrientation);
|
||||
|
||||
|
||||
|
||||
// Prompt for BIOS if missing
|
||||
maybePromptForBios();
|
||||
|
||||
@@ -437,10 +443,6 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
getOnBackPressedDispatcher().addCallback(this, callback);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private void makeButtonTouch() {
|
||||
MaterialButton btn_file = findViewById(R.id.btn_file);
|
||||
if(btn_file != null) {
|
||||
@@ -493,8 +495,6 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
});
|
||||
}
|
||||
|
||||
// Games button handled above
|
||||
|
||||
// Settings button opens dialog
|
||||
MaterialButton btn_settings = findViewById(R.id.btn_settings);
|
||||
if (btn_settings != null) {
|
||||
@@ -513,10 +513,6 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
});
|
||||
}
|
||||
|
||||
// HUD toggle moved to Settings (Developer section)
|
||||
|
||||
// Hide UI button (removed - functionality moved to toggle button)
|
||||
|
||||
// Small unhide button (appears when all UI is hidden)
|
||||
MaterialButton btn_unhide_ui = findViewById(R.id.btn_unhide_ui);
|
||||
if(btn_unhide_ui != null) {
|
||||
@@ -525,9 +521,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
});
|
||||
}
|
||||
|
||||
//////
|
||||
// RENDERER
|
||||
|
||||
MaterialButton btn_ogl = findViewById(R.id.btn_ogl);
|
||||
if(btn_ogl != null) {
|
||||
btn_ogl.setOnClickListener(v -> {
|
||||
@@ -588,9 +582,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
});
|
||||
}
|
||||
|
||||
//////
|
||||
// PAD
|
||||
|
||||
MaterialButton btn_pad_select = findViewById(R.id.btn_pad_select);
|
||||
if(btn_pad_select != null) {
|
||||
btn_pad_select.setOnTouchListener((v, event) -> {
|
||||
@@ -605,7 +597,6 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
MaterialButton btn_pad_a = findViewById(R.id.btn_pad_a);
|
||||
if(btn_pad_a != null) {
|
||||
btn_pad_a.setOnTouchListener((v, event) -> {
|
||||
@@ -635,8 +626,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
});
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
//// Shoulder buttons
|
||||
MaterialButton btn_pad_l1 = findViewById(R.id.btn_pad_l1);
|
||||
if(btn_pad_l1 != null) {
|
||||
btn_pad_l1.setOnTouchListener((v, event) -> {
|
||||
@@ -682,8 +672,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
});
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
//// D-Pad
|
||||
final int PAD_L_UP = 110;
|
||||
final int PAD_L_RIGHT = 111;
|
||||
final int PAD_L_DOWN = 112;
|
||||
@@ -796,8 +785,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
});
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
//// D-Pad buttons
|
||||
MaterialButton btn_pad_dir_top = findViewById(R.id.btn_pad_dir_top);
|
||||
if(btn_pad_dir_top != null) {
|
||||
btn_pad_dir_top.setOnTouchListener((v, event) -> {
|
||||
@@ -1204,6 +1192,10 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
if(_intent != null) {
|
||||
m_szGamefile = _intent.getDataString();
|
||||
if(!TextUtils.isEmpty(m_szGamefile)) {
|
||||
try {
|
||||
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
|
||||
prefs.edit().putLong("last_played:" + m_szGamefile, System.currentTimeMillis()).apply();
|
||||
} catch (Throwable ignored) {}
|
||||
restartEmuThread();
|
||||
}
|
||||
}
|
||||
@@ -1393,7 +1385,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
mHIDDeviceManager.setFrozen(false);
|
||||
}
|
||||
// Re-assert full screen when returning to the activity
|
||||
hideStatusBar();
|
||||
enableImmersiveMode();
|
||||
updateUiForControllerPresence();
|
||||
}
|
||||
|
||||
@@ -1423,8 +1415,6 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
android.os.Process.killProcess(appPid);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void Initialize() {
|
||||
NativeApp.initializeOnce(getApplicationContext());
|
||||
|
||||
@@ -1441,8 +1431,6 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
// Initialize HID device manager for USB and Bluetooth controllers
|
||||
mHIDDeviceManager.initialize(true, true);
|
||||
|
||||
// Avoid forcing renderer init when no game/surface is active.
|
||||
// Renderer is applied on game start (restartEmuThread) and from settings when appropriate.
|
||||
}
|
||||
|
||||
private void setSurfaceView(Object p_value) {
|
||||
@@ -1499,8 +1487,6 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
// Use only our controller handler - disable SDL fallback to avoid conflicts
|
||||
@@ -1581,7 +1567,6 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Asset copy helpers used on first launch to seed default resources
|
||||
private void copyAssetAll(Context context, String srcPath) {
|
||||
AssetManager assetMgr = context.getAssets();
|
||||
@@ -1630,8 +1615,6 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Fallback for older Android versions
|
||||
@@ -1658,8 +1641,6 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
.show();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ControllerInputHandler.ControllerInputListener implementation
|
||||
@Override
|
||||
public void onControllerButtonPressed(int controllerId, int button, boolean pressed) {
|
||||
@@ -1746,9 +1727,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
}
|
||||
|
||||
private void handleAnalogInput(int axis, float value) {
|
||||
// Convert analog input to button presses for the native interface
|
||||
// This matches how AetherSX2 handles analog input
|
||||
|
||||
// Convert analog input to button presses for the native interface
|
||||
// For analog sticks, only send positive values (negative values are handled by opposite direction)
|
||||
int intensity = Math.max(0, Math.round(Math.abs(value) * 255));
|
||||
boolean pressed = Math.abs(value) > 0.1f;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M240,760L360,760L360,520L600,520L600,760L720,760L720,400L480,220L240,400L240,760ZM160,840L160,360L480,120L800,360L800,840L520,840L520,600L440,600L440,840L160,840ZM480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M440,680L520,680L520,440L440,440L440,680ZM480,360Q497,360 508.5,348.5Q520,337 520,320Q520,303 508.5,291.5Q497,280 480,280Q463,280 451.5,291.5Q440,303 440,320Q440,337 451.5,348.5Q463,360 480,360ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M400,720Q367,720 343.5,696.5Q320,673 320,640L320,590Q263,551 231.5,490Q200,429 200,360Q200,243 281.5,161.5Q363,80 480,80Q597,80 678.5,161.5Q760,243 760,360Q760,429 728.5,489.5Q697,550 640,590L640,640Q640,673 616.5,696.5Q593,720 560,720L400,720ZM400,640L560,640Q560,640 560,640Q560,640 560,640L560,548L594,524Q635,496 657.5,452.5Q680,409 680,360Q680,277 621.5,218.5Q563,160 480,160Q397,160 338.5,218.5Q280,277 280,360Q280,409 302.5,452.5Q325,496 366,524L400,548L400,640Q400,640 400,640Q400,640 400,640ZM400,880Q383,880 371.5,868.5Q360,857 360,840L360,800L600,800L600,840Q600,857 588.5,868.5Q577,880 560,880L400,880ZM480,360Q480,360 480,360Q480,360 480,360L480,360L480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360L480,360L480,360Q480,360 480,360Q480,360 480,360Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M784,840L532,588Q502,612 463,626Q424,640 380,640Q271,640 195.5,564.5Q120,489 120,380Q120,271 195.5,195.5Q271,120 380,120Q489,120 564.5,195.5Q640,271 640,380Q640,424 626,463Q612,502 588,532L840,784L784,840ZM380,560Q455,560 507.5,507.5Q560,455 560,380Q560,305 507.5,252.5Q455,200 380,200Q305,200 252.5,252.5Q200,305 200,380Q200,455 252.5,507.5Q305,560 380,560Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:autoMirrored="true">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M120,720L120,640L360,640L360,720L120,720ZM120,520L120,440L600,440L600,520L120,520ZM120,320L120,240L840,240L840,320L120,320Z"/>
|
||||
</vector>
|
||||
@@ -24,7 +24,7 @@
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="Home"
|
||||
android:padding="4dp"
|
||||
android:src="@drawable/ic_home"
|
||||
android:src="@drawable/home_24px"
|
||||
app:tint="@color/brand_primary" />
|
||||
|
||||
<View
|
||||
@@ -33,32 +33,90 @@
|
||||
android:layout_weight="1" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_download"
|
||||
android:id="@+id/btn_search"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Download Covers" />
|
||||
</LinearLayout>
|
||||
android:text=""
|
||||
android:contentDescription="Search"
|
||||
app:icon="@drawable/search_24px"
|
||||
app:iconTint="@color/brand_primary"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="8dp"
|
||||
app:iconSize="24dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_covers_hint"
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_sort"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:contentDescription="Sort"
|
||||
app:icon="@drawable/sort_24px"
|
||||
app:iconTint="@color/brand_primary"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="8dp"
|
||||
app:iconSize="24dp" />
|
||||
</LinearLayout>
|
||||
<!-- Letters row above covers -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_letters"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingTop="4dp"
|
||||
android:text="@string/covers_hint_long_press"
|
||||
android:textColor="@color/brand_primary"
|
||||
android:textStyle="bold"
|
||||
android:drawableStart="@drawable/ic_info"
|
||||
android:drawablePadding="8dp"/>
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:clipToPadding="false"
|
||||
android:overScrollMode="never"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_covers"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="12dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingBottom="0dp"
|
||||
android:clipToPadding="false" />
|
||||
|
||||
<!-- Hint below covers grid: yellow bulb right next to text -->
|
||||
<LinearLayout
|
||||
android:id="@+id/hint_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
<ImageView
|
||||
android:id="@+id/img_hint_bulb"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:tint="@color/covers_hint_yellow"
|
||||
android:src="@drawable/lightbulb_2_24px"
|
||||
android:layout_marginEnd="4dp"/>
|
||||
<TextView
|
||||
android:id="@+id/tv_covers_hint"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/covers_hint_long_press"
|
||||
android:textColor="@color/brand_primary"
|
||||
android:textStyle="bold"
|
||||
android:textSize="12sp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Download button below covers, centered with COVERS text -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_download"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="COVERS"
|
||||
android:contentDescription="Download Covers"
|
||||
app:icon="@drawable/download_24px"
|
||||
app:iconTint="@color/brand_primary"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="20dp"
|
||||
app:iconSize="34dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="Home"
|
||||
android:padding="4dp"
|
||||
android:src="@drawable/ic_home"
|
||||
android:src="@drawable/home_24px"
|
||||
app:tint="@color/brand_primary" />
|
||||
|
||||
<View
|
||||
@@ -33,32 +33,90 @@
|
||||
android:layout_weight="1" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_download"
|
||||
android:id="@+id/btn_search"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Download Covers" />
|
||||
</LinearLayout>
|
||||
android:text=""
|
||||
android:contentDescription="Search"
|
||||
app:icon="@drawable/search_24px"
|
||||
app:iconTint="@color/brand_primary"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="8dp"
|
||||
app:iconSize="24dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_covers_hint"
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_sort"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:contentDescription="Sort"
|
||||
app:icon="@drawable/sort_24px"
|
||||
app:iconTint="@color/brand_primary"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="8dp"
|
||||
app:iconSize="24dp" />
|
||||
</LinearLayout>
|
||||
<!-- Letters row above covers -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_letters"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingTop="4dp"
|
||||
android:text="@string/covers_hint_long_press"
|
||||
android:textColor="@color/brand_primary"
|
||||
android:textStyle="bold"
|
||||
android:drawableStart="@drawable/ic_info"
|
||||
android:drawablePadding="8dp"/>
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:clipToPadding="false"
|
||||
android:overScrollMode="never"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_covers"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="12dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingBottom="0dp"
|
||||
android:clipToPadding="false" />
|
||||
|
||||
<!-- Hint below covers grid: yellow bulb right next to text -->
|
||||
<LinearLayout
|
||||
android:id="@+id/hint_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
<ImageView
|
||||
android:id="@+id/img_hint_bulb"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:tint="@color/covers_hint_yellow"
|
||||
android:src="@drawable/lightbulb_2_24px"
|
||||
android:layout_marginEnd="4dp"/>
|
||||
<TextView
|
||||
android:id="@+id/tv_covers_hint"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/covers_hint_long_press"
|
||||
android:textColor="@color/brand_primary"
|
||||
android:textStyle="bold"
|
||||
android:textSize="12sp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Download button below covers, centered with COVERS text -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_download"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="COVERS"
|
||||
android:contentDescription="Download Covers"
|
||||
app:icon="@drawable/download_24px"
|
||||
app:iconTint="@color/brand_primary"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="20dp"
|
||||
app:iconSize="34dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="Home"
|
||||
android:padding="4dp"
|
||||
android:src="@drawable/ic_home"
|
||||
android:src="@drawable/home_24px"
|
||||
app:tint="#883DA4" />
|
||||
|
||||
<View
|
||||
@@ -33,37 +33,92 @@
|
||||
android:layout_weight="1" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_download"
|
||||
android:id="@+id/btn_search"
|
||||
style="@style/PSX2.ElevatedTransparentButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Download Covers"
|
||||
android:text=""
|
||||
android:contentDescription="Search"
|
||||
android:textColor="#883DA4"
|
||||
app:icon="@drawable/download_24px"
|
||||
app:icon="@drawable/search_24px"
|
||||
app:iconTint="#883DA4"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="8dp"
|
||||
app:iconSize="24dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_sort"
|
||||
style="@style/PSX2.ElevatedTransparentButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:contentDescription="Sort"
|
||||
android:textColor="#883DA4"
|
||||
app:icon="@drawable/sort_24px"
|
||||
app:iconTint="#883DA4"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="8dp"
|
||||
app:iconSize="24dp" />
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/tv_covers_hint"
|
||||
<!-- Letters row above covers -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_letters"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingTop="4dp"
|
||||
android:text="@string/covers_hint_long_press"
|
||||
android:textColor="@color/brand_primary"
|
||||
android:textStyle="bold"
|
||||
android:drawableStart="@drawable/ic_info"
|
||||
android:drawablePadding="8dp"/>
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:clipToPadding="false"
|
||||
android:overScrollMode="never"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_covers"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="12dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingBottom="0dp"
|
||||
android:clipToPadding="false" />
|
||||
|
||||
<!-- Hint below covers grid: yellow bulb right next to text -->
|
||||
<LinearLayout
|
||||
android:id="@+id/hint_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
<ImageView
|
||||
android:id="@+id/img_hint_bulb"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:tint="@color/covers_hint_yellow"
|
||||
android:src="@drawable/lightbulb_2_24px"
|
||||
android:layout_marginEnd="4dp"/>
|
||||
<TextView
|
||||
android:id="@+id/tv_covers_hint"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/covers_hint_long_press"
|
||||
android:textColor="@color/brand_primary"
|
||||
android:textStyle="bold"
|
||||
android:textSize="12sp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Download button below covers, centered with COVERS text -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_download"
|
||||
style="@style/PSX2.ElevatedTransparentButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="COVERS"
|
||||
android:contentDescription="Download Covers"
|
||||
app:icon="@drawable/download_24px"
|
||||
app:iconTint="#883DA4"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="20dp"
|
||||
app:iconSize="34dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="52dp"
|
||||
android:padding="4dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_letter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:textSize="20sp"
|
||||
android:textColor="@color/brand_primary"
|
||||
android:text="A"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -140,4 +140,5 @@
|
||||
<color name="md_theme_surfaceContainer_highContrast">#DFE2EA</color>
|
||||
<color name="md_theme_surfaceContainerHigh_highContrast">#D1D4DC</color>
|
||||
<color name="md_theme_surfaceContainerHighest_highContrast">#C3C6CE</color>
|
||||
<color name="covers_hint_yellow">#FFEB3B</color>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Ensure these IDs exist even if not present in some layout variants -->
|
||||
<item type="id" name="recycler_letters" />
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user