VQA fixes part 2

This commit is contained in:
Grishka 2024-06-08 18:58:04 +03:00
parent bf44f7ef13
commit c3e48d20f3
13 changed files with 87 additions and 13 deletions

View File

@ -13,7 +13,7 @@ android {
applicationId "org.joinmastodon.android" applicationId "org.joinmastodon.android"
minSdk 23 minSdk 23
targetSdk 33 targetSdk 33
versionCode 97 versionCode 99
versionName "2.5.0" versionName "2.5.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@ -61,6 +61,7 @@ public class MainActivity extends FragmentStackActivity{
@Override @Override
protected void onNewIntent(Intent intent){ protected void onNewIntent(Intent intent){
super.onNewIntent(intent); super.onNewIntent(intent);
setIntent(intent);
if(intent.getBooleanExtra("fromNotification", false)){ if(intent.getBooleanExtra("fromNotification", false)){
String accountID=intent.getStringExtra("accountID"); String accountID=intent.getStringExtra("accountID");
AccountSession accountSession; AccountSession accountSession;
@ -85,6 +86,8 @@ public class MainActivity extends FragmentStackActivity{
showCompose(); showCompose();
}else if(Intent.ACTION_VIEW.equals(intent.getAction())){ }else if(Intent.ACTION_VIEW.equals(intent.getAction())){
handleURL(intent.getData(), null); handleURL(intent.getData(), null);
}else if(intent.getBooleanExtra("explore", false)){
restartHomeFragment();
}/*else if(intent.hasExtra(PackageInstaller.EXTRA_STATUS) && GithubSelfUpdater.needSelfUpdating()){ }/*else if(intent.hasExtra(PackageInstaller.EXTRA_STATUS) && GithubSelfUpdater.needSelfUpdating()){
GithubSelfUpdater.getInstance().handleIntentFromInstaller(intent, this); GithubSelfUpdater.getInstance().handleIntentFromInstaller(intent, this);
}*/ }*/
@ -211,6 +214,8 @@ public class MainActivity extends FragmentStackActivity{
} }
}else if(intent.getBooleanExtra("compose", false)){ }else if(intent.getBooleanExtra("compose", false)){
showCompose(); showCompose();
}else if(intent.getBooleanExtra("explore", false) && fragment instanceof HomeFragment hf){
getWindow().getDecorView().post(()->hf.setCurrentTab(R.id.tab_search));
}else if(Intent.ACTION_VIEW.equals(intent.getAction())){ }else if(Intent.ACTION_VIEW.equals(intent.getAction())){
handleURL(intent.getData(), null); handleURL(intent.getData(), null);
}else{ }else{
@ -218,4 +223,10 @@ public class MainActivity extends FragmentStackActivity{
} }
} }
} }
public Fragment getTopmostFragment(){
if(fragmentContainers.isEmpty())
return null;
return getFragmentManager().findFragmentById(fragmentContainers.get(fragmentContainers.size()-1).getId());
}
} }

View File

@ -13,11 +13,13 @@ import java.util.List;
public class GetCatalogInstances extends MastodonAPIRequest<List<CatalogInstance>>{ public class GetCatalogInstances extends MastodonAPIRequest<List<CatalogInstance>>{
private String lang, category; private String lang, category;
private boolean includeClosedSignups;
public GetCatalogInstances(String lang, String category){ public GetCatalogInstances(String lang, String category, boolean includeClosedSignups){
super(HttpMethod.GET, null, new TypeToken<>(){}); super(HttpMethod.GET, null, new TypeToken<>(){});
this.lang=lang; this.lang=lang;
this.category=category; this.category=category;
this.includeClosedSignups=includeClosedSignups;
} }
@Override @Override
@ -30,6 +32,8 @@ public class GetCatalogInstances extends MastodonAPIRequest<List<CatalogInstance
builder.appendQueryParameter("language", lang); builder.appendQueryParameter("language", lang);
if(!TextUtils.isEmpty(category)) if(!TextUtils.isEmpty(category))
builder.appendQueryParameter("category", category); builder.appendQueryParameter("category", category);
if(includeClosedSignups)
builder.appendQueryParameter("registrations", "all");
return builder.build(); return builder.build();
} }
} }

View File

