Add icon to login account list (#595)

* Try to add icon to account

* [pick account screen] add icon stage 2
TODO: extract skin head from skin

* Autoscale head icon and try to fix layout

* Remove import

* Complete skin face extractor

* (11 other fixes commits)

Co-authored-by: Duy Tran Khanh <khanhduytran0@users.noreply.github.com>
This commit is contained in:
Duy Tran Khanh 2021-01-12 13:23:13 +07:00 committed by GitHub
parent 21d5dfcef4
commit 416345b37d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 205 additions and 51 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

View File

@ -1,42 +1,75 @@
package net.kdt.pojavlaunch;
import android.*;
import android.Manifest;
import android.app.Dialog;
import android.content.*;
import android.content.pm.*;
import android.content.res.*;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.drawable.ColorDrawable;
import android.net.*;
import android.os.*;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.system.Os;
import android.text.SpannableString;
import android.text.style.StyleSpan;
import android.util.Base64;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.app.*;
import androidx.core.content.*;
import androidx.appcompat.app.*;
import android.system.*;
import android.text.*;
import android.text.style.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import android.widget.CompoundButton.*;
import com.kdt.pickafile.*;
import java.io.*;
import java.util.*;
import net.kdt.pojavlaunch.authenticator.microsoft.*;
import net.kdt.pojavlaunch.authenticator.mojang.*;
import net.kdt.pojavlaunch.customcontrols.*;
import net.kdt.pojavlaunch.prefs.*;
import net.kdt.pojavlaunch.utils.*;
import org.apache.commons.compress.archivers.tar.*;
import org.apache.commons.compress.compressors.xz.*;
import org.apache.commons.io.*;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.kdt.pickafile.FileListView;
import com.kdt.pickafile.FileSelectedListener;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Locale;
import net.kdt.pojavlaunch.authenticator.microsoft.MicrosoftAuthTask;
import net.kdt.pojavlaunch.authenticator.mojang.InvalidateTokenTask;
import net.kdt.pojavlaunch.authenticator.mojang.LoginListener;
import net.kdt.pojavlaunch.authenticator.mojang.LoginTask;
import net.kdt.pojavlaunch.authenticator.mojang.RefreshListener;
import net.kdt.pojavlaunch.customcontrols.ControlData;
import net.kdt.pojavlaunch.customcontrols.CustomControls;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.utils.JREUtils;
import net.kdt.pojavlaunch.utils.LocaleUtils;
import net.kdt.pojavlaunch.value.MinecraftAccount;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import org.apache.commons.io.FileUtils;
import net.kdt.pojavlaunch.value.*;
import com.google.gson.*;
import org.apache.commons.io.IOUtils;
public class PojavLoginActivity extends BaseActivity
// MineActivity
@ -683,10 +716,28 @@ public class PojavLoginActivity extends BaseActivity
for (String s : new File(Tools.DIR_ACCOUNT_NEW).list()) {
View child = inflater.inflate(R.layout.simple_account_list_item, null);
TextView accountName = child.findViewById(R.id.accountName);
ImageButton removeButton = child.findViewById(R.id.removeBtn);
ImageView accountIcon = child.findViewById(R.id.accountitem_image_icon);
TextView accountName = child.findViewById(R.id.accountitem_text_name);
ImageButton removeButton = child.findViewById(R.id.accountitem_button_remove);
accountName.setText(s.substring(0, s.length() - 5));
String accNameStr = s.substring(0, s.length() - 5);
String skinFaceBase64 = MinecraftAccount.load(accNameStr).skinFaceBase64;
Bitmap bitmap = Bitmap.createBitmap(8, 8, Bitmap.Config.ARGB_8888);
if (skinFaceBase64 != null) {
byte[] faceIconBytes = Base64.decode(skinFaceBase64, Base64.DEFAULT);
bitmap = BitmapFactory.decodeByteArray(faceIconBytes, 0, faceIconBytes.length);
} else {
try {
bitmap = BitmapFactory.decodeStream(getAssets().open("ic_steve.png"));
} catch (IOException e) {
// Should never happen
e.printStackTrace();
}
}
Bitmap upscaledBitmap = Bitmap.createScaledBitmap(bitmap, 80, 80, false);
accountIcon.setImageBitmap(upscaledBitmap);
accountName.setText(accNameStr);
accountListLayout.addView(child);
@ -807,11 +858,14 @@ public class PojavLoginActivity extends BaseActivity
builder.profileId = result[3];
builder.username = result[4];
builder.selectedVersion = "1.12.2";
builder.updateSkinFace();
mProfile = builder;
}
v.setEnabled(true);
prb.setVisibility(View.GONE);
playProfile(false);
runOnUiThread(() -> {
v.setEnabled(true);
prb.setVisibility(View.GONE);
playProfile(false);
});
}
}).execute(edit2.getText().toString(), edit3.getText().toString());
}

View File

@ -78,6 +78,7 @@ public class MicrosoftAuthTask extends AsyncTask<String, Void, Object> {
acc.profileId = msa.mcUuid;
acc.isMicrosoft = true;
acc.msaRefreshToken = msa.msRefreshToken;
acc.updateSkinFace();
}
acc.save();

View File

