Support for invite links (AND-90)
This commit is contained in:
parent
5d7c37262e
commit
48f9aabaf7
|
@ -9,7 +9,7 @@ android {
|
|||
applicationId "org.joinmastodon.android"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 81
|
||||
versionCode 82
|
||||
versionName "2.2.4"
|
||||
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"
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.joinmastodon.android.model.BaseModel;
|
||||
|
||||
public class CheckInviteLink extends MastodonAPIRequest<CheckInviteLink.Response>{
|
||||
public CheckInviteLink(String path){
|
||||
super(HttpMethod.GET, path, Response.class);
|
||||
addHeader("Accept", "application/json");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPathPrefix(){
|
||||
return "";
|
||||
}
|
||||
|
||||
public static class Response extends BaseModel{
|
||||
@RequiredField
|
||||
public String inviteCode;
|
||||
}
|
||||
}
|
|
@ -4,22 +4,23 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
|||
import org.joinmastodon.android.model.Token;
|
||||
|
||||
public class RegisterAccount extends MastodonAPIRequest<Token>{
|
||||
public RegisterAccount(String username, String email, String password, String locale, String reason, String timezone){
|
||||
public RegisterAccount(String username, String email, String password, String locale, String reason, String timezone, String inviteCode){
|
||||
super(HttpMethod.POST, "/accounts", Token.class);
|
||||
setRequestBody(new Body(username, email, password, locale, reason, timezone));
|
||||
setRequestBody(new Body(username, email, password, locale, reason, timezone, inviteCode));
|
||||
}
|
||||
|
||||
private static class Body{
|
||||
public String username, email, password, locale, reason, timeZone;
|
||||
public String username, email, password, locale, reason, timeZone, inviteCode;
|
||||
public boolean agreement=true;
|
||||
|
||||
public Body(String username, String email, String password, String locale, String reason, String timeZone){
|
||||
public Body(String username, String email, String password, String locale, String reason, String timeZone, String inviteCode){
|
||||
this.username=username;
|
||||
this.email=email;
|
||||
this.password=password;
|
||||
this.locale=locale;
|
||||
this.reason=reason;
|
||||
this.timeZone=timeZone;
|
||||
this.inviteCode=inviteCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -11,6 +16,8 @@ import android.widget.ProgressBar;
|
|||
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||
import org.joinmastodon.android.api.requests.accounts.CheckInviteLink;
|
||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogDefaultInstances;
|
||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment;
|
||||
|
@ -20,6 +27,7 @@ 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.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
|
||||
|
@ -48,6 +56,9 @@ public class SplashFragment extends AppKitFragment{
|
|||
private ProgressBar defaultServerProgress;
|
||||
private String chosenDefaultServer=DEFAULT_SERVER;
|
||||
private boolean loadingDefaultServer, loadedDefaultServer;
|
||||
private Uri currentInviteLink;
|
||||
private ProgressDialog instanceLoadingProgress;
|
||||
private String inviteCode;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
|
@ -110,19 +121,65 @@ public class SplashFragment extends AppKitFragment{
|
|||
Bundle extras=new Bundle();
|
||||
boolean isSignup=v.getId()==R.id.btn_get_started;
|
||||
extras.putBoolean("signup", isSignup);
|
||||
extras.putString("defaultServer", chosenDefaultServer);
|
||||
Nav.go(getActivity(), isSignup ? InstanceCatalogSignupFragment.class : InstanceChooserLoginFragment.class, extras);
|
||||
}
|
||||
|
||||
private void onJoinDefaultServerClick(View v){
|
||||
if(loadingDefaultServer)
|
||||
return;
|
||||
instanceLoadingProgress=new ProgressDialog(getActivity());
|
||||
instanceLoadingProgress.setCancelable(false);
|
||||
instanceLoadingProgress.setMessage(getString(R.string.loading_instance));
|
||||
instanceLoadingProgress.show();
|
||||
if(currentInviteLink!=null){
|
||||
new CheckInviteLink(currentInviteLink.getPath())
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(CheckInviteLink.Response result){
|
||||
inviteCode=result.inviteCode;
|
||||
proceedWithServerDomain(currentInviteLink.getHost());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
instanceLoadingProgress.dismiss();
|
||||
instanceLoadingProgress=null;
|
||||
if(error instanceof MastodonErrorResponse mer){
|
||||
switch(mer.httpStatus){
|
||||
case 401 -> new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.expired_invite_link)
|
||||
.setMessage(getString(R.string.expired_clipboard_invite_link_alert, currentInviteLink.getHost(), chosenDefaultServer))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
case 404 -> new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.invalid_invite_link)
|
||||
.setMessage(getString(R.string.invalid_clipboard_invite_link_alert, currentInviteLink.getHost(), chosenDefaultServer))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
default -> error.showToast(getActivity());
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.execNoAuth(currentInviteLink.getHost());
|
||||
return;
|
||||
}
|
||||
proceedWithServerDomain(chosenDefaultServer);
|
||||
}
|
||||
|
||||
private void proceedWithServerDomain(String domain){
|
||||
new GetInstance()
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Instance result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
if(!result.registrations){
|
||||
instanceLoadingProgress.dismiss();
|
||||
instanceLoadingProgress=null;
|
||||
if(!result.registrations && TextUtils.isEmpty(inviteCode)){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.instance_signup_closed)
|
||||
|
@ -132,6 +189,8 @@ public class SplashFragment extends AppKitFragment{
|
|||
}
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(result));
|
||||
if(inviteCode!=null)
|
||||
args.putString("inviteCode", inviteCode);
|
||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||
}
|
||||
|
||||
|
@ -139,11 +198,12 @@ public class SplashFragment extends AppKitFragment{
|
|||
public void onError(ErrorResponse error){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
instanceLoadingProgress.dismiss();
|
||||
instanceLoadingProgress=null;
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.loading_instance, true)
|
||||
.execNoAuth(chosenDefaultServer);
|
||||
.execNoAuth(domain);
|
||||
}
|
||||
|
||||
private void onLearnMoreClick(View v){
|
||||
|
@ -198,9 +258,18 @@ public class SplashFragment extends AppKitFragment{
|
|||
}
|
||||
|
||||
private void loadAndChooseDefaultServer(){
|
||||
loadingDefaultServer=true;
|
||||
defaultServerButton.setTextVisible(false);
|
||||
defaultServerProgress.setVisibility(View.VISIBLE);
|
||||
ClipData clipData=getActivity().getSystemService(ClipboardManager.class).getPrimaryClip();
|
||||
if(clipData!=null && clipData.getItemCount()>0){
|
||||
CharSequence clipText=clipData.getItemAt(0).coerceToText(getActivity());
|
||||
if(HtmlParser.INVITE_LINK_PATTERN.matcher(clipText).find()){
|
||||
currentInviteLink=Uri.parse(clipText.toString());
|
||||
defaultServerButton.setText(getString(R.string.join_server_x_with_invite, currentInviteLink.getHost()));
|
||||
}
|
||||
}else{
|
||||
loadingDefaultServer=true;
|
||||
defaultServerButton.setTextVisible(false);
|
||||
defaultServerProgress.setVisibility(View.VISIBLE);
|
||||
}
|
||||
new GetCatalogDefaultInstances()
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
|
@ -243,7 +312,7 @@ public class SplashFragment extends AppKitFragment{
|
|||
chosenDefaultServer=domain;
|
||||
loadingDefaultServer=false;
|
||||
loadedDefaultServer=true;
|
||||
if(defaultServerButton!=null && getActivity()!=null){
|
||||
if(defaultServerButton!=null && getActivity()!=null && currentInviteLink==null){
|
||||
defaultServerButton.setTextVisible(true);
|
||||
defaultServerProgress.setVisibility(View.GONE);
|
||||
defaultServerButton.setText(getString(R.string.join_default_server, chosenDefaultServer));
|
||||
|
|
|
@ -137,6 +137,9 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
|||
protected void onButtonClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(instance));
|
||||
if(getArguments().containsKey("inviteCode")){
|
||||
args.putString("inviteCode", getArguments().getString("inviteCode"));
|
||||
}
|
||||
Nav.goForResult(getActivity(), SignupFragment.class, args, SIGNUP_REQUEST, this);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ package org.joinmastodon.android.fragments.onboarding;
|
|||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.KeyEvent;
|
||||
|
@ -37,6 +36,7 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
@ -48,7 +48,6 @@ import me.grishka.appkit.api.ErrorResponse;
|
|||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
@ -61,6 +60,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||
protected EditText searchEdit;
|
||||
protected Runnable searchDebouncer=this::onSearchChangedDebounced;
|
||||
protected String currentSearchQuery;
|
||||
protected String currentSearchQueryButWithCasePreserved;
|
||||
protected String loadingInstanceDomain;
|
||||
protected HashMap<String, Instance> instancesCache=new HashMap<>();
|
||||
protected View buttonBar;
|
||||
|
@ -91,6 +91,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||
if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN)
|
||||
return true;
|
||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
|
||||
currentSearchQueryButWithCasePreserved=searchEdit.getText().toString().trim();
|
||||
updateFilteredList();
|
||||
searchEdit.removeCallbacks(searchDebouncer);
|
||||
Instance instance=instancesCache.get(normalizeInstanceDomain(currentSearchQuery));
|
||||
|
@ -105,6 +106,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||
|
||||
protected void onSearchChangedDebounced(){
|
||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
|
||||
currentSearchQueryButWithCasePreserved=searchEdit.getText().toString().trim();
|
||||
updateFilteredList();
|
||||
loadInstanceInfo(currentSearchQuery, false);
|
||||
}
|
||||
|
@ -149,6 +151,10 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||
}
|
||||
|
||||
protected void loadInstanceInfo(String _domain, boolean isFromRedirect){
|
||||
loadInstanceInfo(_domain, isFromRedirect, null);
|
||||
}
|
||||
|
||||
protected void loadInstanceInfo(String _domain, boolean isFromRedirect, Consumer<Object> onError){
|
||||
if(TextUtils.isEmpty(_domain))
|
||||
return;
|
||||
String domain=normalizeInstanceDomain(_domain);
|
||||
|
@ -173,7 +179,10 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||
try{
|
||||
new URI("https://"+domain+"/api/v1/instance"); // Validate the host by trying to parse the URI
|
||||
}catch(URISyntaxException x){
|
||||
showInstanceInfoLoadError(domain, x);
|
||||
if(onError!=null)
|
||||
onError.accept(x);
|
||||
else
|
||||
showInstanceInfoLoadError(domain, x);
|
||||
if(fakeInstance!=null){
|
||||
fakeInstance.description=getString(R.string.error);
|
||||
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
|
||||
|
@ -193,10 +202,11 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||
loadingInstanceDomain=null;
|
||||
result.uri=domain; // needed for instances that use domain redirection
|
||||
instancesCache.put(domain, result);
|
||||
if(instanceProgressDialog!=null || onError!=null)
|
||||
proceedWithAuthOrSignup(result);
|
||||
if(instanceProgressDialog!=null){
|
||||
instanceProgressDialog.dismiss();
|
||||
instanceProgressDialog=null;
|
||||
proceedWithAuthOrSignup(result);
|
||||
}
|
||||
if(Objects.equals(domain, currentSearchQuery) || Objects.equals(currentSearchQuery, redirects.get(domain)) || Objects.equals(currentSearchQuery, redirectsInverse.get(domain))){
|
||||
boolean found=false;
|
||||
|
@ -223,11 +233,14 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||
public void onError(ErrorResponse error){
|
||||
loadingInstanceRequest=null;
|
||||
if(!isFromRedirect && error instanceof MastodonErrorResponse me && me.httpStatus==404){
|
||||
fetchDomainFromHostMetaAndMaybeRetry(domain, error);
|
||||
fetchDomainFromHostMetaAndMaybeRetry(domain, error, onError);
|
||||
return;
|
||||
}
|
||||
loadingInstanceDomain=null;
|
||||
showInstanceInfoLoadError(domain, error);
|
||||
if(onError!=null)
|
||||
onError.accept(error);
|
||||
else
|
||||
showInstanceInfoLoadError(domain, error);
|
||||
if(fakeInstance!=null && getActivity()!=null){
|
||||
fakeInstance.description=getString(R.string.error);
|
||||
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
|
||||
|
@ -276,7 +289,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||
}
|
||||
}
|
||||
|
||||
private void fetchDomainFromHostMetaAndMaybeRetry(String domain, Object origError){
|
||||
private void fetchDomainFromHostMetaAndMaybeRetry(String domain, Object origError, Consumer<Object> onError){
|
||||
String url="https://"+domain+"/.well-known/host-meta";
|
||||
Request req=new Request.Builder()
|
||||
.url(url)
|
||||
|
@ -290,7 +303,12 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||
Activity a=getActivity();
|
||||
if(a==null)
|
||||
return;
|
||||
a.runOnUiThread(()->showInstanceInfoLoadError(domain, e));
|
||||
a.runOnUiThread(()->{
|
||||
if(onError!=null)
|
||||
onError.accept(e);
|
||||
else
|
||||
showInstanceInfoLoadError(domain, e);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -302,7 +320,13 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||
return;
|
||||
try(response){
|
||||
if(!response.isSuccessful()){
|
||||
a.runOnUiThread(()->showInstanceInfoLoadError(domain, response.code()+" "+response.message()));
|
||||
a.runOnUiThread(()->{
|
||||
String err=response.code()+" "+response.message();
|
||||
if(onError!=null)
|
||||
onError.accept(err);
|
||||
else
|
||||
showInstanceInfoLoadError(domain, err);
|
||||
});
|
||||
return;
|
||||
}
|
||||
InputSource source=new InputSource(response.body().charStream());
|
||||
|
@ -321,9 +345,19 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||
}
|
||||
}
|
||||
}
|
||||
a.runOnUiThread(()->showInstanceInfoLoadError(domain, origError));
|
||||
a.runOnUiThread(()->{
|
||||
if(onError!=null)
|
||||
onError.accept(origError);
|
||||
else
|
||||
showInstanceInfoLoadError(domain, origError);
|
||||
});
|
||||
}catch(Exception x){
|
||||
a.runOnUiThread(()->showInstanceInfoLoadError(domain, x));
|
||||
a.runOnUiThread(()->{
|
||||
if(onError!=null)
|
||||
onError.accept(x);
|
||||
else
|
||||
showInstanceInfoLoadError(domain, x);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
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.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
|
@ -12,6 +17,8 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
|
@ -19,9 +26,12 @@ 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;
|
||||
|
@ -29,6 +39,8 @@ 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.SimpleTextWatcher;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.FilterChipView;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
|
@ -40,7 +52,9 @@ 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;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -77,6 +91,9 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
|||
private CatalogInstance.Region chosenRegion;
|
||||
private CategoryChoice categoryChoice=CategoryChoice.GENERAL;
|
||||
|
||||
private String inviteCode, inviteCodeHost;
|
||||
private AlertDialog currentInviteLinkAlert;
|
||||
|
||||
public InstanceCatalogSignupFragment(){
|
||||
super(R.layout.fragment_onboarding_common, 10);
|
||||
}
|
||||
|
@ -317,7 +334,7 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
|||
focusThing=view.findViewById(R.id.focus_thing);
|
||||
focusThing.requestFocus();
|
||||
|
||||
view.findViewById(R.id.btn_random_instance).setOnClickListener(this::onPickRandomInstanceClick);
|
||||
view.findViewById(R.id.btn_use_invite).setOnClickListener(this::onUseInviteClick);
|
||||
nextButton.setEnabled(chosenInstance!=null);
|
||||
}
|
||||
|
||||
|
@ -351,34 +368,191 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
|||
|
||||
@Override
|
||||
protected void proceedWithAuthOrSignup(Instance instance){
|
||||
if(currentInviteLinkAlert!=null){
|
||||
currentInviteLinkAlert.dismiss();
|
||||
}else if(!TextUtils.isEmpty(currentSearchQuery) && HtmlParser.INVITE_LINK_PATTERN.matcher(currentSearchQueryButWithCasePreserved).find()){
|
||||
if(TextUtils.isEmpty(inviteCode) || !Objects.equals(instance.uri, inviteCodeHost)){
|
||||
Uri inviteLink=Uri.parse(currentSearchQueryButWithCasePreserved);
|
||||
new CheckInviteLink(inviteLink.getPath())
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(CheckInviteLink.Response result){
|
||||
inviteCodeHost=inviteLink.getHost();
|
||||
inviteCode=result.inviteCode;
|
||||
proceedWithAuthOrSignup(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
if(error instanceof MastodonErrorResponse mer){
|
||||
switch(mer.httpStatus){
|
||||
case 401 -> new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.expired_invite_link)
|
||||
.setMessage(getString(R.string.expired_clipboard_invite_link_alert, inviteLink.getHost(), getArguments().getString("defaultServer")))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
case 404 -> new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.invalid_invite_link)
|
||||
.setMessage(getString(R.string.invalid_clipboard_invite_link_alert, inviteLink.getHost(), getArguments().getString("defaultServer")))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
default -> error.showToast(getActivity());
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.loading_instance, true)
|
||||
.execNoAuth(inviteLink.getHost());
|
||||
return;
|
||||
}
|
||||
}
|
||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
|
||||
if(!instance.registrations){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.instance_signup_closed)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
if(!instance.registrations && (TextUtils.isEmpty(inviteCode) || !Objects.equals(instance.uri, inviteCodeHost))){
|
||||
if(instance.invitesEnabled){
|
||||
showInviteLinkAlert(instance.uri);
|
||||
}else{
|
||||
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(instance));
|
||||
if(!TextUtils.isEmpty(inviteCode) && Objects.equals(instance.uri, inviteCodeHost))
|
||||
args.putString("inviteCode", inviteCode);
|
||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||
}
|
||||
|
||||
private void onPickRandomInstanceClick(View v){
|
||||
String lang=Locale.getDefault().getLanguage();
|
||||
List<CatalogInstance> instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general"))) && (lang.equals(ci.language) || (ci.languages!=null && ci.languages.contains(lang)))).collect(Collectors.toList());
|
||||
if(instances.isEmpty()){
|
||||
instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList());
|
||||
private void onUseInviteClick(View v){
|
||||
showInviteLinkAlert(null);
|
||||
}
|
||||
|
||||
private void showInviteLinkAlert(String domain){
|
||||
AlertDialog alert=new M3AlertDialogBuilder(getActivity())
|
||||
.setView(R.layout.alert_invite_link)
|
||||
.setPositiveButton(R.string.next, null)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.create();
|
||||
|
||||
Button next=alert.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
EditText edit=alert.findViewById(R.id.edit);
|
||||
TextView supportingText=alert.findViewById(R.id.supporting_text);
|
||||
TextView label=alert.findViewById(R.id.label);
|
||||
TextView subtitle=alert.findViewById(R.id.subtitle);
|
||||
ImageButton clear=alert.findViewById(R.id.clear);
|
||||
clear.setVisibility(View.GONE);
|
||||
|
||||
if(TextUtils.isEmpty(domain)){
|
||||
subtitle.setVisibility(View.GONE);
|
||||
}else{
|
||||
subtitle.setText(getString(R.string.need_invite_to_join_server, domain));
|
||||
}
|
||||
if(instances.isEmpty()){
|
||||
instances=data.stream().filter(ci->("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList());
|
||||
|
||||
Consumer<String> errorSetter=err->{
|
||||
supportingText.setText(err);
|
||||
int errorColor=UiUtils.getThemeColor(getActivity(), R.attr.colorM3Error);
|
||||
supportingText.setTextColor(errorColor);
|
||||
label.setTextColor(errorColor);
|
||||
edit.setBackgroundResource(R.drawable.bg_m3_filled_text_field_error);
|
||||
};
|
||||
|
||||
next.setOnClickListener(_v->{
|
||||
Uri inviteLink=Uri.parse(edit.getText().toString());
|
||||
if(TextUtils.isEmpty(inviteLink.getHost()) || TextUtils.isEmpty(inviteLink.getPath())){
|
||||
errorSetter.accept(getString(R.string.this_invite_is_invalid));
|
||||
return;
|
||||
}
|
||||
UiUtils.showProgressForAlertButton(next, true);
|
||||
new CheckInviteLink(inviteLink.getPath())
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(CheckInviteLink.Response result){
|
||||
if(getActivity()==null || !alert.isShowing())
|
||||
return;
|
||||
|
||||
String host=inviteLink.getHost();
|
||||
inviteCode=result.inviteCode;
|
||||
inviteCodeHost=host;
|
||||
|
||||
Instance instance=instancesCache.get(normalizeInstanceDomain(host));
|
||||
if(instance==null){
|
||||
loadInstanceInfo(host, false, err->{
|
||||
String errorStr;
|
||||
if(err instanceof String str){
|
||||
errorStr=str;
|
||||
}else if(err instanceof Throwable x){
|
||||
errorStr=x.getMessage();
|
||||
}else if(err instanceof MastodonErrorResponse mer){
|
||||
errorStr=mer.error;
|
||||
}else{
|
||||
errorStr=getString(R.string.error);
|
||||
}
|
||||
errorSetter.accept(errorStr);
|
||||
UiUtils.showProgressForAlertButton(next, false);
|
||||
});
|
||||
}else{
|
||||
proceedWithAuthOrSignup(instance);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
if(getActivity()==null || !alert.isShowing())
|
||||
return;
|
||||
UiUtils.showProgressForAlertButton(next, false);
|
||||
if(error instanceof MastodonErrorResponse mer){
|
||||
errorSetter.accept(switch(mer.httpStatus){
|
||||
case 404 -> getString(R.string.this_invite_is_invalid);
|
||||
case 401 -> getString(R.string.this_invite_has_expired);
|
||||
default -> mer.error;
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.execNoAuth(inviteLink.getHost());
|
||||
});
|
||||
next.setEnabled(false);
|
||||
edit.addTextChangedListener(new SimpleTextWatcher(e->{
|
||||
boolean wasEmpty=!next.isEnabled();
|
||||
next.setEnabled(e.length()>0);
|
||||
if(supportingText.length()>0){
|
||||
supportingText.setText("");
|
||||
int regularColor=UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant);
|
||||
supportingText.setTextColor(regularColor);
|
||||
label.setTextColor(regularColor);
|
||||
edit.setBackgroundResource(R.drawable.bg_m3_filled_text_field);
|
||||
}
|
||||
if(wasEmpty!=(e.length()==0)){
|
||||
int padEnd;
|
||||
if(e.length()==0){
|
||||
clear.setVisibility(View.GONE);
|
||||
padEnd=V.dp(16);
|
||||
}else{
|
||||
clear.setVisibility(View.VISIBLE);
|
||||
padEnd=V.dp(48);
|
||||
}
|
||||
edit.setPaddingRelative(edit.getPaddingStart(), edit.getPaddingTop(), padEnd, edit.getPaddingBottom());
|
||||
}
|
||||
}));
|
||||
clear.setOnClickListener(_v->edit.setText(""));
|
||||
|
||||
ClipData clipData=getActivity().getSystemService(ClipboardManager.class).getPrimaryClip();
|
||||
if(clipData!=null && clipData.getItemCount()>0){
|
||||
CharSequence clipText=clipData.getItemAt(0).coerceToText(getActivity());
|
||||
if(HtmlParser.INVITE_LINK_PATTERN.matcher(clipText).find()){
|
||||
edit.setText(clipText);
|
||||
supportingText.setText(R.string.invite_link_pasted);
|
||||
}
|
||||
}
|
||||
if(instances.isEmpty()){
|
||||
return;
|
||||
}
|
||||
chosenInstance=instances.get(new Random().nextInt(instances.size()));
|
||||
onNextClick(v);
|
||||
|
||||
currentInviteLinkAlert=alert;
|
||||
alert.setOnDismissListener(dialog->currentInviteLinkAlert=null);
|
||||
alert.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -387,8 +561,14 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
|||
filteredData.clear();
|
||||
if(searchQueryMode){
|
||||
if(!TextUtils.isEmpty(currentSearchQuery)){
|
||||
String actualQuery;
|
||||
if(currentSearchQuery.startsWith("https:")){
|
||||
actualQuery=Uri.parse(currentSearchQuery).getHost();
|
||||
}else{
|
||||
actualQuery=currentSearchQuery;
|
||||
}
|
||||
for(CatalogInstance instance:data){
|
||||
if(instance.domain.contains(currentSearchQuery)){
|
||||
if(instance.domain.contains(actualQuery)){
|
||||
filteredData.add(instance);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,6 +91,9 @@ public class InstanceRulesFragment extends ToolbarFragment{
|
|||
protected void onButtonClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(instance));
|
||||
if(getArguments().containsKey("inviteCode")){
|
||||
args.putString("inviteCode", getArguments().getString("inviteCode"));
|
||||
}
|
||||
Nav.goForResult(getActivity(), GoogleMadeMeAddThisFragment.class, args, RULES_REQUEST, this);
|
||||
}
|
||||
|
||||
|
|
|
@ -219,7 +219,9 @@ public class SignupFragment extends ToolbarFragment{
|
|||
if(!serverSupportedTimezones.contains(timezone))
|
||||
timezone=null;
|
||||
|
||||
new RegisterAccount(username, email, password.getText().toString(), locale, reason.getText().toString(), timezone)
|
||||
String inviteCode=getArguments().getString("inviteCode");
|
||||
|
||||
new RegisterAccount(username, email, password.getText().toString(), locale, reason.getText().toString(), timezone, inviteCode)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Token result){
|
||||
|
|
|
@ -52,6 +52,7 @@ public class HtmlParser{
|
|||
")" +
|
||||
")";
|
||||
public static final Pattern URL_PATTERN=Pattern.compile(VALID_URL_PATTERN_STRING, Pattern.CASE_INSENSITIVE);
|
||||
public static final Pattern INVITE_LINK_PATTERN=Pattern.compile("^https://"+Regex.URL_VALID_DOMAIN+"/invite/[a-z\\d]+$", Pattern.CASE_INSENSITIVE);
|
||||
private static Pattern EMOJI_CODE_PATTERN=Pattern.compile(":([\\w]+):");
|
||||
|
||||
private HtmlParser(){}
|
||||
|
|
|
@ -11,9 +11,11 @@ import android.content.res.TypedArray;
|
|||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
@ -32,6 +34,8 @@ import android.transition.ChangeScroll;
|
|||
import android.transition.Fade;
|
||||
import android.transition.TransitionManager;
|
||||
import android.transition.TransitionSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
@ -39,7 +43,9 @@ import android.view.ViewGroup;
|
|||
import android.view.WindowInsets;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
|
@ -882,4 +888,31 @@ public class UiUtils{
|
|||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static void showProgressForAlertButton(Button button, boolean show){
|
||||
boolean shown=button.getTag(R.id.button_progress_orig_color)!=null;
|
||||
if(shown==show)
|
||||
return;
|
||||
button.setEnabled(!show);
|
||||
if(show){
|
||||
ColorStateList origColor=button.getTextColors();
|
||||
button.setTag(R.id.button_progress_orig_color, origColor);
|
||||
button.setTextColor(0);
|
||||
ProgressBar progressBar=(ProgressBar) LayoutInflater.from(button.getContext()).inflate(R.layout.progress_bar, null);
|
||||
Drawable progress=progressBar.getIndeterminateDrawable().mutate();
|
||||
progress.setTint(getThemeColor(button.getContext(), R.attr.colorM3OnSurface) & 0x60ffffff);
|
||||
if(progress instanceof Animatable a)
|
||||
a.start();
|
||||
LayerDrawable layerList=new LayerDrawable(new Drawable[]{progress});
|
||||
layerList.setLayerGravity(0, Gravity.CENTER);
|
||||
layerList.setLayerSize(0, V.dp(24), V.dp(24));
|
||||
layerList.setBounds(0, 0, button.getWidth(), button.getHeight());
|
||||
button.getOverlay().add(layerList);
|
||||
}else{
|
||||
button.getOverlay().clear();
|
||||
ColorStateList origColor=(ColorStateList) button.getTag(R.id.button_progress_orig_color);
|
||||
button.setTag(R.id.button_progress_orig_color, null);
|
||||
button.setTextColor(origColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<selector>
|
||||
<item android:state_enabled="true">
|
||||
<shape>
|
||||
<corners android:topLeftRadius="4dp" android:topRightRadius="4dp"/>
|
||||
<solid android:color="?colorM3SurfaceVariant"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:tint="?colorM3OnSurface">
|
||||
<corners android:topLeftRadius="4dp" android:topRightRadius="4dp"/>
|
||||
<solid android:color="#0a000000"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
</item>
|
||||
<item android:left="-3dp" android:top="-3dp" android:right="-3dp">
|
||||
<selector>
|
||||
<item android:state_focused="true">
|
||||
<shape>
|
||||
<stroke android:color="?colorM3Primary" android:width="2dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<stroke android:color="?colorM3OnSurfaceVariant" android:width="1dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<corners android:topLeftRadius="4dp" android:topRightRadius="4dp" />
|
||||
<solid android:color="?colorM3SurfaceVariant" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:left="-3dp" android:right="-3dp" android:top="-3dp">
|
||||
<shape>
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="?colorM3Error" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4,20Q3.175,20 2.588,19.413Q2,18.825 2,18V14Q2.825,14 3.413,13.412Q4,12.825 4,12Q4,11.175 3.413,10.587Q2.825,10 2,10V6Q2,5.175 2.588,4.588Q3.175,4 4,4H20Q20.825,4 21.413,4.588Q22,5.175 22,6V10Q21.175,10 20.587,10.587Q20,11.175 20,12Q20,12.825 20.587,13.412Q21.175,14 22,14V18Q22,18.825 21.413,19.413Q20.825,20 20,20ZM4,18H20V15.45Q19.075,14.9 18.538,13.988Q18,13.075 18,12Q18,10.925 18.538,10.012Q19.075,9.1 20,8.55V6H4V8.55Q4.925,9.1 5.463,10.012Q6,10.925 6,12Q6,13.075 5.463,13.988Q4.925,14.9 4,15.45ZM12,17Q12.425,17 12.713,16.712Q13,16.425 13,16Q13,15.575 12.713,15.287Q12.425,15 12,15Q11.575,15 11.288,15.287Q11,15.575 11,16Q11,16.425 11.288,16.712Q11.575,17 12,17ZM12,13Q12.425,13 12.713,12.712Q13,12.425 13,12Q13,11.575 12.713,11.287Q12.425,11 12,11Q11.575,11 11.288,11.287Q11,11.575 11,12Q11,12.425 11.288,12.712Q11.575,13 12,13ZM12,9Q12.425,9 12.713,8.712Q13,8.425 13,8Q13,7.575 12.713,7.287Q12.425,7 12,7Q11.575,7 11.288,7.287Q11,7.575 11,8Q11,8.425 11.288,8.712Q11.575,9 12,9ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Z"/>
|
||||
</vector>
|
|
@ -0,0 +1,88 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingTop="24dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/m3_headline_small"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:drawableTop="@drawable/ic_confirmation_number_24px"
|
||||
android:drawableTint="?colorM3Secondary"
|
||||
android:drawablePadding="16dp"
|
||||
android:text="@string/enter_invite_link"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
tools:text="@string/need_invite_to_join_server"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="76dp"
|
||||
android:layout_marginTop="16dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@drawable/bg_m3_filled_text_field"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:textColorHint="?colorM3OnSurfaceVariant"
|
||||
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"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_gravity="top"
|
||||
android:paddingEnd="23dp"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:text="@string/server_url"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/supporting_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:text=""/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/clear"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_gravity="end|top"
|
||||
android:src="@drawable/ic_m3_cancel"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:contentDescription="@string/clear"/>
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -125,25 +125,15 @@
|
|||
android:orientation="vertical"
|
||||
android:background="@drawable/bg_onboarding_panel">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:text="@string/signup_random_server_explain"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_random_instance"
|
||||
android:id="@+id/btn_use_invite"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Text"
|
||||
android:text="@string/pick_server_for_me"/>
|
||||
android:text="@string/use_invite_link"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_next"
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</ProgressBar>
|
|
@ -30,4 +30,6 @@
|
|||
|
||||
<item name="server_about" type="id"/>
|
||||
<item name="server_rules" type="id"/>
|
||||
|
||||
<item name="button_progress_orig_color" type="id"/>
|
||||
</resources>
|
|
@ -332,7 +332,6 @@
|
|||
<string name="login_title">Welcome Back</string>
|
||||
<string name="login_subtitle">Log in with the server where you created your account.</string>
|
||||
<string name="server_url">Server URL</string>
|
||||
<string name="signup_random_server_explain">We’ll pick a server based on your language if you continue without making a selection.</string>
|
||||
<string name="server_filter_any_language">Any Language</string>
|
||||
<string name="server_filter_instant_signup">Instant Sign-up</string>
|
||||
<string name="server_filter_manual_review">Manual Review</string>
|
||||
|
@ -346,7 +345,6 @@
|
|||
<string name="not_accepting_new_members">Not accepting new members</string>
|
||||
<string name="category_special_interests">Special Interests</string>
|
||||
<string name="signup_passwords_dont_match">Passwords don’t match</string>
|
||||
<string name="pick_server_for_me">Pick for me</string>
|
||||
<string name="profile_add_row">Add row</string>
|
||||
<string name="profile_setup">Profile setup</string>
|
||||
<string name="profile_setup_subtitle">You can always complete this later in the Profile tab.</string>
|
||||
|
@ -670,4 +668,15 @@
|
|||
<string name="button_reblogged">Boosted</string>
|
||||
<string name="button_favorited">Favorited</string>
|
||||
<string name="bookmarked">Bookmarked</string>
|
||||
<string name="join_server_x_with_invite">Join %s with invite</string>
|
||||
<string name="expired_invite_link">Expired invite link</string>
|
||||
<string name="expired_clipboard_invite_link_alert">The invite link for %1$s in your clipboard has expired and cannot be used to sign up.\n\nYou can request a new link from an existing user, sign up through %2$s, or pick another server to sign up through.</string>
|
||||
<string name="invalid_invite_link">Invalid invite link</string>
|
||||
<string name="invalid_clipboard_invite_link_alert">The invite link for %1$s in your clipboard is not valid and cannot be used to sign up.\n\nYou can request a new link from an existing user, sign up through %2$s, or pick another server to sign up through.</string>
|
||||
<string name="use_invite_link">Use invite link</string>
|
||||
<string name="enter_invite_link">Enter invite link</string>
|
||||
<string name="this_invite_is_invalid">This invite link is not valid.</string>
|
||||
<string name="this_invite_has_expired">This invite link has expired.</string>
|
||||
<string name="invite_link_pasted">Link pasted from your clipboard.</string>
|
||||
<string name="need_invite_to_join_server">To join %s, you’ll need an invite link from an existing user.</string>
|
||||
</resources>
|
Loading…
Reference in New Issue