@ -425,7 +425,7 @@ public class AccountSessionManager{
ShortcutManager sm=MastodonApp.context.getSystemService(ShortcutManager.class); ShortcutManager sm=MastodonApp.context.getSystemService(ShortcutManager.class);
if((sm.getDynamicShortcuts().isEmpty() || BuildConfig.DEBUG) && !sessions.isEmpty()){ if((sm.getDynamicShortcuts().isEmpty() || BuildConfig.DEBUG) && !sessions.isEmpty()){
// There are no shortcuts, but there are accounts. Add a compose shortcut. // There are no shortcuts, but there are accounts. Add a compose shortcut.
ShortcutInfo info=new ShortcutInfo.Builder(MastodonApp.context, "compose") ShortcutInfo compose=new ShortcutInfo.Builder(MastodonApp.context, "compose")
.setActivity(ComponentName.createRelative(MastodonApp.context, MainActivity.class.getName())) .setActivity(ComponentName.createRelative(MastodonApp.context, MainActivity.class.getName()))
.setShortLabel(MastodonApp.context.getString(R.string.new_post)) .setShortLabel(MastodonApp.context.getString(R.string.new_post))
.setIcon(Icon.createWithResource(MastodonApp.context, R.mipmap.ic_shortcut_compose)) .setIcon(Icon.createWithResource(MastodonApp.context, R.mipmap.ic_shortcut_compose))
@ -433,12 +433,20 @@ public class AccountSessionManager{
.setAction(Intent.ACTION_MAIN) .setAction(Intent.ACTION_MAIN)
.putExtra("compose", true)) .putExtra("compose", true))
.build(); .build();
sm.setDynamicShortcuts(Collections.singletonList(info)); ShortcutInfo explore=new ShortcutInfo.Builder(MastodonApp.context, "explore")
.setActivity(ComponentName.createRelative(MastodonApp.context, MainActivity.class.getName()))
.setShortLabel(MastodonApp.context.getString(R.string.tab_search))
.setIcon(Icon.createWithResource(MastodonApp.context, R.mipmap.ic_shortcut_explore))
.setIntent(new Intent(MastodonApp.context, MainActivity.class)
.setAction(Intent.ACTION_MAIN)
.putExtra("explore", true))
.build();
sm.setDynamicShortcuts(List.of(compose, explore));
}else if(sessions.isEmpty()){ }else if(sessions.isEmpty()){
// There are shortcuts, but no accounts. Disable existing shortcuts. // There are shortcuts, but no accounts. Disable existing shortcuts.
sm.disableShortcuts(Collections.singletonList("compose"), MastodonApp.context.getString(R.string.err_not_logged_in)); sm.disableShortcuts(List.of("compose", "explore"), MastodonApp.context.getString(R.string.err_not_logged_in));
}else{ }else{
sm.enableShortcuts(Collections.singletonList("compose")); sm.enableShortcuts(List.of("compose", "explore"));
} }
} }

View File

@ -59,6 +59,9 @@ import java.util.stream.Collectors;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
@ -79,6 +82,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected HashMap<String, Relationship> relationships=new HashMap<>(); protected HashMap<String, Relationship> relationships=new HashMap<>();
protected Rect tmpRect=new Rect(); protected Rect tmpRect=new Rect();
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView); protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
private SpringAnimation listShakeAnimation;
public BaseStatusListFragment(){ public BaseStatusListFragment(){
super(20); super(20);
@ -675,6 +679,17 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected void onModifyItemViewHolder(BindableViewHolder<StatusDisplayItem> holder){} protected void onModifyItemViewHolder(BindableViewHolder<StatusDisplayItem> holder){}
public void shakeListView(){
if(listShakeAnimation!=null)
listShakeAnimation.cancel();
SpringAnimation anim=new SpringAnimation(list, DynamicAnimation.TRANSLATION_X, 0);
anim.setStartVelocity(V.dp(-500));
anim.getSpring().setStiffness(500).setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
listShakeAnimation=anim;
anim.addEndListener((animation, canceled, value, velocity)->listShakeAnimation=null);
anim.start();
}
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{ protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
public DisplayItemsAdapter(){ public DisplayItemsAdapter(){

View File

@ -275,4 +275,8 @@ public class HashtagTimelineFragment extends StatusListFragment{
}) })
.exec(accountID); .exec(accountID);
} }
public String getHashtagName(){
return hashtagName;
}
} }

View File

@ -1149,7 +1149,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
startImagePicker(COVER_RESULT); startImagePicker(COVER_RESULT);
}else{ }else{
Drawable drawable=cover.getDrawable(); Drawable drawable=cover.getDrawable();
if(drawable==null || drawable instanceof ColorDrawable) if(drawable==null || drawable instanceof ColorDrawable || account.headerStatic.endsWith("/missing.png"))
return; return;
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.header, drawable), 0, currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.header, drawable), 0,
null, accountID, new SingleImagePhotoViewerListener(cover, cover, null, this, ()->currentPhotoViewer=null, ()->drawable, ()->avatarBorder.setTranslationZ(2), ()->avatarBorder.setTranslationZ(0))); null, accountID, new SingleImagePhotoViewerListener(cover, cover, null, this, ()->currentPhotoViewer=null, ()->drawable, ()->avatarBorder.setTranslationZ(2), ()->avatarBorder.setTranslationZ(0)));

View File

