mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-29 13:44:20 +01:00
UI update: Snackbars + NRO Metadata
This UI updates adds Snackbar notifications for actions and extracts metadata in the form of an icon, name and author from NRO files to display in the game list. Co-Authored-By: Ryan Teal <zephyren25@users.noreply.github.com>
This commit is contained in:
parent
4921bb41ea
commit
ae6dddf9f6
@ -41,5 +41,5 @@ dependencies {
|
|||||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.preference:preference:1.1.0-beta01'
|
implementation 'androidx.preference:preference:1.1.0-beta01'
|
||||||
implementation 'com.squareup.picasso:picasso:2.4.0'
|
implementation 'com.google.android.material:material:1.0.0'
|
||||||
}
|
}
|
||||||
|
104
app/src/main/java/gq/cyuubi/lightswitch/FileAdapter.java
Normal file
104
app/src/main/java/gq/cyuubi/lightswitch/FileAdapter.java
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package gq.cyuubi.lightswitch;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
class DataModel {
|
||||||
|
File file;
|
||||||
|
TitleEntry meta;
|
||||||
|
|
||||||
|
int index;
|
||||||
|
|
||||||
|
public DataModel(File file) {
|
||||||
|
this.file = file;
|
||||||
|
index = file.getName().lastIndexOf(".");
|
||||||
|
meta = NroMeta.GetNroTitle(getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap getIcon() {
|
||||||
|
return meta.getIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return meta.getName() + " (" + getType() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFileName() {
|
||||||
|
return file.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return meta.getAuthor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return file.getName().substring(index + 1).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
return file.getAbsolutePath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileAdapter extends ArrayAdapter<DataModel> implements View.OnClickListener {
|
||||||
|
|
||||||
|
Context mContext;
|
||||||
|
private ArrayList<DataModel> dataSet;
|
||||||
|
|
||||||
|
public FileAdapter(Context context, @NonNull ArrayList<DataModel> data) {
|
||||||
|
super(context, R.layout.file_item, data);
|
||||||
|
this.dataSet = new ArrayList<>();
|
||||||
|
this.mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
|
||||||
|
int position = (Integer) v.getTag();
|
||||||
|
Object object = getItem(position);
|
||||||
|
DataModel dataModel = (DataModel) object;
|
||||||
|
switch (v.getId()) {
|
||||||
|
case R.id.icon:
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
DataModel dataModel = getItem(position);
|
||||||
|
ViewHolder viewHolder;
|
||||||
|
if (convertView == null) {
|
||||||
|
viewHolder = new ViewHolder();
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||||
|
convertView = inflater.inflate(R.layout.file_item, parent, false);
|
||||||
|
viewHolder.icon = convertView.findViewById(R.id.icon);
|
||||||
|
viewHolder.txtTitle = convertView.findViewById(R.id.text_title);
|
||||||
|
viewHolder.txtSub = convertView.findViewById(R.id.text_subtitle);
|
||||||
|
convertView.setTag(viewHolder);
|
||||||
|
} else {
|
||||||
|
viewHolder = (ViewHolder) convertView.getTag();
|
||||||
|
}
|
||||||
|
viewHolder.txtTitle.setText(dataModel.getTitle());
|
||||||
|
viewHolder.txtSub.setText(dataModel.getAuthor());
|
||||||
|
viewHolder.icon.setImageBitmap(dataModel.getIcon());
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ViewHolder {
|
||||||
|
ImageView icon;
|
||||||
|
TextView txtTitle;
|
||||||
|
TextView txtSub;
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,17 @@
|
|||||||
package gq.cyuubi.lightswitch;
|
package gq.cyuubi.lightswitch;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.TextView;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
@ -24,82 +19,12 @@ import androidx.core.app.ActivityCompat;
|
|||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
class DataModel {
|
|
||||||
File file;
|
|
||||||
int index;
|
|
||||||
|
|
||||||
public DataModel(File file) {
|
|
||||||
this.file = file;
|
|
||||||
index = file.getName().lastIndexOf(".");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTitle() {
|
|
||||||
return getName() + "(" + getType() + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
String name = "";
|
|
||||||
for (String str_i : file.getName().substring(0, index).split("_")) {
|
|
||||||
name += str_i.substring(0, 1).toUpperCase() + str_i.substring(1) + " ";
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getType() {
|
|
||||||
return file.getName().substring(index + 1).toUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPath() {
|
|
||||||
return file.getAbsolutePath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FileAdapter extends ArrayAdapter<DataModel> {
|
|
||||||
|
|
||||||
Context mContext;
|
|
||||||
private ArrayList<DataModel> dataSet;
|
|
||||||
|
|
||||||
public FileAdapter(Context context, @NonNull ArrayList<DataModel> data) {
|
|
||||||
super(context, android.R.layout.simple_list_item_2, data);
|
|
||||||
this.dataSet = new ArrayList<>();
|
|
||||||
this.mContext = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(DataModel object) {
|
|
||||||
super.add(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
|
||||||
DataModel dataModel = getItem(position);
|
|
||||||
ViewHolder viewHolder;
|
|
||||||
if (convertView == null) {
|
|
||||||
viewHolder = new ViewHolder();
|
|
||||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
|
||||||
convertView = inflater.inflate(android.R.layout.simple_list_item_2, parent, false);
|
|
||||||
viewHolder.txtTitle = convertView.findViewById(android.R.id.text1);
|
|
||||||
viewHolder.txtPath = convertView.findViewById(android.R.id.text2);
|
|
||||||
convertView.setTag(viewHolder);
|
|
||||||
} else {
|
|
||||||
viewHolder = (ViewHolder) convertView.getTag();
|
|
||||||
}
|
|
||||||
viewHolder.txtTitle.setText(dataModel.getTitle());
|
|
||||||
viewHolder.txtPath.setText(dataModel.getPath());
|
|
||||||
|
|
||||||
return convertView;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ViewHolder {
|
|
||||||
TextView txtTitle;
|
|
||||||
TextView txtPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@ -109,6 +34,11 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
SharedPreferences sharedPreferences;
|
SharedPreferences sharedPreferences;
|
||||||
FileAdapter adapter;
|
FileAdapter adapter;
|
||||||
|
|
||||||
|
private void notifyUser(String text) {
|
||||||
|
Snackbar.make(findViewById(android.R.id.content), text, Snackbar.LENGTH_SHORT).show();
|
||||||
|
// Toast.makeText(getApplicationContext(), text, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
private List<File> findFile(String ext, File file, @Nullable List<File> files) {
|
private List<File> findFile(String ext, File file, @Nullable List<File> files) {
|
||||||
if (files == null) {
|
if (files == null) {
|
||||||
files = new ArrayList<>();
|
files = new ArrayList<>();
|
||||||
@ -159,7 +89,9 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
game_list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
game_list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
loadFile(((DataModel) parent.getItemAtPosition(position)).getPath());
|
String path = ((DataModel) parent.getItemAtPosition(position)).getPath();
|
||||||
|
notifyUser(getString(R.string.launch_string) + " " + path);
|
||||||
|
loadFile(path);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
refresh_files();
|
refresh_files();
|
||||||
@ -178,6 +110,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
startActivity(new Intent(this, SettingsActivity.class));
|
startActivity(new Intent(this, SettingsActivity.class));
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_refresh:
|
case R.id.action_refresh:
|
||||||
|
notifyUser(getString(R.string.refresh_string));
|
||||||
refresh_files();
|
refresh_files();
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
|
@ -1,30 +1,34 @@
|
|||||||
package gq.cyuubi.lightswitch;
|
package gq.cyuubi.lightswitch;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import com.squareup.picasso.Picasso;
|
|
||||||
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.RandomAccessFile;
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
final class TitleEntry {
|
final class TitleEntry {
|
||||||
private final String name;
|
private final String name;
|
||||||
private final String author;
|
private final String author;
|
||||||
|
private final Bitmap icon;
|
||||||
|
|
||||||
public TitleEntry(String name, String author) {
|
public TitleEntry(String name, String author, Bitmap icon) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.author = author;
|
this.author = author;
|
||||||
|
this.icon = icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String Name() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String Author() {
|
public String getAuthor() {
|
||||||
return author;
|
return author;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Bitmap getIcon() {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class NroMeta {
|
public class NroMeta {
|
||||||
@ -36,56 +40,34 @@ public class NroMeta {
|
|||||||
f.seek(asetOffset); // Skip to the offset specified by NroHeader.size
|
f.seek(asetOffset); // Skip to the offset specified by NroHeader.size
|
||||||
byte[] buffer = new byte[4];
|
byte[] buffer = new byte[4];
|
||||||
f.read(buffer);
|
f.read(buffer);
|
||||||
if(!(new String(buffer).equals("ASET")))
|
if (!(new String(buffer).equals("ASET")))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
f.skipBytes(0x14);
|
f.skipBytes(0x4);
|
||||||
|
long iconOffset = Long.reverseBytes(f.readLong());
|
||||||
|
int iconSize = Integer.reverseBytes(f.readInt());
|
||||||
|
if (iconOffset == 0 || iconSize == 0)
|
||||||
|
throw new IOException();
|
||||||
|
f.seek(asetOffset + iconOffset);
|
||||||
|
byte[] iconData = new byte[iconSize];
|
||||||
|
f.read(iconData);
|
||||||
|
Bitmap icon = BitmapFactory.decodeByteArray(iconData, 0, iconSize);
|
||||||
|
|
||||||
|
f.seek(asetOffset + 0x18);
|
||||||
long nacpOffset = Long.reverseBytes(f.readLong());
|
long nacpOffset = Long.reverseBytes(f.readLong());
|
||||||
long nacpSize = Long.reverseBytes(f.readLong());
|
long nacpSize = Long.reverseBytes(f.readLong());
|
||||||
if(nacpOffset == 0 || nacpSize == 0)
|
if (nacpOffset == 0 || nacpSize == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
f.seek(asetOffset + nacpOffset);
|
f.seek(asetOffset + nacpOffset);
|
||||||
byte[] name = new byte[0x200];
|
byte[] name = new byte[0x200];
|
||||||
f.read(name);
|
f.read(name);
|
||||||
byte[] author = new byte[0x100];
|
byte[] author = new byte[0x100];
|
||||||
f.read(author);
|
f.read(author);
|
||||||
|
|
||||||
return new TitleEntry(new String(name).trim(), new String(author).trim());
|
return new TitleEntry(new String(name).trim(), new String(author).trim(), icon);
|
||||||
}
|
} catch (IOException e) {
|
||||||
catch(IOException e) {
|
|
||||||
Log.e("app_process64", "Error while loading ASET: " + e.getMessage());
|
Log.e("app_process64", "Error while loading ASET: " + e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void LoadImage(String file, ImageView target, MainActivity context) {
|
|
||||||
try {
|
|
||||||
RandomAccessFile f = new RandomAccessFile(file, "r");
|
|
||||||
f.seek(0x18); // Skip to NroHeader.size
|
|
||||||
int asetOffset = Integer.reverseBytes(f.readInt());
|
|
||||||
f.seek(asetOffset); // Skip to the offset specified by NroHeader.size
|
|
||||||
byte[] buffer = new byte[4];
|
|
||||||
f.read(buffer);
|
|
||||||
if(!(new String(buffer).equals("ASET")))
|
|
||||||
return;
|
|
||||||
|
|
||||||
f.skipBytes(0x4);
|
|
||||||
long iconOffset = Long.reverseBytes(f.readLong());
|
|
||||||
long iconSize = Long.reverseBytes(f.readLong());
|
|
||||||
if(iconOffset == 0 || iconSize == 0)
|
|
||||||
return;
|
|
||||||
f.seek(asetOffset + iconOffset);
|
|
||||||
|
|
||||||
byte[] iconData = new byte[(int)iconSize];
|
|
||||||
f.read(iconData);
|
|
||||||
|
|
||||||
new FileOutputStream(context.getFilesDir() + "/tmp.jpg").write(iconData);
|
|
||||||
Picasso.with(context).load(context.getFilesDir() + "/tmp.jpg").into(target);
|
|
||||||
}
|
|
||||||
catch(IOException e) {
|
|
||||||
Log.e("app_process64", "Error while loading ASET: " + e.getMessage());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
5
app/src/main/res/drawable/ic_missing_icon.xml
Normal file
5
app/src/main/res/drawable/ic_missing_icon.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:alpha="0.6" android:height="24dp"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13L7,13v-2h10v2z"/>
|
||||||
|
</vector>
|
34
app/src/main/res/layout/file_item.xml
Normal file
34
app/src/main/res/layout/file_item.xml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="15dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentTop="false"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:contentDescription="@string/icon"
|
||||||
|
android:src="@drawable/ic_missing_icon" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="60dp"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceListItem" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_subtitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/text_title"
|
||||||
|
android:layout_alignStart="@id/text_title"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
|
||||||
|
android:textColor="@android:color/tertiary_text_light" />
|
||||||
|
</RelativeLayout>
|
@ -4,7 +4,9 @@
|
|||||||
<string name="settings">Settings</string>
|
<string name="settings">Settings</string>
|
||||||
<string name="refresh">Refresh</string>
|
<string name="refresh">Refresh</string>
|
||||||
<!-- Main -->
|
<!-- Main -->
|
||||||
<string name="request_string">The following permission</string>
|
<string name="refresh_string">The list of ROMs has been refreshed.</string>
|
||||||
|
<string name="launch_string">Launching</string>
|
||||||
|
<string name="icon">Icon</string>
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<string name="search">Search</string>
|
<string name="search">Search</string>
|
||||||
<string name="search_location">Search Location</string>
|
<string name="search_location">Search Location</string>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||||
<!-- Switch Red Theme -->
|
<!-- Switch Red Theme -->
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
|
Loading…
Reference in New Issue
Block a user