VQA fixes part 1

This commit is contained in:
Grishka 2024-06-08 14:49:39 +03:00
parent 1b4afe7ba9
commit bf44f7ef13
11 changed files with 168 additions and 124 deletions

View File

@ -73,8 +73,6 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
protected boolean isSignup;
protected CatalogInstance fakeInstance=new CatalogInstance();
private static final double DUNBAR=Math.log(800);
public InstanceCatalogFragment(int layout, int perPage){
super(layout, perPage);
}
@ -155,7 +153,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
}
protected void loadInstanceInfo(String _domain, boolean isFromRedirect, Consumer<Object> onError){
if(TextUtils.isEmpty(_domain))
if(TextUtils.isEmpty(_domain) || _domain.indexOf('.')==-1)
return;
String domain=normalizeInstanceDomain(_domain);
Instance cachedInstance=instancesCache.get(domain);

View File

@ -5,8 +5,9 @@ import android.app.AlertDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
@ -26,20 +27,17 @@ import android.widget.PopupMenu;
import android.widget.RadioButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.MastodonErrorResponse;
import org.joinmastodon.android.api.requests.accounts.CheckInviteLink;
import org.joinmastodon.android.api.requests.catalog.GetCatalogCategories;
import org.joinmastodon.android.api.requests.catalog.GetCatalogInstances;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.catalog.CatalogCategory;
import org.joinmastodon.android.model.catalog.CatalogInstance;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.FilterChipView;
@ -49,11 +47,9 @@ import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Random;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@ -71,9 +67,6 @@ import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class InstanceCatalogSignupFragment extends InstanceCatalogFragment implements OnBackPressedListener{
private MastodonAPIRequest<?> getCategoriesRequest;
private String currentCategory="all";
private List<CatalogCategory> categories=new ArrayList<>();
private View topBar;
private List<String> languages=Collections.emptyList();
@ -149,58 +142,16 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
}
})
.execNoAuth("");
getCategoriesRequest=new GetCatalogCategories(null)
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<CatalogCategory> result){
getCategoriesRequest=null;
CatalogCategory all=new CatalogCategory();
all.category="all";
categories.add(all);
result.stream().sorted(Comparator.comparingInt((CatalogCategory cc)->cc.serversCount).reversed()).forEach(categories::add);
updateCategories();
}
@Override
public void onError(ErrorResponse error){
getCategoriesRequest=null;
error.showToast(getActivity());
CatalogCategory all=new CatalogCategory();
all.category="all";
categories.add(all);
updateCategories();
}
})
.execNoAuth("");
}
private void updateCategories(){
// categoriesList.removeAllTabs();
// for(CatalogCategory cat:categories){
// int titleRes=getTitleForCategory(cat.category);
// TabLayout.Tab tab=categoriesList.newTab().setText(titleRes!=0 ? getString(titleRes) : cat.category).setCustomView(R.layout.item_instance_category);
// ImageView emoji=tab.getCustomView().findViewById(R.id.emoji);
// emoji.setImageResource(getEmojiForCategory(cat.category));
// categoriesList.addTab(tab);
// }
}
@Override
public void onDestroy(){
super.onDestroy();
if(getCategoriesRequest!=null)
getCategoriesRequest.cancel();
}
@Override
protected RecyclerView.Adapter getAdapter(){
View headerView=new View(getActivity());
headerView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1));
mergeAdapter=new MergeRecyclerAdapter();
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
return mergeAdapter;
return adapter=new InstancesAdapter();
}
@SuppressLint("ClickableViewAccessibility")
@ -222,7 +173,16 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
setStatusBarColor(0);
topBar=view.findViewById(R.id.top_bar);
list.addOnScrollListener(new ElevationOnScrollListener(null, topBar, buttonBar));
list.addOnScrollListener(new ElevationOnScrollListener(null, topBar));
if(buttonBar.getBackground() instanceof LayerDrawable ld){
ld=(LayerDrawable) ld.mutate();
buttonBar.setBackground(ld);
Drawable overlay=ld.findDrawableByLayerId(R.id.color_overlay);
if(overlay!=null){
overlay.setAlpha(20);
}
}
buttonBar.setElevation(V.dp(3));
searchEdit=view.findViewById(R.id.search_edit);
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
@ -572,6 +532,9 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
filteredData.add(instance);
}
}
setEmptyText(getString(R.string.no_servers_found, currentSearchQuery));
}else{
setEmptyText("");
}
}else{
for(CatalogInstance instance:data){
@ -591,27 +554,29 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
}
}
}
DiffUtil.calculateDiff(new DiffUtil.Callback(){
@Override
public int getOldListSize(){
return prevData.size();
}
UiUtils.updateRecyclerViewKeepingAbsoluteScrollPosition(list, ()->{
DiffUtil.calculateDiff(new DiffUtil.Callback(){
@Override
public int getOldListSize(){
return prevData.size();
}
@Override
public int getNewListSize(){
return filteredData.size();
}
@Override
public int getNewListSize(){
return filteredData.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition){
return prevData.get(oldItemPosition)==filteredData.get(newItemPosition);
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition){
return prevData.get(oldItemPosition)==filteredData.get(newItemPosition);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition){
return prevData.get(oldItemPosition)==filteredData.get(newItemPosition);
}
}).dispatchUpdatesTo(adapter);
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition){
return prevData.get(oldItemPosition)==filteredData.get(newItemPosition);
}
}).dispatchUpdatesTo(adapter);
});
}
@Override

