mirror of https://github.com/stenzek/duckstation
Android: Implement RetroAchievements
parent
0f1dc93eaa
commit
c182edf196
@ -0,0 +1,76 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
public final class Achievement {
|
||||
public static final int CATEGORY_LOCAL = 0;
|
||||
public static final int CATEGORY_CORE = 3;
|
||||
public static final int CATEGORY_UNOFFICIAL = 5;
|
||||
|
||||
private final int id;
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final String lockedBadgePath;
|
||||
private final String unlockedBadgePath;
|
||||
private final int points;
|
||||
private final boolean locked;
|
||||
|
||||
public Achievement(int id, String name, String description, String lockedBadgePath,
|
||||
String unlockedBadgePath, int points, boolean locked) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.lockedBadgePath = lockedBadgePath;
|
||||
this.unlockedBadgePath = unlockedBadgePath;
|
||||
this.points = points;
|
||||
this.locked = locked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if challenge mode will be enabled when a game is started.
|
||||
* Does not depend on the emulation running.
|
||||
*
|
||||
* @param context context to pull settings from
|
||||
* @return true if challenge mode will be used, false otherwise
|
||||
*/
|
||||
public static boolean willChallengeModeBeEnabled(Context context) {
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
return prefs.getBoolean("Cheevos/Enabled", false) &&
|
||||
prefs.getBoolean("Cheevos/ChallengeMode", false);
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getLockedBadgePath() {
|
||||
return lockedBadgePath;
|
||||
}
|
||||
|
||||
public String getUnlockedBadgePath() {
|
||||
return unlockedBadgePath;
|
||||
}
|
||||
|
||||
public int getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
public boolean isLocked() {
|
||||
return locked;
|
||||
}
|
||||
|
||||
public String getBadgePath() {
|
||||
return locked ? lockedBadgePath : unlockedBadgePath;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,193 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
public class AchievementListFragment extends DialogFragment {
|
||||
|
||||
private RecyclerView mRecyclerView;
|
||||
private AchievementListFragment.ViewAdapter mAdapter;
|
||||
private final Achievement[] mAchievements;
|
||||
private DialogInterface.OnDismissListener mOnDismissListener;
|
||||
|
||||
public AchievementListFragment(Achievement[] achievements) {
|
||||
mAchievements = achievements;
|
||||
sortAchievements();
|
||||
}
|
||||
|
||||
public void setOnDismissListener(DialogInterface.OnDismissListener l) {
|
||||
mOnDismissListener = l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(@NonNull DialogInterface dialog) {
|
||||
if (mOnDismissListener != null)
|
||||
mOnDismissListener.onDismiss(dialog);
|
||||
|
||||
super.onDismiss(dialog);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (getDialog() == null)
|
||||
return;
|
||||
|
||||
final boolean isLandscape = (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
|
||||
final float scale = (float) getContext().getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT;
|
||||
final int width = Math.round((isLandscape ? 700.0f : 400.0f) * scale);
|
||||
final int height = Math.round((isLandscape ? 400.0f : 700.0f) * scale);
|
||||
getDialog().getWindow().setLayout(width, height);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_achievement_list, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
mAdapter = new AchievementListFragment.ViewAdapter(getContext(), mAchievements);
|
||||
|
||||
mRecyclerView = view.findViewById(R.id.recyclerView);
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
mRecyclerView.addItemDecoration(new DividerItemDecoration(mRecyclerView.getContext(),
|
||||
DividerItemDecoration.VERTICAL));
|
||||
|
||||
fillHeading(view);
|
||||
}
|
||||
|
||||
private void fillHeading(@NonNull View view) {
|
||||
final AndroidHostInterface hi = AndroidHostInterface.getInstance();
|
||||
final String gameTitle = hi.getCheevoGameTitle();
|
||||
if (gameTitle != null) {
|
||||
final String formattedTitle = hi.isCheevosChallengeModeActive() ?
|
||||
String.format(getString(R.string.achievement_title_challenge_mode_format_string), gameTitle) :
|
||||
gameTitle;
|
||||
((TextView) view.findViewById(R.id.title)).setText(formattedTitle);
|
||||
}
|
||||
|
||||
final int cheevoCount = hi.getCheevoCount();
|
||||
final int unlockedCheevoCount = hi.getUnlockedCheevoCount();
|
||||
final String summary = String.format(getString(R.string.achievement_summary_format_string),
|
||||
unlockedCheevoCount, cheevoCount, hi.getCheevoPointsForGame(), hi.getCheevoMaximumPointsForGame());
|
||||
((TextView) view.findViewById(R.id.summary)).setText(summary);
|
||||
|
||||
ProgressBar pb = ((ProgressBar) view.findViewById(R.id.progressBar));
|
||||
pb.setMax(cheevoCount);
|
||||
pb.setProgress(unlockedCheevoCount);
|
||||
|
||||
final ImageView icon = ((ImageView) view.findViewById(R.id.icon));
|
||||
final String badgePath = hi.getCheevoGameIconPath();
|
||||
if (badgePath != null) {
|
||||
new ImageLoadTask(icon).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, badgePath);
|
||||
}
|
||||
}
|
||||
|
||||
private void sortAchievements() {
|
||||
Arrays.sort(mAchievements, (o1, o2) -> {
|
||||
if (o2.isLocked() && !o1.isLocked())
|
||||
return -1;
|
||||
else if (o1.isLocked() && !o2.isLocked())
|
||||
return 1;
|
||||
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
});
|
||||
}
|
||||
|
||||
private static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
|
||||
private final View mItemView;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
mItemView = itemView;
|
||||
mItemView.setOnClickListener(this);
|
||||
mItemView.setOnLongClickListener(this);
|
||||
}
|
||||
|
||||
public void bindToEntry(Achievement cheevo) {
|
||||
ImageView icon = ((ImageView) mItemView.findViewById(R.id.icon));
|
||||
icon.setImageDrawable(mItemView.getContext().getDrawable(R.drawable.ic_baseline_lock_24));
|
||||
|
||||
final String badgePath = cheevo.getBadgePath();
|
||||
if (badgePath != null) {
|
||||
new ImageLoadTask(icon).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, badgePath);
|
||||
}
|
||||
|
||||
((TextView) mItemView.findViewById(R.id.title)).setText(cheevo.getName());
|
||||
((TextView) mItemView.findViewById(R.id.description)).setText(cheevo.getDescription());
|
||||
|
||||
((ImageView) mItemView.findViewById(R.id.locked_icon)).setImageDrawable(
|
||||
mItemView.getContext().getDrawable(cheevo.isLocked() ? R.drawable.ic_baseline_lock_24 : R.drawable.ic_baseline_lock_open_24));
|
||||
|
||||
final String pointsString = String.format(mItemView.getContext().getString(R.string.achievement_points_format_string), cheevo.getPoints());
|
||||
((TextView) mItemView.findViewById(R.id.points)).setText(pointsString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
//
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ViewAdapter extends RecyclerView.Adapter<AchievementListFragment.ViewHolder> {
|
||||
private final LayoutInflater mInflater;
|
||||
private final Achievement[] mAchievements;
|
||||
|
||||
public ViewAdapter(@NonNull Context context, Achievement[] achievements) {
|
||||
mInflater = LayoutInflater.from(context);
|
||||
mAchievements = achievements;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AchievementListFragment.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new AchievementListFragment.ViewHolder(mInflater.inflate(R.layout.layout_achievement_entry, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull AchievementListFragment.ViewHolder holder, int position) {
|
||||
holder.bindToEntry(mAchievements[position]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return (mAchievements != null) ? mAchievements.length : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return R.layout.layout_game_list_entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,266 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
public class AchievementSettingsFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceClickListener {
|
||||
private static final String REGISTER_URL = "http://retroachievements.org/createaccount.php";
|
||||
private static final String PROFILE_URL_PREFIX = "https://retroachievements.org/user/";
|
||||
|
||||
private boolean isLoggedIn = false;
|
||||
private String username;
|
||||
private String loginTokenTime;
|
||||
|
||||
public AchievementSettingsFragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.achievement_preferences, rootKey);
|
||||
updateViews();
|
||||
}
|
||||
|
||||
private void updateViews() {
|
||||
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
|
||||
|
||||
username = prefs.getString("Cheevos/Username", "");
|
||||
isLoggedIn = (username != null && !username.isEmpty());
|
||||
if (isLoggedIn) {
|
||||
try {
|
||||
final String loginTokenTimeString = prefs.getString("Cheevos/LoginTimestamp", "");
|
||||
final long loginUnixTimestamp = Long.parseLong(loginTokenTimeString);
|
||||
|
||||
// TODO: Extract to a helper function.
|
||||
final Date date = new Date(loginUnixTimestamp * 1000);
|
||||
final DateFormat format = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.SHORT, Locale.getDefault());
|
||||
loginTokenTime = format.format(date);
|
||||
} catch (Exception e) {
|
||||
loginTokenTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
final PreferenceScreen preferenceScreen = getPreferenceScreen();
|
||||
|
||||
Preference preference = preferenceScreen.findPreference("Cheevos/ChallengeMode");
|
||||
if (preference != null) {
|
||||
// toggling this is disabled while it's running to avoid the whole power off thing
|
||||
preference.setEnabled(!AndroidHostInterface.getInstance().isEmulationThreadRunning());
|
||||
}
|
||||
|
||||
preference = preferenceScreen.findPreference("Cheevos/Login");
|
||||
if (preference != null)
|
||||
{
|
||||
preference.setVisible(!isLoggedIn);
|
||||
preference.setOnPreferenceClickListener(this);
|
||||
}
|
||||
|
||||
preference = preferenceScreen.findPreference("Cheevos/Register");
|
||||
if (preference != null)
|
||||
{
|
||||
preference.setVisible(!isLoggedIn);
|
||||
preference.setOnPreferenceClickListener(this);
|
||||
}
|
||||
|
||||
preference = preferenceScreen.findPreference("Cheevos/Logout");
|
||||
if (preference != null)
|
||||
{
|
||||
preference.setVisible(isLoggedIn);
|
||||
preference.setOnPreferenceClickListener(this);
|
||||
}
|
||||
|
||||
preference = preferenceScreen.findPreference("Cheevos/Username");
|
||||
if (preference != null)
|
||||
{
|
||||
preference.setVisible(isLoggedIn);
|
||||
preference.setSummary((username != null) ? username : "");
|
||||
}
|
||||
|
||||
preference = preferenceScreen.findPreference("Cheevos/LoginTokenTime");
|
||||
if (preference != null)
|
||||
{
|
||||
preference.setVisible(isLoggedIn);
|
||||
preference.setSummary((loginTokenTime != null) ? loginTokenTime : "");
|
||||
}
|
||||
|
||||
preference = preferenceScreen.findPreference("Cheevos/ViewProfile");
|
||||
if (preference != null)
|
||||
{
|
||||
preference.setVisible(isLoggedIn);
|
||||
preference.setOnPreferenceClickListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
final String key = preference.getKey();
|
||||
if (key == null)
|
||||
return false;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case "Cheevos/Login":
|
||||
{
|
||||
handleLogin();
|
||||
return true;
|
||||
}
|
||||
|
||||
case "Cheevos/Logout":
|
||||
{
|
||||
handleLogout();
|
||||
return true;
|
||||
}
|
||||
|
||||
case "Cheevos/Register":
|
||||
{
|
||||
openUrl(REGISTER_URL);
|
||||
return true;
|
||||
}
|
||||
|
||||
case "Cheevos/ViewProfile":
|
||||
{
|
||||
final String profileUrl = getProfileUrl(username);
|
||||
if (profileUrl != null)
|
||||
openUrl(profileUrl);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void openUrl(String url) {
|
||||
final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
startActivity(browserIntent);
|
||||
}
|
||||
|
||||
private void handleLogin() {
|
||||
LoginDialogFragment loginDialog = new LoginDialogFragment(this);
|
||||
loginDialog.show(getFragmentManager(), "fragment_achievement_login");
|
||||
}
|
||||
|
||||
private void handleLogout() {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder.setTitle(R.string.settings_achievements_confirm_logout_title);
|
||||
builder.setMessage(R.string.settings_achievements_confirm_logout_message);
|
||||
builder.setPositiveButton(R.string.settings_achievements_logout, (dialog, which) -> {
|
||||
AndroidHostInterface.getInstance().cheevosLogout();
|
||||
updateViews();
|
||||
});
|
||||
builder.setNegativeButton(R.string.achievement_settings_login_cancel_button, (dialog, which) -> dialog.dismiss());
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private static String getProfileUrl(String username) {
|
||||
try {
|
||||
final String encodedUsername = URLEncoder.encode(username, "UTF-8");
|
||||
return PROFILE_URL_PREFIX + encodedUsername;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LoginDialogFragment extends DialogFragment {
|
||||
private AchievementSettingsFragment mParent;
|
||||
|
||||
public LoginDialogFragment(AchievementSettingsFragment parent) {
|
||||
mParent = parent;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_achievements_login, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
((Button)view.findViewById(R.id.login)).setOnClickListener((View.OnClickListener) v -> doLogin());
|
||||
((Button)view.findViewById(R.id.cancel)).setOnClickListener((View.OnClickListener) v -> dismiss());
|
||||
}
|
||||
|
||||
private static class LoginTask extends AsyncTask<Void, Void, Void> {
|
||||
private LoginDialogFragment mParent;
|
||||
private String mUsername;
|
||||
private String mPassword;
|
||||
private boolean mResult;
|
||||
|
||||
public LoginTask(LoginDialogFragment parent, String username, String password) {
|
||||
mParent = parent;
|
||||
mUsername = username;
|
||||
mPassword = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
final Activity activity = mParent.getActivity();
|
||||
if (activity == null)
|
||||
return null;
|
||||
|
||||
mResult = AndroidHostInterface.getInstance().cheevosLogin(mUsername, mPassword);
|
||||
|
||||
activity.runOnUiThread(() -> {
|
||||
if (!mResult) {
|
||||
((TextView) mParent.getView().findViewById(R.id.error)).setText(R.string.achievement_settings_login_failed);
|
||||
mParent.enableUi(true);
|
||||
return;
|
||||
}
|
||||
|
||||
mParent.mParent.updateViews();
|
||||
mParent.dismiss();
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void doLogin() {
|
||||
final View rootView = getView();
|
||||
final String username = ((EditText)rootView.findViewById(R.id.username)).getText().toString();
|
||||
final String password = ((EditText)rootView.findViewById(R.id.password)).getText().toString();
|
||||
if (username == null || username.length() == 0 || password == null || password.length() == 0)
|
||||
return;
|
||||
|
||||
enableUi(false);
|
||||
((TextView)rootView.findViewById(R.id.error)).setText("");
|
||||
new LoginDialogFragment.LoginTask(this, username, password).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private void enableUi(boolean enabled) {
|
||||
final View rootView = getView();
|
||||
((EditText)rootView.findViewById(R.id.username)).setEnabled(enabled);
|
||||
((EditText)rootView.findViewById(R.id.password)).setEnabled(enabled);
|
||||
((Button)rootView.findViewById(R.id.login)).setEnabled(enabled);
|
||||
((Button)rootView.findViewById(R.id.cancel)).setEnabled(enabled);
|
||||
((ProgressBar)rootView.findViewById(R.id.progressBar)).setVisibility(enabled ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h1.9c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM18,20L6,20L6,10h12v10z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/navigation_header_container"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentStart="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:foregroundGravity="center_vertical"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentStart="true"
|
||||
tools:srcCompat="@drawable/ic_media_cdrom" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:text="Game Title"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Large"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toRightOf="@+id/icon"
|
||||
android:layout_alignParentEnd="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:text="You have unlocked %d of %d achievements, earning %d of %d possible points."
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:layout_below="@+id/title"
|
||||
android:layout_toRightOf="@+id/icon"
|
||||
android:layout_alignParentEnd="true" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_below="@+id/summary"
|
||||
android:layout_toRightOf="@+id/icon"
|
||||
android:layout_alignParentEnd="true" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:scrollbars="vertical"
|
||||
android:layout_below="@+id/navigation_header_container"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentEnd="true" />
|
||||
|
||||
</RelativeLayout>
|
||||
@ -0,0 +1,133 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="280dp"
|
||||
>
|
||||
<LinearLayout
|
||||
android:id="@+id/panel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:text="@string/achievement_settings_login_title"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/achievement_settings_login_help"
|
||||
/>
|
||||
<EditText
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:inputType="textVisiblePassword"
|
||||
android:nextFocusDown="@+id/password"
|
||||
android:imeOptions="actionNext"
|
||||
android:singleLine="true"
|
||||
android:hint="@string/achievement_settings_login_username_hint"
|
||||
/>
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:inputType="textPassword"
|
||||
android:imeOptions="actionDone"
|
||||
android:singleLine="true"
|
||||
android:hint="@string/achievement_settings_login_password_hint"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:visibility="visible" />
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/buttonPanel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="?android:attr/dividerHorizontal"
|
||||
android:dividerPadding="0dip"
|
||||
android:minHeight="48dp"
|
||||
android:orientation="vertical"
|
||||
android:showDividers="beginning"
|
||||
android:padding="5dp"
|
||||
>
|
||||
<LinearLayout
|
||||
style="?android:attr/buttonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layoutDirection="locale"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="UselessParent"
|
||||
>
|
||||
<Space
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancel"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:focusable="true"
|
||||
android:maxLines="2"
|
||||
android:minHeight="48dp"
|
||||
android:text="@string/achievement_settings_login_cancel_button"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/login"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:focusable="true"
|
||||
android:maxLines="2"
|
||||
android:minHeight="48dp"
|
||||
android:text="@string/achievement_settings_login_login_button"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminateOnly="true"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
</FrameLayout>
|
||||
@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/linearLayout"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:background="?android:attr/selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:foregroundGravity="center_vertical"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:srcCompat="@drawable/ic_media_cdrom" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="80dp"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:text="Achievement Title"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Large"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="80dp"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:paddingBottom="8px"
|
||||
android:text="Achievement Description"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/locked_icon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:paddingBottom="8px"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_baseline_lock_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/points"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:text="5 Points"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:srcCompat="@drawable/ic_star_5"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/locked_icon" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceCategory app:title="Global Settings" app:iconSpaceReserved="false">
|
||||
<SwitchPreferenceCompat
|
||||
app:key="Cheevos/Enabled"
|
||||
app:title="@string/settings_achievements_enable"
|
||||
app:summary="@string/settings_summary_achievements_enable"
|
||||
app:defaultValue="false"
|
||||
app:iconSpaceReserved="false" />
|
||||
<SwitchPreferenceCompat
|
||||
app:key="Cheevos/ChallengeMode"
|
||||
app:title="@string/settings_achievements_challenge_mode"
|
||||
app:summary="@string/settings_summary_achievements_challenge_mode"
|
||||
app:dependency="Cheevos/Enabled"
|
||||
app:defaultValue="false"
|
||||
app:iconSpaceReserved="false" />
|
||||
<SwitchPreferenceCompat
|
||||
app:key="Cheevos/RichPresence"
|
||||
app:title="@string/settings_achievements_rich_presence"
|
||||
app:summary="@string/settings_summary_achievements_rich_presence"
|
||||
app:dependency="Cheevos/Enabled"
|
||||
app:defaultValue="true"
|
||||
app:iconSpaceReserved="false" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory app:title="Account" app:iconSpaceReserved="false">
|
||||
<Preference
|
||||
app:key="Cheevos/Username"
|
||||
app:title="@string/settings_achievements_username"
|
||||
app:iconSpaceReserved="false" />
|
||||
<Preference
|
||||
app:key="Cheevos/LoginTokenTime"
|
||||
app:title="@string/settings_achievements_token_generation_time"
|
||||
app:iconSpaceReserved="false" />
|
||||
<PreferenceScreen
|
||||
app:key="Cheevos/Login"
|
||||
app:title="@string/settings_achievements_login"
|
||||
app:summary="@string/settings_summary_achievements_login"
|
||||
app:iconSpaceReserved="false" />
|
||||
<PreferenceScreen
|
||||
app:key="Cheevos/Register"
|
||||
app:title="@string/settings_achievements_register"
|
||||
app:summary="@string/settings_summary_achievements_register"
|
||||
app:iconSpaceReserved="false" />
|
||||
<PreferenceScreen
|
||||
app:key="Cheevos/Logout"
|
||||
app:title="@string/settings_achievements_logout"
|
||||
app:summary="@string/settings_summary_achievements_logout"
|
||||
app:iconSpaceReserved="false" />
|
||||
<PreferenceScreen
|
||||
app:key="Cheevos/ViewProfile"
|
||||
app:title="@string/settings_achievements_view_profile"
|
||||
app:summary="@string/settings_summary_achievements_view_profile"
|
||||
app:iconSpaceReserved="false" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory app:title="@string/settings_achievements_disclaimer" app:iconSpaceReserved="false">
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
Loading…
Reference in New Issue