Detailed signup errors
This commit is contained in:
parent
37caa0f607
commit
b9ba198408
|
@ -8,6 +8,7 @@ import android.util.Log;
|
||||||
import com.google.gson.FieldNamingPolicy;
|
import com.google.gson.FieldNamingPolicy;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonIOException;
|
import com.google.gson.JsonIOException;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
@ -25,6 +26,8 @@ import java.io.IOException;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -157,10 +160,29 @@ public class MastodonAPIController{
|
||||||
try{
|
try{
|
||||||
JsonObject error=JsonParser.parseReader(reader).getAsJsonObject();
|
JsonObject error=JsonParser.parseReader(reader).getAsJsonObject();
|
||||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" received error: "+error);
|
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" received error: "+error);
|
||||||
req.onError(error.get("error").getAsString(), response.code());
|
if(error.has("details")){
|
||||||
|
MastodonDetailedErrorResponse err=new MastodonDetailedErrorResponse(error.get("error").getAsString(), response.code());
|
||||||
|
HashMap<String, List<MastodonDetailedErrorResponse.FieldError>> details=new HashMap<>();
|
||||||
|
JsonObject errorDetails=error.getAsJsonObject("details");
|
||||||
|
for(String key:errorDetails.keySet()){
|
||||||
|
ArrayList<MastodonDetailedErrorResponse.FieldError> fieldErrors=new ArrayList<>();
|
||||||
|
for(JsonElement el:errorDetails.getAsJsonArray(key)){
|
||||||
|
JsonObject eobj=el.getAsJsonObject();
|
||||||
|
MastodonDetailedErrorResponse.FieldError fe=new MastodonDetailedErrorResponse.FieldError();
|
||||||
|
fe.description=eobj.get("description").getAsString();
|
||||||
|
fe.error=eobj.get("error").getAsString();
|
||||||
|
fieldErrors.add(fe);
|
||||||
|
}
|
||||||
|
details.put(key, fieldErrors);
|
||||||
|
}
|
||||||
|
err.detailedErrors=details;
|
||||||
|
req.onError(err);
|
||||||
|
}else{
|
||||||
|
req.onError(error.get("error").getAsString(), response.code());
|
||||||
|
}
|
||||||
}catch(JsonIOException|JsonSyntaxException x){
|
}catch(JsonIOException|JsonSyntaxException x){
|
||||||
req.onError(response.code()+" "+response.message(), response.code());
|
req.onError(response.code()+" "+response.message(), response.code());
|
||||||
}catch(IllegalStateException x){
|
}catch(Exception x){
|
||||||
req.onError("Error parsing an API error", response.code());
|
req.onError("Error parsing an API error", response.code());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import androidx.annotation.CallSuper;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import me.grishka.appkit.api.APIRequest;
|
import me.grishka.appkit.api.APIRequest;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import okhttp3.Call;
|
import okhttp3.Call;
|
||||||
import okhttp3.RequestBody;
|
import okhttp3.RequestBody;
|
||||||
|
|
||||||
|
@ -183,6 +184,10 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onError(ErrorResponse err){
|
||||||
|
invokeErrorCallback(err);
|
||||||
|
}
|
||||||
|
|
||||||
void onError(String msg, int httpStatus){
|
void onError(String msg, int httpStatus){
|
||||||
invokeErrorCallback(new MastodonErrorResponse(msg, httpStatus));
|
invokeErrorCallback(new MastodonErrorResponse(msg, httpStatus));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package org.joinmastodon.android.api;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class MastodonDetailedErrorResponse extends MastodonErrorResponse{
|
||||||
|
public Map<String, List<FieldError>> detailedErrors;
|
||||||
|
|
||||||
|
public MastodonDetailedErrorResponse(String error, int httpStatus){
|
||||||
|
super(error, httpStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FieldError{
|
||||||
|
public String error;
|
||||||
|
public String description;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
@ -17,11 +18,10 @@ import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonDetailedErrorResponse;
|
||||||
import org.joinmastodon.android.api.requests.accounts.RegisterAccount;
|
import org.joinmastodon.android.api.requests.accounts.RegisterAccount;
|
||||||
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
||||||
import org.joinmastodon.android.api.requests.oauth.GetOauthToken;
|
import org.joinmastodon.android.api.requests.oauth.GetOauthToken;
|
||||||
|
@ -39,8 +39,11 @@ import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.util.HashSet;
|
||||||
import java.nio.file.Files;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
|
@ -70,6 +73,7 @@ public class SignupFragment extends AppKitFragment{
|
||||||
private ProgressDialog progressDialog;
|
private ProgressDialog progressDialog;
|
||||||
private Uri avatarUri;
|
private Uri avatarUri;
|
||||||
private File avatarFile;
|
private File avatarFile;
|
||||||
|
private HashSet<EditText> errorFields=new HashSet<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
@ -115,6 +119,10 @@ public class SignupFragment extends AppKitFragment{
|
||||||
email.addTextChangedListener(buttonStateUpdater);
|
email.addTextChangedListener(buttonStateUpdater);
|
||||||
password.addTextChangedListener(buttonStateUpdater);
|
password.addTextChangedListener(buttonStateUpdater);
|
||||||
|
|
||||||
|
username.addTextChangedListener(new ErrorClearingListener(username));
|
||||||
|
email.addTextChangedListener(new ErrorClearingListener(email));
|
||||||
|
password.addTextChangedListener(new ErrorClearingListener(password));
|
||||||
|
|
||||||
avaWrap.setOutlineProvider(OutlineProviders.roundedRect(22));
|
avaWrap.setOutlineProvider(OutlineProviders.roundedRect(22));
|
||||||
avaWrap.setClipToOutline(true);
|
avaWrap.setClipToOutline(true);
|
||||||
avaWrap.setOnClickListener(v->onAvatarClick());
|
avaWrap.setOnClickListener(v->onAvatarClick());
|
||||||
|
@ -172,8 +180,12 @@ public class SignupFragment extends AppKitFragment{
|
||||||
}
|
}
|
||||||
|
|
||||||
private void actuallySubmit(){
|
private void actuallySubmit(){
|
||||||
String username=this.username.getText().toString();
|
String username=this.username.getText().toString().trim();
|
||||||
String email=this.email.getText().toString();
|
String email=this.email.getText().toString().trim();
|
||||||
|
for(EditText edit:errorFields){
|
||||||
|
edit.setError(null);
|
||||||
|
}
|
||||||
|
errorFields.clear();
|
||||||
new RegisterAccount(username, email, password.getText().toString(), getResources().getConfiguration().locale.getLanguage(), null)
|
new RegisterAccount(username, email, password.getText().toString(), getResources().getConfiguration().locale.getLanguage(), null)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
|
@ -194,7 +206,23 @@ public class SignupFragment extends AppKitFragment{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
error.showToast(getActivity());
|
if(error instanceof MastodonDetailedErrorResponse){
|
||||||
|
Map<String, List<MastodonDetailedErrorResponse.FieldError>> fieldErrors=((MastodonDetailedErrorResponse) error).detailedErrors;
|
||||||
|
boolean first=true;
|
||||||
|
for(String fieldName:fieldErrors.keySet()){
|
||||||
|
EditText field=getFieldByName(fieldName);
|
||||||
|
if(field==null)
|
||||||
|
continue;
|
||||||
|
field.setError(fieldErrors.get(fieldName).stream().map(err->err.description).collect(Collectors.joining("\n")));
|
||||||
|
errorFields.add(field);
|
||||||
|
if(first){
|
||||||
|
first=false;
|
||||||
|
field.requestFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
error.showToast(getActivity());
|
||||||
|
}
|
||||||
progressDialog.dismiss();
|
progressDialog.dismiss();
|
||||||
progressDialog=null;
|
progressDialog=null;
|
||||||
}
|
}
|
||||||
|
@ -202,6 +230,15 @@ public class SignupFragment extends AppKitFragment{
|
||||||
.exec(instance.uri, apiToken);
|
.exec(instance.uri, apiToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EditText getFieldByName(String name){
|
||||||
|
return switch(name){
|
||||||
|
case "email" -> email;
|
||||||
|
case "username" -> username;
|
||||||
|
case "password" -> password;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void showProgressDialog(){
|
private void showProgressDialog(){
|
||||||
progressDialog=new ProgressDialog(getActivity());
|
progressDialog=new ProgressDialog(getActivity());
|
||||||
progressDialog.setMessage(getString(R.string.loading));
|
progressDialog.setMessage(getString(R.string.loading));
|
||||||
|
@ -287,4 +324,30 @@ public class SignupFragment extends AppKitFragment{
|
||||||
private void onAvatarClick(){
|
private void onAvatarClick(){
|
||||||
startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT).setType("image/*").addCategory(Intent.CATEGORY_OPENABLE), AVATAR_RESULT);
|
startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT).setType("image/*").addCategory(Intent.CATEGORY_OPENABLE), AVATAR_RESULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ErrorClearingListener implements TextWatcher{
|
||||||
|
public final EditText editText;
|
||||||
|
|
||||||
|
private ErrorClearingListener(EditText editText){
|
||||||
|
this.editText=editText;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s){
|
||||||
|
if(errorFields.contains(editText)){
|
||||||
|
errorFields.remove(editText);
|
||||||
|
editText.setError(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,7 +176,7 @@
|
||||||
<string name="back">Back</string>
|
<string name="back">Back</string>
|
||||||
<string name="instance_catalog_title">Mastodon is made of users in different communities.</string>
|
<string name="instance_catalog_title">Mastodon is made of users in different communities.</string>
|
||||||
<string name="instance_catalog_subtitle">Pick a community based on your interests, region, or a general purpose one. You can still connect with everyone, regardless of community.</string>
|
<string name="instance_catalog_subtitle">Pick a community based on your interests, region, or a general purpose one. You can still connect with everyone, regardless of community.</string>
|
||||||
<string name="search_communities">Search communities</string>
|
<string name="search_communities">Search communities or enter URL</string>
|
||||||
<string name="instance_rules_title">Some ground rules</string>
|
<string name="instance_rules_title">Some ground rules</string>
|
||||||
<string name="instance_rules_subtitle">Take a minute to review the rules set and enforced by %s admins.</string>
|
<string name="instance_rules_subtitle">Take a minute to review the rules set and enforced by %s admins.</string>
|
||||||
<string name="signup_title">Let\'s get you set up on %s</string>
|
<string name="signup_title">Let\'s get you set up on %s</string>
|
||||||
|
|
Loading…
Reference in New Issue