View File

@ -1,12 +1,11 @@
package org.joinmastodon.android.fragments.onboarding;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.view.ViewGroup;
@ -65,7 +64,7 @@ public class InstanceChooserLoginFragment extends InstanceCatalogFragment{
protected void updateFilteredList(){
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
filteredData.clear();
if(currentSearchQuery.length()>0){
if(!TextUtils.isEmpty(currentSearchQuery)){
boolean foundExactMatch=false;
for(CatalogInstance inst:data){
if(inst.normalizedDomain.contains(currentSearchQuery)){
@ -74,9 +73,16 @@ public class InstanceChooserLoginFragment extends InstanceCatalogFragment{
foundExactMatch=true;
}
}
if(!foundExactMatch)
if(!foundExactMatch && currentSearchQuery.indexOf('.')!=-1)
filteredData.add(0, fakeInstance);
}
if(filteredData.isEmpty()){
for(CatalogInstance inst:data){
if(inst.normalizedDomain.equals("mastodon.social") || inst.normalizedDomain.equals("mastodon.online")){
filteredData.add(inst);
}
}
}
UiUtils.updateList(prevData, filteredData, list, adapter, Objects::equals);
for(int i=0;i<list.getChildCount();i++){
list.getChildAt(i).invalidateOutline();
@ -96,6 +102,7 @@ public class InstanceChooserLoginFragment extends InstanceCatalogFragment{
public void onSuccess(List<CatalogInstance> result){
data.clear();
data.addAll(sortInstances(result));
updateFilteredList();
}
@Override
@ -112,6 +119,9 @@ public class InstanceChooserLoginFragment extends InstanceCatalogFragment{
Toolbar toolbar=getToolbar();
toolbar.setElevation(0);
toolbar.setBackground(null);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
toolbar.setContentInsetStartWithNavigation(V.dp(80));
}
}
@Override

View File

@ -50,6 +50,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import androidx.annotation.Nullable;
@ -62,6 +63,7 @@ import me.grishka.appkit.views.FragmentRootLinearLayout;
public class SignupFragment extends ToolbarFragment{
private static final String TAG="SignupFragment";
private final Pattern emailRegex=Pattern.compile("^[^@]+@[^@]+\\.[^@]{2,}$");
private Instance instance;
@ -97,6 +99,7 @@ public class SignupFragment extends ToolbarFragment{
View view=inflater.inflate(R.layout.fragment_onboarding_signup, container, false);
TextView domain=view.findViewById(R.id.domain);
TextView atSign=view.findViewById(R.id.at_sign);
displayName=view.findViewById(R.id.display_name);
username=view.findViewById(R.id.username);
email=view.findViewById(R.id.email);
@ -118,7 +121,7 @@ public class SignupFragment extends ToolbarFragment{
@Override
public boolean onPreDraw(){
username.getViewTreeObserver().removeOnPreDrawListener(this);
username.setPadding(username.getPaddingLeft(), username.getPaddingTop(), domain.getWidth(), username.getPaddingBottom());
username.setPadding(atSign.getWidth(), username.getPaddingTop(), domain.getWidth(), username.getPaddingBottom());
return true;
}
});
@ -145,6 +148,10 @@ public class SignupFragment extends ToolbarFragment{
reasonExplain.setVisibility(View.GONE);
}
password.setOnFocusChangeListener(this::onPasswordFieldFocusChange);
passwordConfirm.setOnFocusChangeListener(this::onPasswordFieldFocusChange);
email.setOnFocusChangeListener(this::onEmailFieldFocusChange);
return view;
}
@ -281,34 +288,44 @@ public class SignupFragment extends ToolbarFragment{
.exec(instance.uri, apiToken);
}
private CharSequence makeLinkInErrorMessage(String source, LinkSpan.OnLinkClickListener onClick){
SpannableStringBuilder ssb=new SpannableStringBuilder();
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
private int spanStart;
@Override
public void head(Node node, int depth){
if(node instanceof TextNode tn){
ssb.append(tn.text());
}else if(node instanceof Element){
spanStart=ssb.length();
}
}
@Override
public void tail(Node node, int depth){
if(node instanceof Element){
ssb.setSpan(new LinkSpan("", onClick, LinkSpan.Type.CUSTOM, null, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
});
return ssb;
}
private CharSequence getErrorDescription(MastodonDetailedErrorResponse.FieldError error, String fieldName){
return switch(fieldName){
case "email" -> switch(error.error){
case "ERR_BLOCKED" -> {
String emailAddr=email.getText().toString();
String s=getResources().getString(R.string.signup_email_domain_blocked, TextUtils.htmlEncode(instance.uri), TextUtils.htmlEncode(emailAddr.substring(emailAddr.lastIndexOf('@')+1)));
SpannableStringBuilder ssb=new SpannableStringBuilder();
Jsoup.parseBodyFragment(s).body().traverse(new NodeVisitor(){
private int spanStart;
@Override
public void head(Node node, int depth){
if(node instanceof TextNode tn){
ssb.append(tn.text());
}else if(node instanceof Element){
spanStart=ssb.length();
}
}
@Override
public void tail(Node node, int depth){
if(node instanceof Element){
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
});
yield ssb;
yield makeLinkInErrorMessage(s, this::onGoBackLinkClick);
}
case "ERR_INVALID" -> getString(R.string.signup_email_invalid);
case "ERR_TAKEN" -> makeLinkInErrorMessage(getString(R.string.signup_email_taken), this::onForgotPasswordLinkClick);
default -> error.description;
};
case "username" -> switch(error.error){
case "ERR_TAKEN" -> makeLinkInErrorMessage(getString(R.string.signup_username_taken), this::onGoBackLinkClick);
default -> error.description;
};
default -> error.description;
@ -345,7 +362,9 @@ public class SignupFragment extends ToolbarFragment{
}
private void updateButtonState(){
btn.setEnabled(username.length()>0 && email.length()>0 && email.getText().toString().contains("@") && password.length()>=8 && passwordConfirm.length()>=8 && (!instance.approvalRequired || reason.length()>0));
btn.setEnabled(username.length()>0 && email.length()>0 && emailRegex.matcher(email.getText()).find()
&& password.length()>=8 && passwordConfirm.length()>=8 && password.getText().toString().equals(passwordConfirm.getText().toString())
&& (!instance.approvalRequired || reason.length()>0));
}
private void createAppAndGetToken(){
@ -406,6 +425,24 @@ public class SignupFragment extends ToolbarFragment{
Nav.finish(this);
}
private void onForgotPasswordLinkClick(LinkSpan span){
UiUtils.launchWebBrowser(getActivity(), "https://"+instance.uri+"/auth/password/new");
}
private void onPasswordFieldFocusChange(View v, boolean hasFocus){
if(hasFocus || password.length()==0 || passwordConfirm.length()==0)
return;
if(!password.getText().toString().equals(passwordConfirm.getText().toString())){
passwordConfirmWrap.setErrorState(getString(R.string.signup_passwords_dont_match));
}
}
private void onEmailFieldFocusChange(View v, boolean hasFocus){
if(!hasFocus && email.length()>0 && !emailRegex.matcher(email.getText()).find()){
emailWrap.setErrorState(getString(R.string.signup_email_invalid));
}
}
private class ErrorClearingListener implements TextWatcher{
public final EditText editText;

View File

@ -1040,4 +1040,20 @@ public class UiUtils{
button.setTextColor(origColor);
}
}
public static void updateRecyclerViewKeepingAbsoluteScrollPosition(RecyclerView rv, Runnable onUpdate){
int topItem=-1;
int topItemOffset=0;
if(rv.getChildCount()>0){
View item=rv.getChildAt(0);
topItem=rv.getChildAdapterPosition(item);
topItemOffset=item.getTop();
}
onUpdate.run();
int newCount=rv.getAdapter().getItemCount();
if(newCount>=topItem){
rv.scrollToPosition(topItem);
rv.scrollBy(0, -topItemOffset);
}
}
}

View File

@ -158,6 +158,9 @@ public class FloatingHintEditTextLayout extends FrameLayout implements CustomVie
}else{
transY=edit.getHeight()/2f-edit.getLineHeight()/2f+(edit.getTop()-label.getTop())-(label.getHeight()/2f-label.getLineHeight()/2f);
}
int labelX=label.getLeft();
int editX=edit.getLeft()+edit.getPaddingLeft();
float xOffset=editX-labelX;
AnimatorSet anim=new AnimatorSet();
if(hintVisible){
@ -166,6 +169,7 @@ public class FloatingHintEditTextLayout extends FrameLayout implements CustomVie
ObjectAnimator.ofFloat(label, SCALE_X, scale),
ObjectAnimator.ofFloat(label, SCALE_Y, scale),
ObjectAnimator.ofFloat(label, TRANSLATION_Y, transY),
ObjectAnimator.ofFloat(label, TRANSLATION_X, xOffset),
ObjectAnimator.ofFloat(FloatingHintEditTextLayout.this, "animProgress", 0f)
);
edit.setHintTextColor(0);
@ -173,11 +177,13 @@ public class FloatingHintEditTextLayout extends FrameLayout implements CustomVie
label.setScaleX(scale);
label.setScaleY(scale);
label.setTranslationY(transY);
label.setTranslationX(xOffset);
anim.playTogether(
ObjectAnimator.ofFloat(edit, TRANSLATION_Y, offsetY),
ObjectAnimator.ofFloat(label, SCALE_X, 1f),
ObjectAnimator.ofFloat(label, SCALE_Y, 1f),
ObjectAnimator.ofFloat(label, TRANSLATION_Y, 0f),
ObjectAnimator.ofFloat(label, TRANSLATION_X, 0f),
ObjectAnimator.ofFloat(FloatingHintEditTextLayout.this, "animProgress", 1f)
);
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorM3OnSurfaceVariant" android:alpha="0.4"/>
</selector>

View File

@ -39,14 +39,15 @@
android:layout_height="56dp"
android:background="@drawable/bg_m3_filled_text_field"
android:paddingHorizontal="16dp"
android:textColorHint="?colorM3OnSurfaceVariant"
android:textColorHint="@color/m3_on_surface_variant_alpha40"
android:textColor="?colorM3OnSurface"
android:gravity="start|bottom"
android:paddingBottom="8dp"
android:singleLine="true"
android:inputType="textUri"
android:textAppearance="@style/m3_body_large"
android:hint="example.social/invite/AbC123"/>
android:hint="example.social/invite/AbC123"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/label"

View File

@ -56,10 +56,12 @@
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
android:id="@+id/username_wrap"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_height="wrap_content"
android:paddingTop="4dp"
android:paddingBottom="12dp"
app:labelTextColor="@color/m3_outlined_text_field_label"
android:foreground="@drawable/bg_m3_outlined_text_field">
android:foreground="@drawable/bg_m3_outlined_text_field"
tools:ignore="RtlHardcoded">
<EditText
android:id="@+id/username"
@ -91,6 +93,20 @@
android:textColor="?colorM3OnSurface"
tools:text="\@mastodon.social"/>
<TextView
android:id="@+id/at_sign"
android:layout_width="wrap_content"
android:layout_height="56dp"
android:layout_gravity="left|top"
android:layout_marginLeft="56dp"
android:layout_marginTop="8dp"
android:paddingLeft="8dp"
android:paddingRight="2dp"
android:textAppearance="@style/m3_body_large"
android:gravity="center_vertical"
android:textColor="?colorM3OnSurface"
android:text="\@"/>
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
@ -191,19 +207,6 @@
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="56dp"
android:layout_marginEnd="20dp"
android:layout_marginTop="-8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textAppearance="@style/m3_body_small"
android:textColor="?colorM3OnSurfaceVariant"
android:text="@string/password_note"/>
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
android:id="@+id/reason_wrap"
android:layout_width="match_parent"

View File

@ -10,7 +10,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_body_large"
android:paddingStart="56dp"
android:paddingStart="80dp"
android:paddingEnd="24dp"
android:textColor="?colorM3OnSurface"
android:text="@string/login_subtitle"/>
@ -38,7 +38,7 @@
android:textColorHint="?colorM3OnSurfaceVariant"
android:inputType="textUri"
android:importantForAutofill="no"
android:paddingStart="48dp"
android:paddingStart="64dp"
android:layout_marginEnd="52dp"
android:drawablePadding="16dp"
android:textAppearance="@style/m3_body_large"
@ -47,6 +47,7 @@
<ImageView
android:layout_width="44dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:layout_gravity="start|center_vertical"
android:scaleType="center"
android:importantForAccessibility="no"

View File

@ -157,7 +157,6 @@
<string name="email">Email</string>
<string name="password">Password</string>
<string name="confirm_password">Confirm password</string>
<string name="password_note">Include capital letters, special characters, and numbers to increase your password strength.</string>
<string name="category_general">General</string>
<string name="confirm_email_title">Check your inbox</string>
<!-- %s is the email address -->
@ -321,9 +320,9 @@
<string name="login_subtitle">Log in with the server where you created your account.</string>
<string name="server_url">Server URL</string>
<string name="server_filter_any_language">Any language</string>
<string name="server_filter_instant_signup">Instant sign-up</string>
<string name="server_filter_instant_signup">Instant sign up</string>
<string name="server_filter_manual_review">Manual review</string>
<string name="server_filter_any_signup_speed">Any sign-up speed</string>
<string name="server_filter_any_signup_speed">Any sign up speed</string>
<string name="server_filter_region_europe">Europe</string>
<string name="server_filter_region_north_america">North America</string>
<string name="server_filter_region_south_america">South America</string>
@ -759,4 +758,8 @@
<string name="connection_timed_out">The request timed out. Check your connection and try again?</string>
<string name="server_error">Something went wrong talking with your server. Its probably not your fault. Try again?</string>
<string name="not_found">It couldve been deleted, or maybe it never existed at all.</string>
<string name="no_servers_found">No servers found for “%s”</string>
<string name="signup_username_taken">This username is taken. Try a different one or &lt;a>pick a different server&lt;/a>.</string>
<string name="signup_email_invalid">That doesnt look like a valid email address.</string>
<string name="signup_email_taken">Email address is already in use. Did you &lt;a>forget your password&lt;/a>?</string>
</resources>