diff --git a/mastodon/build.gradle b/mastodon/build.gradle index a0996dca..19b0051d 100644 --- a/mastodon/build.gradle +++ b/mastodon/build.gradle @@ -9,8 +9,8 @@ android { applicationId "org.joinmastodon.android" minSdk 23 targetSdk 33 - versionCode 60 - versionName "2.0.0" + versionCode 61 + versionName "2.0.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "da-rDK", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fa-rIR", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "ig-rNG", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "my-rMM", "nl-rNL", "no-rNO", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "ur-rIN", "vi-rVN", "zh-rCN", "zh-rTW" } diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java index 36071d05..a4063f08 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -86,6 +87,9 @@ public class MastodonAPIController{ synchronized(req){ req.okhttpCall=call; } + if(req.timeout>0){ + call.timeout().timeout(req.timeout, TimeUnit.MILLISECONDS); + } if(BuildConfig.DEBUG) Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] Sending request: "+hreq); diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java index ad9a6254..a7d1e77c 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java @@ -45,6 +45,7 @@ public abstract class MastodonAPIRequest extends APIRequest{ Token token; boolean canceled; Map headers; + long timeout; private ProgressDialog progressDialog; protected boolean removeUnsupportedItems; @@ -127,6 +128,10 @@ public abstract class MastodonAPIRequest extends APIRequest{ headers.put(key, value); } + protected void setTimeout(long timeout){ + this.timeout=timeout; + } + protected String getPathPrefix(){ return "/api/v1"; } diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/catalog/GetCatalogDefaultInstances.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/catalog/GetCatalogDefaultInstances.java new file mode 100644 index 00000000..238ce2d8 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/catalog/GetCatalogDefaultInstances.java @@ -0,0 +1,22 @@ +package org.joinmastodon.android.api.requests.catalog; + +import android.net.Uri; + +import com.google.gson.reflect.TypeToken; + +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.catalog.CatalogDefaultInstance; + +import java.util.List; + +public class GetCatalogDefaultInstances extends MastodonAPIRequest>{ + public GetCatalogDefaultInstances(){ + super(HttpMethod.GET, null, new TypeToken<>(){}); + setTimeout(500); + } + + @Override + public Uri getURL(){ + return Uri.parse("https://api.joinmastodon.org/default-servers"); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java index 6b609745..12c6e0af 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java @@ -7,20 +7,27 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowInsets; -import android.widget.Button; +import android.widget.ProgressBar; import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.R; +import org.joinmastodon.android.api.requests.catalog.GetCatalogDefaultInstances; import org.joinmastodon.android.api.requests.instance.GetInstance; import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment; import org.joinmastodon.android.fragments.onboarding.InstanceChooserLoginFragment; import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment; import org.joinmastodon.android.model.Instance; +import org.joinmastodon.android.model.catalog.CatalogDefaultInstance; import org.joinmastodon.android.ui.InterpolatingMotionEffect; +import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.utils.UiUtils; +import org.joinmastodon.android.ui.views.ProgressBarButton; import org.joinmastodon.android.ui.views.SizeListenerFrameLayout; import org.parceler.Parcels; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + import androidx.annotation.Nullable; import me.grishka.appkit.Nav; import me.grishka.appkit.api.Callback; @@ -37,11 +44,16 @@ public class SplashFragment extends AppKitFragment{ private View artContainer, blueFill, greenFill; private InterpolatingMotionEffect motionEffect; private View artClouds, artPlaneElephant, artRightHill, artLeftHill, artCenterHill; + private ProgressBarButton defaultServerButton; + private ProgressBar defaultServerProgress; + private String chosenDefaultServer=DEFAULT_SERVER; + private boolean loadingDefaultServer; @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); motionEffect=new InterpolatingMotionEffect(MastodonApp.context); + loadAndChooseDefaultServer(); } @Nullable @@ -50,9 +62,14 @@ public class SplashFragment extends AppKitFragment{ contentView=(SizeListenerFrameLayout) inflater.inflate(R.layout.fragment_splash, container, false); contentView.findViewById(R.id.btn_get_started).setOnClickListener(this::onButtonClick); contentView.findViewById(R.id.btn_log_in).setOnClickListener(this::onButtonClick); - Button joinDefault=contentView.findViewById(R.id.btn_join_default_server); - joinDefault.setText(getString(R.string.join_default_server, DEFAULT_SERVER)); - joinDefault.setOnClickListener(this::onJoinDefaultServerClick); + defaultServerButton=contentView.findViewById(R.id.btn_join_default_server); + defaultServerButton.setText(getString(R.string.join_default_server, chosenDefaultServer)); + defaultServerButton.setOnClickListener(this::onJoinDefaultServerClick); + defaultServerProgress=contentView.findViewById(R.id.action_progress); + if(loadingDefaultServer){ + defaultServerButton.setTextVisible(false); + defaultServerProgress.setVisibility(View.VISIBLE); + } contentView.findViewById(R.id.btn_learn_more).setOnClickListener(this::onLearnMoreClick); artClouds=contentView.findViewById(R.id.art_clouds); @@ -96,12 +113,22 @@ public class SplashFragment extends AppKitFragment{ } private void onJoinDefaultServerClick(View v){ + if(loadingDefaultServer) + return; new GetInstance() .setCallback(new Callback<>(){ @Override public void onSuccess(Instance result){ if(getActivity()==null) return; + if(!result.registrations){ + new M3AlertDialogBuilder(getActivity()) + .setTitle(R.string.error) + .setMessage(R.string.instance_signup_closed) + .setPositiveButton(R.string.ok, null) + .show(); + return; + } Bundle args=new Bundle(); args.putParcelable("instance", Parcels.wrap(result)); Nav.go(getActivity(), InstanceRulesFragment.class, args); @@ -115,7 +142,7 @@ public class SplashFragment extends AppKitFragment{ } }) .wrapProgress(getActivity(), R.string.loading_instance, true) - .execNoAuth(DEFAULT_SERVER); + .execNoAuth(chosenDefaultServer); } private void onLearnMoreClick(View v){ @@ -168,4 +195,54 @@ public class SplashFragment extends AppKitFragment{ super.onHidden(); motionEffect.deactivate(); } + + private void loadAndChooseDefaultServer(){ + loadingDefaultServer=true; + new GetCatalogDefaultInstances() + .setCallback(new Callback<>(){ + @Override + public void onSuccess(List result){ + if(result.isEmpty()){ + setChosenDefaultServer(DEFAULT_SERVER); + return; + } + float sum=0f; + for(CatalogDefaultInstance inst:result){ + sum+=inst.weight; + } + if(sum<=0) + sum=1f; + for(CatalogDefaultInstance inst:result){ + inst.weight/=sum; + } + float rand=ThreadLocalRandom.current().nextFloat(); + float prev=0f; + for(CatalogDefaultInstance inst:result){ + if(rand>=prev && rand -