Detailed signup errors

This commit is contained in:
Grishka 2022-04-06 04:16:35 +03:00
parent 37caa0f607
commit b9ba198408
5 changed files with 118 additions and 10 deletions

View File

@ -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());
} }
} }

View File

@ -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));
} }

View File

@ -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;
}
}

View File

@ -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);
}
}
}
} }

View File

@ -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>