@ -6,7 +6,7 @@ import java.io.*;
import java.util.*;
import net.kdt.pojavlaunch.*;
public class LoginTask extends AsyncTask<String, Void, String[]>
public class LoginTask extends AsyncTask<String, Void, Void>
{
private YggdrasilAuthenticator authenticator = new YggdrasilAuthenticator();
//private String TAG = "MojangAuth-login";
@ -29,7 +29,7 @@ public class LoginTask extends AsyncTask<String, Void, String[]>
}
@Override
protected String[] doInBackground(String[] args) {
protected Void doInBackground(String[] args) {
ArrayList<String> str = new ArrayList<String>();
str.add("ERROR");
try{
@ -57,12 +57,15 @@ public class LoginTask extends AsyncTask<String, Void, String[]>
catch(Exception e){
str.add(e.getMessage());
}
return str.toArray(new String[0]);
listener.onLoginDone(str.toArray(new String[0]));
return null;
}
@Override
protected void onPostExecute(String[] result) {
listener.onLoginDone(result);
protected void onPostExecute(Void result) {
// listener.onLoginDone(result);
super.onPostExecute(result);
}
}

View File

@ -38,7 +38,7 @@ public class RefreshTokenTask extends AsyncTask<String, Void, Throwable> {
this.profilePath = MinecraftAccount.load(args[0]);
int responseCode = 400;
responseCode = this.authenticator.validate(profilePath.accessToken).statusCode;
if (responseCode >= 200 && responseCode < 300) {
if (responseCode == 403) {
RefreshResponse response = this.authenticator.refresh(profilePath.accessToken, UUID.fromString(profilePath.clientToken));
// if (response == null) {
// throw new NullPointerException("Response is null?");
@ -54,8 +54,9 @@ public class RefreshTokenTask extends AsyncTask<String, Void, Throwable> {
profilePath.accessToken = response.accessToken;
profilePath.username = response.selectedProfile.name;
profilePath.profileId = response.selectedProfile.id;
profilePath.save();
}
profilePath.updateSkinFace();
profilePath.save();
return null;
} catch (Throwable e) {
return e;

View File

@ -0,0 +1,51 @@
package net.kdt.pojavlaunch.value;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Base64;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.utils.DownloadUtils;
public class AccountSkin {
public static Bitmap getSkin(String uuid) throws IOException {
Profile p = Tools.GLOBAL_GSON.fromJson(DownloadUtils.downloadString("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid), Profile.class);
for (Property property : p.properties) {
if (property.name.equals("textures")) {
return getSkinFromProperty(Tools.GLOBAL_GSON.fromJson(new String(Base64.decode(property.value, Base64.DEFAULT), "UTF-8"), SkinProperty.class));
}
}
return null;
}
private static Bitmap getSkinFromProperty(SkinProperty p) throws IOException {
for (Map.Entry<String, Texture> texture : p.textures.entrySet()) {
if (texture.getKey().equals("SKIN")) {
String skinFile = File.createTempFile("skin", "png", new File(Tools.DIR_DATA, "cache")).getAbsolutePath();
Tools.downloadFile(texture.getValue().url, skinFile);
return BitmapFactory.decodeFile(skinFile);
}
}
return null;
}
public static class Texture {
public String url;
}
public static class SkinProperty {
public Map<String, Texture> textures;
}
public static class Property {
public String name, value;
}
public static class Profile {
public Property[] properties;
}
}

View File

@ -4,6 +4,10 @@ import android.util.Log;
import net.kdt.pojavlaunch.*;
import java.io.*;
import com.google.gson.*;
import android.os.Environment;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Base64;
public class MinecraftAccount
{
@ -14,6 +18,34 @@ public class MinecraftAccount
public String selectedVersion = "1.7.10";
public boolean isMicrosoft = false;
public String msaRefreshToken = "0";
public String skinFaceBase64;
public void updateSkinFace() {
try {
Bitmap bSkin = AccountSkin.getSkin(profileId);
if (bSkin.getWidth() != 64 || bSkin.getHeight() != 64) {
Log.w("SkinLoader", "Only skin size 64x64 is currently supported, this skin is " + bSkin.getWidth() + "x" + bSkin.getHeight());
return;
}
int[] pixels = new int[8 * 8];
bSkin.getPixels(pixels, 0, 8, 8, 8, 8, 8);
bSkin.recycle();
ByteArrayOutputStream outByteArr = new ByteArrayOutputStream();
Bitmap bFace = Bitmap.createBitmap(pixels, 8, 8, Bitmap.Config.ARGB_8888);
bFace.compress(Bitmap.CompressFormat.PNG, 100, outByteArr);
bFace.recycle();
skinFaceBase64 = Base64.encodeToString(outByteArr.toByteArray(), Base64.DEFAULT);
outByteArr.close();
Log.i("SkinLoader", "Update skin face success");
} catch (IOException e) {
// Skin refresh limit, no internet connection, etc...
// Simply ignore updating skin face
Log.w("SkinLoader", "Could not update skin face", e);
}
}
public String save(String outPath) throws IOException {
Tools.write(outPath, Tools.GLOBAL_GSON.toJson(this));
@ -50,7 +82,7 @@ public class MinecraftAccount
acc.msaRefreshToken = "0";
}
return acc;
}catch(IOException e) {
} catch(IOException e) {
Log.e(MinecraftAccount.class.getName(), "Caught an exception while loading the profile",e);
return null;
}

View File

@ -6,8 +6,19 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/accountitem_image_icon"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:paddingStart="10dp"
android:paddingEnd="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/accountName"
android:id="@+id/accountitem_text_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center_vertical"
@ -18,22 +29,23 @@
android:text="Name Placeholder"
app:layout_constraintEnd_toStartOf="@+id/removeBtn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/accountitem_image_icon"
app:layout_constraintRight_toLeftOf="@id/accountitem_button_remove"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/removeBtn"
android:id="@+id/accountitem_button_remove"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:background="?attr/selectableItemBackground"
android:paddingStart="10dp"
android:paddingEnd="10dp"
app:layout_constraintBottom_toBottomOf="@+id/accountName"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_remove" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>