@ -37,7 +37,6 @@ import org.joinmastodon.android.model.catalog.CatalogInstance;
import org.joinmastodon.android.ui.BetterItemAnimator; import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.text.HtmlParser; 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.SimpleTextWatcher;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.FilterChipView; import org.joinmastodon.android.ui.views.FilterChipView;
@ -61,8 +60,6 @@ import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.OnBackPressedListener; import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.utils.BindableViewHolder; import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
@ -106,7 +103,7 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetCatalogInstances(null, null) currentRequest=new GetCatalogInstances(null, null, false)
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(List<CatalogInstance> result){ public void onSuccess(List<CatalogInstance> result){

View File

@ -96,7 +96,7 @@ public class InstanceChooserLoginFragment extends InstanceCatalogFragment{
private void loadAutocompleteServers(){ private void loadAutocompleteServers(){
loadedAutocomplete=true; loadedAutocomplete=true;
new GetCatalogInstances(null, null) new GetCatalogInstances(null, null, true)
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(List<CatalogInstance> result){ public void onSuccess(List<CatalogInstance> result){

View File

@ -7,6 +7,7 @@ import com.google.gson.annotations.SerializedName;
import org.joinmastodon.android.api.AllFieldsAreRequired; import org.joinmastodon.android.api.AllFieldsAreRequired;
import org.joinmastodon.android.api.ObjectValidationException; import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.model.BaseModel; import org.joinmastodon.android.model.BaseModel;
import java.net.IDN; import java.net.IDN;
@ -15,14 +16,18 @@ import java.util.List;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
@AllFieldsAreRequired
public class CatalogInstance extends BaseModel{ public class CatalogInstance extends BaseModel{
@RequiredField
public String domain; public String domain;
@RequiredField
public String version; public String version;
@RequiredField
public String description; public String description;
@RequiredField
public List<String> languages; public List<String> languages;
@SerializedName("region") @SerializedName("region")
private String _region; private String _region;
@RequiredField
public List<String> categories; public List<String> categories;
public String proxiedThumbnail; public String proxiedThumbnail;
public int totalUsers; public int totalUsers;

View File

@ -52,6 +52,7 @@ import android.widget.Toast;
import org.joinmastodon.android.E; import org.joinmastodon.android.E;
import org.joinmastodon.android.FileProvider; import org.joinmastodon.android.FileProvider;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked; import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
@ -368,6 +369,8 @@ public class UiUtils{
} }
public static void openHashtagTimeline(Context context, String accountID, Hashtag hashtag){ public static void openHashtagTimeline(Context context, String accountID, Hashtag hashtag){
if(checkIfAlreadyDisplayingSameHashtag(context, hashtag.name))
return;
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putParcelable("hashtag", Parcels.wrap(hashtag)); args.putParcelable("hashtag", Parcels.wrap(hashtag));
@ -375,12 +378,22 @@ public class UiUtils{
} }
public static void openHashtagTimeline(Context context, String accountID, String hashtag){ public static void openHashtagTimeline(Context context, String accountID, String hashtag){
if(checkIfAlreadyDisplayingSameHashtag(context, hashtag))
return;
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putString("hashtagName", hashtag); args.putString("hashtagName", hashtag);
Nav.go((Activity)context, HashtagTimelineFragment.class, args); Nav.go((Activity)context, HashtagTimelineFragment.class, args);
} }
private static boolean checkIfAlreadyDisplayingSameHashtag(Context context, String hashtag){
if(context instanceof MainActivity ma && ma.getTopmostFragment() instanceof HashtagTimelineFragment htf && htf.getHashtagName().equalsIgnoreCase(hashtag)){
htf.shakeListView();
return true;
}
return false;
}
public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed){ public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed){
showConfirmationAlert(context, context.getString(title), message==0 ? null : context.getString(message), context.getString(confirmButton), onConfirmed); showConfirmationAlert(context, context.getString(title), message==0 ? null : context.getString(message), context.getString(confirmButton), onConfirmed);
} }

View File

@ -0,0 +1,7 @@
<vector android:height="108dp"
android:viewportHeight="56" android:viewportWidth="56"
android:width="108dp" xmlns:android="http://schemas.android.com/apk/res/android">
<group android:translateX="16" android:translateY="16">
<path android:fillColor="@color/shortcut_icon_foreground" android:pathData="M19.6,21 L13.3,14.7Q12.55,15.3 11.575,15.65Q10.6,16 9.5,16Q6.775,16 4.888,14.113Q3,12.225 3,9.5Q3,6.775 4.888,4.887Q6.775,3 9.5,3Q12.225,3 14.113,4.887Q16,6.775 16,9.5Q16,10.6 15.65,11.575Q15.3,12.55 14.7,13.3L21,19.6ZM9.5,14Q11.375,14 12.688,12.688Q14,11.375 14,9.5Q14,7.625 12.688,6.312Q11.375,5 9.5,5Q7.625,5 6.312,6.312Q5,7.625 5,9.5Q5,11.375 6.312,12.688Q7.625,14 9.5,14Z"/>
</group>
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background>
<shape>
<solid android:color="@color/shortcut_icon_background"/>
<size android:width="108dp" android:height="108dp"/>
</shape>
</background>
<foreground android:drawable="@drawable/ic_explore_foreground"/>
</adaptive-icon>