RetroAchievements - Android login callback

Modify the RetroAchievements login code in Android to pass in a callback, pop a message if login fails, close the login box if it succeeds.
This commit is contained in:
LillyJadeKatrin 2025-06-09 23:38:49 -04:00
parent 1e0f6a557e
commit ddced1a070
8 changed files with 87 additions and 12 deletions

View File

@ -2,12 +2,21 @@
package org.dolphinemu.dolphinemu.features.settings.model
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
object AchievementModel {
@JvmStatic
external fun init()
suspend fun asyncLogin(password: String): Boolean {
return withContext(Dispatchers.IO) {
login(password)
}
}
@JvmStatic
external fun login(password: String)
private external fun login(password: String): Boolean
@JvmStatic
external fun logout()

View File

@ -7,12 +7,15 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import org.dolphinemu.dolphinemu.databinding.DialogLoginBinding
import org.dolphinemu.dolphinemu.features.settings.model.AchievementModel.login
import org.dolphinemu.dolphinemu.dialogs.AlertMessage
import org.dolphinemu.dolphinemu.features.settings.model.AchievementModel.asyncLogin
import org.dolphinemu.dolphinemu.features.settings.model.NativeConfig
import org.dolphinemu.dolphinemu.features.settings.model.StringSetting
class LoginDialog : DialogFragment() {
class LoginDialog(val parent: SettingsFragmentPresenter) : DialogFragment() {
private var _binding: DialogLoginBinding? = null
private val binding get() = _binding!!
@ -43,9 +46,18 @@ class LoginDialog : DialogFragment() {
}
private fun onLoginClicked() {
binding.loginFailed.visibility = View.GONE
binding.loginInProgress.visibility = View.VISIBLE
StringSetting.ACHIEVEMENTS_USERNAME.setString(NativeConfig.LAYER_BASE_OR_CURRENT,
binding.usernameInput.text.toString())
login(binding.passwordInput.text.toString())
dismiss()
lifecycleScope.launch {
if (asyncLogin(binding.passwordInput.text.toString())) {
parent.loadSettingsList()
dismiss()
} else {
binding.loginInProgress.visibility = View.GONE
binding.loginFailed.visibility = View.VISIBLE
}
}
}
}

View File

@ -101,7 +101,7 @@ class SettingsFragmentPresenter(
}
}
private fun loadSettingsList() {
fun loadSettingsList() {
val sl = ArrayList<SettingsItem>()
when (menuTag) {
MenuTag.SETTINGS -> addTopLevelSettings(sl)
@ -964,7 +964,7 @@ class SettingsFragmentPresenter(
0,
false
) {
fragmentView.showDialogFragment(LoginDialog())
fragmentView.showDialogFragment(LoginDialog(this))
loadSettingsList()
})
} else {

View File

@ -36,7 +36,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/spacing_large"
app:layout_constraintBottom_toTopOf="@id/button_cancel"
app:layout_constraintBottom_toTopOf="@id/login_in_progress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/username">
@ -53,6 +53,38 @@
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/login_in_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="@string/achievements_login_in_progress"
android:textAlignment="center"
android:textColor="?attr/colorOnErrorContainer"
android:visibility="invisible"
android:clickable="false"
android:focusable="false"
app:layout_constraintBottom_toTopOf="@id/login_failed"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/password"/>
<TextView
android:id="@+id/login_failed"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="@string/achievements_login_failed"
android:textAlignment="center"
android:textColor="?attr/colorOnErrorContainer"
android:visibility="invisible"
android:clickable="false"
android:focusable="false"
app:layout_constraintBottom_toTopOf="@id/button_cancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/login_in_progress"/>
<Button
android:id="@+id/button_cancel"
android:layout_width="0dp"
@ -62,7 +94,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/button_login"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/password" />
app:layout_constraintTop_toBottomOf="@id/login_failed" />
<Button
android:id="@+id/button_login"
@ -73,6 +105,6 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/button_cancel"
app:layout_constraintTop_toBottomOf="@id/password" />
app:layout_constraintTop_toBottomOf="@id/login_failed" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -966,4 +966,6 @@ It can efficiently compress both junk data and encrypted Wii data.
<string name="achievements_password">Password</string>
<string name="achievements_login">Log In</string>
<string name="achievements_logout">Log Out</string>
<string name="achievements_login_in_progress">Logging In</string>
<string name="achievements_login_failed">Login Failed</string>
</resources>

View File

@ -3,8 +3,12 @@
#include <jni.h>
#include <latch>
#include "Common/Event.h"
#include "Common/HookableEvent.h"
#include "Core/AchievementManager.h"
#include "jni/AndroidCommon/AndroidCommon.h"
#include "jni/AndroidCommon/IDCache.h"
extern "C" {
@ -14,11 +18,21 @@ Java_org_dolphinemu_dolphinemu_features_settings_model_AchievementModel_init(JNI
AchievementManager::GetInstance().Init(nullptr);
}
JNIEXPORT void JNICALL
JNIEXPORT jboolean JNICALL
Java_org_dolphinemu_dolphinemu_features_settings_model_AchievementModel_login(JNIEnv* env, jclass,
jstring password)
{
AchievementManager::GetInstance().Login(GetJString(env, password));
auto& instance = AchievementManager::GetInstance();
bool success;
std::latch login_complete_event{1};
Common::EventHook login_hook =
instance.login_event.Register([&login_complete_event, &success](int result) {
success = (result == RC_OK);
login_complete_event.count_down();
});
instance.Login(GetJString(env, password));
login_complete_event.wait();
return success;
}
JNIEXPORT void JNICALL

View File

@ -934,6 +934,7 @@ void AchievementManager::LoginCallback(int result, const char* error_message, rc
Config::Get(Config::RA_USERNAME));
Config::SetBaseOrCurrent(Config::RA_API_TOKEN, "");
instance.update_event.Trigger({.failed_login_code = result});
instance.login_event.Trigger(result);
return;
}
@ -946,6 +947,7 @@ void AchievementManager::LoginCallback(int result, const char* error_message, rc
{
WARN_LOG_FMT(ACHIEVEMENTS, "Failed to retrieve user information from client.");
instance.update_event.Trigger({.failed_login_code = RC_INVALID_STATE});
instance.login_event.Trigger(RC_INVALID_STATE);
return;
}
@ -965,9 +967,12 @@ void AchievementManager::LoginCallback(int result, const char* error_message, rc
user->username, Config::Get(Config::RA_USERNAME));
rc_client_logout(client);
instance.update_event.Trigger({.failed_login_code = RC_INVALID_STATE});
instance.login_event.Trigger(RC_INVALID_STATE);
return;
}
}
instance.login_event.Trigger(RC_OK);
INFO_LOG_FMT(ACHIEVEMENTS, "Successfully logged in {} to RetroAchievements server.",
user->username);
std::lock_guard lg{instance.GetLock()};

View File

@ -121,6 +121,7 @@ public:
int failed_login_code = 0;
};
Common::HookableEvent<const UpdatedItems&> update_event;
Common::HookableEvent<int> login_event;
static AchievementManager& GetInstance();
void Init(void* hwnd);