Onboarding & signup

This commit is contained in:
Grishka 2022-03-10 18:48:24 +03:00
parent 86892e4103
commit 03c0b183cb
80 changed files with 2024 additions and 261 deletions

View File

@ -10,7 +10,7 @@ android {
applicationId "org.joinmastodon.android"
minSdk 23
targetSdk 31
versionCode 7
versionCode 8
versionName "0.1"
}

View File

@ -1,11 +1,14 @@
package org.joinmastodon.android;
import android.app.Application;
import android.app.Fragment;
import android.os.Bundle;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.HomeFragment;
import org.joinmastodon.android.fragments.SplashFragment;
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
import java.lang.reflect.InvocationTargetException;
@ -22,9 +25,10 @@ public class MainActivity extends FragmentStackActivity{
showFragmentClearingBackStack(new SplashFragment());
}else{
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
AccountSession session=AccountSessionManager.getInstance().getLastActiveAccount();
Bundle args=new Bundle();
args.putString("account", AccountSessionManager.getInstance().getLastActiveAccountID());
HomeFragment fragment=new HomeFragment();
args.putString("account", session.getID());
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
fragment.setArguments(args);
showFragmentClearingBackStack(fragment);
}

View File

@ -54,7 +54,7 @@ public class OAuthActivity extends Activity{
progress.setMessage(getString(R.string.finishing_auth));
progress.setCancelable(false);
progress.show();
new GetOauthToken(app.clientId, app.clientSecret, code)
new GetOauthToken(app.clientId, app.clientSecret, code, GetOauthToken.GrantType.AUTHORIZATION_CODE)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Token token){
@ -62,7 +62,7 @@ public class OAuthActivity extends Activity{
.setCallback(new Callback<>(){
@Override
public void onSuccess(Account account){
AccountSessionManager.getInstance().addAccount(instance, token, account, app);
AccountSessionManager.getInstance().addAccount(instance, token, account, app, true);
progress.dismiss();
finish();
// not calling restartMainActivity() here on purpose to have it recreated (notice different flags)

View File

@ -8,6 +8,7 @@ import android.util.Log;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonIOException;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@ -119,10 +120,19 @@ public class MastodonAPIController{
if(response.isSuccessful()){
T respObj;
try{
if(req.respTypeToken!=null)
respObj=gson.fromJson(reader, req.respTypeToken.getType());
else
respObj=gson.fromJson(reader, req.respClass);
if(BuildConfig.DEBUG){
JsonElement respJson=JsonParser.parseReader(reader);
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] response body: "+respJson);
if(req.respTypeToken!=null)
respObj=gson.fromJson(respJson, req.respTypeToken.getType());
else
respObj=gson.fromJson(respJson, req.respClass);
}else{
if(req.respTypeToken!=null)
respObj=gson.fromJson(reader, req.respTypeToken.getType());
else
respObj=gson.fromJson(reader, req.respClass);
}
}catch(JsonIOException|JsonSyntaxException x){
if(BuildConfig.DEBUG)
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error parsing or reading body", x);
@ -146,6 +156,7 @@ public class MastodonAPIController{
}else{
try{
JsonObject error=JsonParser.parseReader(reader).getAsJsonObject();
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" received error: "+error);
req.onError(error.get("error").getAsString());
}catch(JsonIOException|JsonSyntaxException x){
req.onError(response.code()+" "+response.message());

View File

@ -0,0 +1,24 @@
package org.joinmastodon.android.api.requests.accounts;
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){
super(HttpMethod.POST, "/accounts", Token.class);
setRequestBody(new Body(username, email, password, locale, reason));
}
private static class Body{
public String username, email, password, locale, reason;
public boolean agreement=true;
public Body(String username, String email, String password, String locale, String reason){
this.username=username;
this.email=email;
this.password=password;
this.locale=locale;
this.reason=reason;
}
}
}

View File

@ -0,0 +1,19 @@
package org.joinmastodon.android.api.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
public class ResendConfirmationEmail extends MastodonAPIRequest<Object>{
public ResendConfirmationEmail(String email){
super(HttpMethod.POST, "/emails/confirmations", Object.class);
// setRequestBody(new Body(email));
setRequestBody(new Object());
}
private static class Body{
public String email;
public Body(String email){
this.email=email;
}
}
}

View File

@ -8,6 +8,7 @@ import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AccountField;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.io.File;
import java.util.List;
import okhttp3.MultipartBody;
@ -16,6 +17,7 @@ import okhttp3.RequestBody;
public class UpdateAccountCredentials extends MastodonAPIRequest<Account>{
private String displayName, bio;
private Uri avatar, cover;
private File avatarFile, coverFile;
private List<AccountField> fields;
public UpdateAccountCredentials(String displayName, String bio, Uri avatar, Uri cover, List<AccountField> fields){
@ -27,6 +29,15 @@ public class UpdateAccountCredentials extends MastodonAPIRequest<Account>{
this.fields=fields;
}
public UpdateAccountCredentials(String displayName, String bio, File avatar, File cover, List<AccountField> fields){
super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class);
this.displayName=displayName;
this.bio=bio;
this.avatarFile=avatar;
this.coverFile=cover;
this.fields=fields;
}
@Override
public RequestBody getRequestBody(){
MultipartBody.Builder bldr=new MultipartBody.Builder()
@ -36,9 +47,13 @@ public class UpdateAccountCredentials extends MastodonAPIRequest<Account>{
if(avatar!=null){
bldr.addFormDataPart("avatar", UiUtils.getFileName(avatar), new ContentUriRequestBody(avatar, null));
}else if(avatarFile!=null){
bldr.addFormDataPart("avatar", avatarFile.getName(), RequestBody.create(UiUtils.getFileMediaType(avatarFile), avatarFile));
}
if(cover!=null){
bldr.addFormDataPart("header", UiUtils.getFileName(cover), new ContentUriRequestBody(cover, null));
}else if(coverFile!=null){
bldr.addFormDataPart("header", coverFile.getName(), RequestBody.create(UiUtils.getFileMediaType(coverFile), coverFile));
}
if(fields.isEmpty()){
bldr.addFormDataPart("fields_attributes[0][name]", "").addFormDataPart("fields_attributes[0][value]", "");

View File

@ -1,13 +1,15 @@
package org.joinmastodon.android.api.requests.oauth;
import com.google.gson.annotations.SerializedName;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Token;
public class GetOauthToken extends MastodonAPIRequest<Token>{
public GetOauthToken(String clientID, String clientSecret, String code){
public GetOauthToken(String clientID, String clientSecret, String code, GrantType grantType){
super(HttpMethod.POST, "/oauth/token", Token.class);
setRequestBody(new Request(clientID, clientSecret, code));
setRequestBody(new Request(clientID, clientSecret, code, grantType));
}
@Override
@ -16,17 +18,25 @@ public class GetOauthToken extends MastodonAPIRequest<Token>{
}
private static class Request{
public String grantType="authorization_code";
public GrantType grantType;
public String clientId;
public String clientSecret;
public String redirectUri=AccountSessionManager.REDIRECT_URI;
public String scope=AccountSessionManager.SCOPE;
public String code;
public Request(String clientId, String clientSecret, String code){
public Request(String clientId, String clientSecret, String code, GrantType grantType){
this.clientId=clientId;
this.clientSecret=clientSecret;
this.code=code;
this.grantType=grantType;
}
}
public enum GrantType{
@SerializedName("authorization_code")
AUTHORIZATION_CODE,
@SerializedName("client_credentials")
CLIENT_CREDENTIALS
}
}

View File

@ -16,16 +16,18 @@ public class AccountSession{
public long infoLastUpdated;
public long instanceLastUpdated;
public Instance instance;
public boolean activated=true;
private transient MastodonAPIController apiController;
private transient StatusInteractionController statusInteractionController;
AccountSession(Token token, Account self, Application app, String domain, int tootCharLimit, Instance instance){
AccountSession(Token token, Account self, Application app, String domain, int tootCharLimit, Instance instance, boolean activated){
this.token=token;
this.self=self;
this.domain=domain;
this.app=app;
this.tootCharLimit=tootCharLimit;
this.instance=instance;
this.activated=activated;
instanceLastUpdated=infoLastUpdated=System.currentTimeMillis();
}

View File

@ -1,5 +1,6 @@
package org.joinmastodon.android.api.session;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.SharedPreferences;
@ -36,10 +37,12 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.browser.customtabs.CustomTabsIntent;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
@ -84,8 +87,8 @@ public class AccountSessionManager{
MastodonAPIController.runInBackground(()->readCustomEmojis(domains));
}
public void addAccount(Instance instance, Token token, Account self, Application app){
AccountSession session=new AccountSession(token, self, app, instance.uri, instance.maxTootChars, instance);
public void addAccount(Instance instance, Token token, Account self, Application app, boolean active){
AccountSession session=new AccountSession(token, self, app, instance.uri, instance.maxTootChars, instance, active);
sessions.put(session.getID(), session);
lastActiveAccountID=session.getID();
writeAccountsFile();
@ -159,18 +162,13 @@ public class AccountSessionManager{
return unauthenticatedApiController;
}
public void authenticate(Context context, Instance instance){
public void authenticate(Activity activity, Instance instance){
authenticatingInstance=instance;
ProgressDialog progress=new ProgressDialog(context);
progress.setMessage(context.getString(R.string.preparing_auth));
progress.setCancelable(false);
progress.show();
new CreateOAuthApp()
.setCallback(new Callback<Application>(){
.setCallback(new Callback<>(){
@Override
public void onSuccess(Application result){
authenticatingApp=result;
progress.dismiss();
Uri uri=new Uri.Builder()
.scheme("https")
.authority(instance.uri)
@ -184,15 +182,15 @@ public class AccountSessionManager{
new CustomTabsIntent.Builder()
.setShareState(CustomTabsIntent.SHARE_STATE_OFF)
.build()
.launchUrl(context, uri);
.launchUrl(activity, uri);
}
@Override
public void onError(ErrorResponse error){
error.showToast(context);
progress.dismiss();
error.showToast(activity);
}
})
.wrapProgress(activity, R.string.preparing_auth, false)
.execNoAuth(instance.uri);
}

View File

@ -17,6 +17,7 @@ import android.widget.LinearLayout;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.views.TabBar;
import org.parceler.Parcels;

View File

@ -38,9 +38,9 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AccountField;
import org.joinmastodon.android.model.Relationship;

View File

@ -1,5 +1,5 @@
package org.joinmastodon.android.fragments;
/*package*/ interface ScrollableToTop{
public interface ScrollableToTop{
void scrollToTop();
}

View File

@ -1,30 +1,66 @@
package org.joinmastodon.android.fragments;
import android.app.Fragment;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogFragment;
import org.joinmastodon.android.ui.InterpolatingMotionEffect;
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
import androidx.annotation.Nullable;
import me.grishka.appkit.Nav;
import me.grishka.appkit.fragments.AppKitFragment;
import me.grishka.appkit.views.FragmentRootLinearLayout;
import me.grishka.appkit.utils.V;
public class SplashFragment extends AppKitFragment{
private View contentView;
private SizeListenerFrameLayout contentView;
private View artContainer, blueFill, greenFill;
private InterpolatingMotionEffect motionEffect;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
motionEffect=new InterpolatingMotionEffect(MastodonApp.context);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
contentView= inflater.inflate(R.layout.fragment_splash, container, false);
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);
artContainer=contentView.findViewById(R.id.art_container);
blueFill=contentView.findViewById(R.id.blue_fill);
greenFill=contentView.findViewById(R.id.green_fill);
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_clouds), V.dp(-5), V.dp(5), V.dp(-5), V.dp(5)));
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_right_hill), V.dp(-15), V.dp(25), V.dp(-10), V.dp(10)));
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_left_hill), V.dp(-25), V.dp(15), V.dp(-15), V.dp(15)));
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_center_hill), V.dp(-14), V.dp(14), V.dp(-5), V.dp(25)));
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_plane_elephant), V.dp(-20), V.dp(12), V.dp(-20), V.dp(12)));
contentView.setSizeListener(new SizeListenerFrameLayout.OnSizeChangedListener(){
@Override
public void onSizeChanged(int w, int h, int oldw, int oldh){
contentView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw(){
contentView.getViewTreeObserver().removeOnPreDrawListener(this);
updateArtSize(w, h);
return true;
}
});
}
});
return contentView;
}
@ -33,10 +69,46 @@ public class SplashFragment extends AppKitFragment{
extras.putBoolean("signup", v.getId()==R.id.btn_get_started);
Nav.go(getActivity(), InstanceCatalogFragment.class, extras);
}
//
// @Override
// public void onApplyWindowInsets(WindowInsets insets){
// if(contentView!=null)
// contentView.dispatchApplyWindowInsets(insets);
// }
private void updateArtSize(int w, int h){
float scale=w/(float)V.dp(412);
artContainer.setScaleX(scale);
artContainer.setScaleY(scale);
blueFill.setScaleY(h/2f);
greenFill.setScaleY(h-artContainer.getBottom()+V.dp(90));
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
super.onApplyWindowInsets(insets);
int bottomInset=insets.getSystemWindowInsetBottom();
if(bottomInset>0 && bottomInset<V.dp(36)){
contentView.setPadding(contentView.getPaddingLeft(), contentView.getPaddingTop(), contentView.getPaddingRight(), V.dp(36));
}
((ViewGroup.MarginLayoutParams)blueFill.getLayoutParams()).topMargin=-contentView.getPaddingTop();
((ViewGroup.MarginLayoutParams)greenFill.getLayoutParams()).bottomMargin=-contentView.getPaddingBottom();
}
@Override
public boolean wantsLightStatusBar(){
return true;
}
@Override
public boolean wantsLightNavigationBar(){
return true;
}
@Override
protected void onShown(){
super.onShown();
motionEffect.activate();
}
@Override
protected void onHidden(){
super.onHidden();
motionEffect.deactivate();
}
}

View File

@ -1,4 +1,4 @@
package org.joinmastodon.android.fragments;
package org.joinmastodon.android.fragments.discover;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
@ -15,6 +15,7 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.FollowSuggestion;
import org.joinmastodon.android.model.Relationship;

View File

@ -1,4 +1,4 @@
package org.joinmastodon.android.fragments;
package org.joinmastodon.android.fragments.discover;
import android.app.Fragment;
import android.os.Build;
@ -10,6 +10,7 @@ import android.widget.FrameLayout;
import android.widget.LinearLayout;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.ui.SimpleViewHolder;
import org.joinmastodon.android.ui.tabs.TabLayout;
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;

View File

@ -1,4 +1,4 @@
package org.joinmastodon.android.fragments;
package org.joinmastodon.android.fragments.discover;
import android.graphics.drawable.Drawable;
import android.os.Bundle;

View File

@ -1,10 +1,11 @@
package org.joinmastodon.android.fragments;
package org.joinmastodon.android.fragments.discover;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.fragments.StatusListFragment;
import org.joinmastodon.android.model.Status;
import java.util.List;

View File

@ -1,4 +1,4 @@
package org.joinmastodon.android.fragments;
package org.joinmastodon.android.fragments.discover;
import android.graphics.Canvas;
import android.graphics.Paint;

View File

@ -0,0 +1,172 @@
package org.joinmastodon.android.fragments.onboarding;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.Button;
import android.widget.Toast;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
import org.joinmastodon.android.api.requests.accounts.ResendConfirmationEmail;
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.HomeFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.io.File;
import java.util.Collections;
import androidx.annotation.Nullable;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.APIRequest;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.AppKitFragment;
import me.grishka.appkit.utils.V;
public class AccountActivationFragment extends AppKitFragment{
private String accountID;
private Button btn, backBtn;
private View buttonBar;
private Handler uiHandler=new Handler(Looper.getMainLooper());
private Runnable pollRunnable=this::tryGetAccount;
private APIRequest currentRequest;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
accountID=getArguments().getString("account");
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
View view=inflater.inflate(R.layout.fragment_onboarding_activation, container, false);
btn=view.findViewById(R.id.btn_next);
btn.setOnClickListener(v->onButtonClick());
buttonBar=view.findViewById(R.id.button_bar);
view.findViewById(R.id.btn_back).setOnClickListener(v->onBackButtonClick());
return view;
}
@Override
public boolean wantsLightStatusBar(){
return (MastodonApp.context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)!=Configuration.UI_MODE_NIGHT_YES;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
if(Build.VERSION.SDK_INT>=27){
int inset=insets.getSystemWindowInsetBottom();
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
}else{
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
}
}
@Override
protected void onShown(){
super.onShown();
tryGetAccount();
}
@Override
protected void onHidden(){
super.onHidden();
if(currentRequest!=null){
currentRequest.cancel();
currentRequest=null;
}else{
uiHandler.removeCallbacks(pollRunnable);
}
}
private void onButtonClick(){
startActivity(Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_EMAIL).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
private void onBackButtonClick(){
new ResendConfirmationEmail(null)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Object result){
Toast.makeText(getActivity(), R.string.resent_email, Toast.LENGTH_SHORT).show();
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
}
})
.wrapProgress(getActivity(), R.string.loading, false)
.exec(accountID);
}
private void tryGetAccount(){
currentRequest=new GetOwnAccount()
.setCallback(new Callback<>(){
@Override
public void onSuccess(Account result){
currentRequest=null;
AccountSessionManager mgr=AccountSessionManager.getInstance();
AccountSession session=mgr.getAccount(accountID);
mgr.removeAccount(accountID);
mgr.addAccount(session.instance, session.token, result, session.app, true);
String newID=mgr.getLastActiveAccountID();
Bundle args=new Bundle();
args.putString("account", newID);
if(session.self.avatar!=null || session.self.displayName!=null){
File avaFile=session.self.avatar!=null ? new File(session.self.avatar) : null;
new UpdateAccountCredentials(session.self.displayName, "", avaFile, null, Collections.emptyList())
.setCallback(new Callback<>(){
@Override
public void onSuccess(Account result){
if(avaFile!=null)
avaFile.delete();
mgr.updateAccountInfo(newID, result);
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
}
@Override
public void onError(ErrorResponse error){
if(avaFile!=null)
avaFile.delete();
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
}
})
.exec(newID);
}else{
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
}
}
@Override
public void onError(ErrorResponse error){
currentRequest=null;
uiHandler.postDelayed(pollRunnable, 10_000L);
}
})
.exec(accountID);
}
}

View File

@ -1,6 +1,5 @@
package org.joinmastodon.android.fragments.onboarding;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Build;
@ -12,11 +11,12 @@ import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.RadioButton;
import android.widget.TextView;
import android.widget.Toast;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIRequest;
@ -28,6 +28,12 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
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.DividerItemDecoration;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.tabs.TabLayout;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
import java.net.IDN;
import java.util.ArrayList;
@ -40,14 +46,15 @@ import java.util.stream.Collectors;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
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.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstance>{
@ -59,7 +66,7 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
private Button nextButton;
private MastodonAPIRequest<?> getCategoriesRequest;
private EditText searchEdit;
private UsableRecyclerView categoriesList;
private TabLayout categoriesList;
private Runnable searchDebouncer=this::onSearchChangedDebounced;
private String currentSearchQuery;
private String currentCategory="all";
@ -68,6 +75,7 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
private GetInstance loadingInstanceRequest;
private HashMap<String, Instance> instancesCache=new HashMap<>();
private ProgressDialog instanceProgressDialog;
private View buttonBar;
private boolean isSignup;
@ -145,8 +153,14 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
CatalogCategory all=new CatalogCategory();
all.category="all";
categories.add(all);
categories.addAll(result);
categoriesList.getAdapter().notifyItemRangeInserted(0, categories.size());
result.stream().sorted(Comparator.comparingInt((CatalogCategory cc)->cc.serversCount).reversed()).forEach(categories::add);
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
@ -170,6 +184,24 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
headerView=getActivity().getLayoutInflater().inflate(R.layout.header_onboarding_instance_catalog, list, false);
searchEdit=headerView.findViewById(R.id.search_edit);
categoriesList=headerView.findViewById(R.id.categories_list);
categoriesList.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
@Override
public void onTabSelected(TabLayout.Tab tab){
CatalogCategory category=categories.get(tab.getPosition());
currentCategory=category.category;
updateFilteredList();
}
@Override
public void onTabUnselected(TabLayout.Tab tab){
}
@Override
public void onTabReselected(TabLayout.Tab tab){
}
});
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
searchEdit.addTextChangedListener(new TextWatcher(){
@Override
@ -187,8 +219,6 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
public void afterTextChanged(Editable s){
}
});
categoriesList.setAdapter(new CategoriesAdapter());
categoriesList.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false));
mergeAdapter=new MergeRecyclerAdapter();
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
@ -201,6 +231,13 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
super.onViewCreated(view, savedInstanceState);
nextButton=view.findViewById(R.id.btn_next);
nextButton.setOnClickListener(this::onNextClick);
nextButton.setEnabled(chosenInstance!=null);
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
list.setItemAnimator(new BetterItemAnimator());
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 16, 16, DividerItemDecoration.NOT_FIRST));
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
buttonBar=view.findViewById(R.id.button_bar);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
}
private void onNextClick(View v){
@ -218,12 +255,71 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
private void proceedWithAuthOrSignup(Instance instance){
if(isSignup){
Toast.makeText(getActivity(), "not implemented yet", Toast.LENGTH_SHORT).show();
Bundle args=new Bundle();
args.putParcelable("instance", Parcels.wrap(instance));
Nav.go(getActivity(), InstanceRulesFragment.class, args);
}else{
AccountSessionManager.getInstance().authenticate(getActivity(), instance);
}
}
// private String getEmojiForCategory(String category){
// return switch(category){
// case "all" -> "💬";
// case "academia" -> "📚";
// case "activism" -> "";
// case "food" -> "🍕";
// case "furry" -> "🦁";
// case "games" -> "🕹";
// case "general" -> "🐘";
// case "journalism" -> "📰";
// case "lgbt" -> "🏳️‍🌈";
// case "regional" -> "📍";
// case "art" -> "🎨";
// case "music" -> "🎼";
// case "tech" -> "📱";
// default -> "";
// };
// }
private int getEmojiForCategory(String category){
return switch(category){
case "all" -> R.drawable.ic_category_all;
case "academia" -> R.drawable.ic_category_academia;
case "activism" -> R.drawable.ic_category_activism;
case "food" -> R.drawable.ic_category_food;
case "furry" -> R.drawable.ic_category_furry;
case "games" -> R.drawable.ic_category_games;
case "general" -> R.drawable.ic_category_general;
case "journalism" -> R.drawable.ic_category_journalism;
case "lgbt" -> R.drawable.ic_category_lgbt;
case "regional" -> R.drawable.ic_category_regional;
case "art" -> R.drawable.ic_category_art;
case "music" -> R.drawable.ic_category_music;
case "tech" -> R.drawable.ic_category_tech;
default -> R.drawable.ic_category_unknown;
};
}
private int getTitleForCategory(String category){
return switch(category){
case "all" -> R.string.category_all;
case "academia" -> R.string.category_academia;
case "activism" -> R.string.category_activism;
case "food" -> R.string.category_food;
case "furry" -> R.string.category_furry;
case "games" -> R.string.category_games;
case "general" -> R.string.category_general;
case "journalism" -> R.string.category_journalism;
case "lgbt" -> R.string.category_lgbt;
case "regional" -> R.string.category_regional;
case "art" -> R.string.category_art;
case "music" -> R.string.category_music;
case "tech" -> R.string.category_tech;
default -> 0;
};
}
private boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){
if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN)
return true;
@ -290,6 +386,8 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
}
private void loadInstanceInfo(String _domain){
if(TextUtils.isEmpty(_domain))
return;
String domain;
try{
domain=IDN.toASCII(_domain);
@ -299,7 +397,7 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
Instance cachedInstance=instancesCache.get(domain);
if(cachedInstance!=null){
for(CatalogInstance ci:filteredData){
if(ci.domain.equals(currentSearchQuery))
if(ci.domain.equals(domain))
return;
}
CatalogInstance ci=cachedInstance.toCatalogInstance();
@ -330,7 +428,7 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
if(domain.equals(currentSearchQuery)){
boolean found=false;
for(CatalogInstance ci:filteredData){
if(ci.domain.equals(currentSearchQuery)){
if(ci.domain.equals(domain)){
found=true;
break;
}
@ -350,7 +448,7 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
if(instanceProgressDialog!=null){
instanceProgressDialog.dismiss();
instanceProgressDialog=null;
new AlertDialog.Builder(getActivity())
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error)
.setMessage(getString(R.string.not_a_mastodon_instance, domain)+"\n\n"+((MastodonErrorResponse)error).error)
.setPositiveButton(R.string.ok, null)
@ -360,6 +458,17 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
}).execNoAuth(domain);
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
if(Build.VERSION.SDK_INT>=27){
int inset=insets.getSystemWindowInsetBottom();
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
}else{
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
}
}
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceViewHolder>{
public InstancesAdapter(){
super(imgLoader);
@ -399,13 +508,17 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
userCount=findViewById(R.id.user_count);
lang=findViewById(R.id.lang);
radioButton=findViewById(R.id.radiobtn);
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
UiUtils.fixCompoundDrawableTintOnAndroid6(userCount);
UiUtils.fixCompoundDrawableTintOnAndroid6(lang);
}
}
@Override
public void onBind(CatalogInstance item){
title.setText(item.normalizedDomain);
description.setText(item.description);
userCount.setText(""+item.totalUsers);
userCount.setText(UiUtils.abbreviateNumber(item.totalUsers));
lang.setText(item.language.toUpperCase());
radioButton.setChecked(chosenInstance==item);
}
@ -430,57 +543,4 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
loadInstanceInfo(chosenInstance.domain);
}
}
private class CategoriesAdapter extends RecyclerView.Adapter<CategoryViewHolder>{
@NonNull
@Override
public CategoryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new CategoryViewHolder();
}
@Override
public void onBindViewHolder(@NonNull CategoryViewHolder holder, int position){
holder.bind(categories.get(position));
}
@Override
public int getItemCount(){
return categories.size();
}
}
private class CategoryViewHolder extends BindableViewHolder<CatalogCategory> implements UsableRecyclerView.Clickable{
private final RadioButton radioButton;
public CategoryViewHolder(){
super(getActivity(), R.layout.item_instance_category, categoriesList);
radioButton=findViewById(R.id.radiobtn);
}
@Override
public void onBind(CatalogCategory item){
radioButton.setText(item.category);
radioButton.setChecked(item.category.equals(currentCategory));
}
@Override
public void onClick(){
if(currentCategory.equals(item.category))
return;
int i=0;
for(CatalogCategory c:categories){
if(c.category.equals(currentCategory)){
RecyclerView.ViewHolder holder=categoriesList.findViewHolderForAdapterPosition(i);
if(holder!=null){
((CategoryViewHolder)holder).radioButton.setChecked(false);
}
break;
}
i++;
}
currentCategory=item.category;
radioButton.setChecked(true);
updateFilteredList();
}
}
}

View File

@ -0,0 +1,137 @@
package org.joinmastodon.android.fragments.onboarding;
import android.app.Activity;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav;
import me.grishka.appkit.fragments.AppKitFragment;
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.views.UsableRecyclerView;
public class InstanceRulesFragment extends AppKitFragment{
private UsableRecyclerView list;
private MergeRecyclerAdapter adapter;
private Button btn;
private View buttonBar;
private Instance instance;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false);
list=view.findViewById(R.id.list);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
View headerView=inflater.inflate(R.layout.item_list_header, list, false);
TextView title=headerView.findViewById(R.id.title);
TextView subtitle=headerView.findViewById(R.id.subtitle);
title.setText(R.string.instance_rules_title);
subtitle.setText(getString(R.string.instance_rules_subtitle, instance.uri));
adapter=new MergeRecyclerAdapter();
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
adapter.addAdapter(new ItemsAdapter());
list.setAdapter(adapter);
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 16, 16, DividerItemDecoration.NOT_FIRST));
btn=view.findViewById(R.id.btn_next);
btn.setOnClickListener(v->onButtonClick());
buttonBar=view.findViewById(R.id.button_bar);
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
}
protected void onButtonClick(){
Bundle args=new Bundle();
args.putParcelable("instance", Parcels.wrap(instance));
Nav.go(getActivity(), SignupFragment.class, args);
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
if(Build.VERSION.SDK_INT>=27){
int inset=insets.getSystemWindowInsetBottom();
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
}else{
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
}
}
private class ItemsAdapter extends RecyclerView.Adapter<ItemViewHolder>{
@NonNull
@Override
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new ItemViewHolder();
}
@Override
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position){
holder.bind(instance.rules.get(position));
}
@Override
public int getItemCount(){
return instance.rules.size();
}
}
private class ItemViewHolder extends BindableViewHolder<Instance.Rule>{
private final TextView title, subtitle;
private final ImageView checkbox;
public ItemViewHolder(){
super(getActivity(), R.layout.item_report_choice, list);
title=findViewById(R.id.title);
subtitle=findViewById(R.id.subtitle);
checkbox=findViewById(R.id.checkbox);
subtitle.setVisibility(View.GONE);
}
@Override
public void onBind(Instance.Rule item){
title.setText(item.text);
}
}
}

View File

@ -0,0 +1,290 @@
package org.joinmastodon.android.fragments.onboarding;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.requests.accounts.RegisterAccount;
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
import org.joinmastodon.android.api.requests.oauth.GetOauthToken;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Application;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Token;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import androidx.annotation.Nullable;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.APIRequest;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.AppKitFragment;
import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V;
public class SignupFragment extends AppKitFragment{
private static final int AVATAR_RESULT=198;
private static final String TAG="SignupFragment";
private Instance instance;
private EditText displayName, username, email, password;
private Button btn;
private View buttonBar;
private TextWatcher buttonStateUpdater=new SimpleTextWatcher(e->updateButtonState());
private ImageView avatar;
private APIRequest currentBackgroundRequest;
private Application apiApplication;
private Token apiToken;
private boolean submitAfterGettingToken;
private ProgressDialog progressDialog;
private Uri avatarUri;
private File avatarFile;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setRetainInstance(true);
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
createAppAndGetToken();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
View view=inflater.inflate(R.layout.fragment_onboarding_signup, container, false);
TextView title=view.findViewById(R.id.title);
TextView domain=view.findViewById(R.id.domain);
displayName=view.findViewById(R.id.display_name);
username=view.findViewById(R.id.username);
email=view.findViewById(R.id.email);
password=view.findViewById(R.id.password);
avatar=view.findViewById(R.id.avatar);
View avaWrap=view.findViewById(R.id.ava_wrap);
title.setText(getString(R.string.signup_title, instance.uri));
domain.setText('@'+instance.uri);
username.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw(){
username.getViewTreeObserver().removeOnPreDrawListener(this);
username.setPadding(username.getPaddingLeft(), username.getPaddingTop(), domain.getWidth(), username.getPaddingBottom());
return true;
}
});
btn=view.findViewById(R.id.btn_next);
btn.setOnClickListener(v->onButtonClick());
buttonBar=view.findViewById(R.id.button_bar);
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
updateButtonState();
username.addTextChangedListener(buttonStateUpdater);
email.addTextChangedListener(buttonStateUpdater);
password.addTextChangedListener(buttonStateUpdater);
avaWrap.setOutlineProvider(OutlineProviders.roundedRect(22));
avaWrap.setClipToOutline(true);
avaWrap.setOnClickListener(v->onAvatarClick());
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
}
private void onButtonClick(){
showProgressDialog();
if(currentBackgroundRequest!=null){
submitAfterGettingToken=true;
}else if(apiApplication==null){
submitAfterGettingToken=true;
createAppAndGetToken();
}else if(apiToken==null){
submitAfterGettingToken=true;
getToken();
}else{
submit();
}
}
private void copyAvatar(Runnable onDone){
// Need to copy the avatar from the content provider to somewhere accessible in case the app gets killed between signup and account activation
Activity activity=getActivity();
MastodonAPIController.runInBackground(()->{
String origName=UiUtils.getFileName(avatarUri);
avatarFile=new File(activity.getCacheDir(), System.currentTimeMillis()+origName.substring(origName.lastIndexOf('.')));
try(InputStream in=activity.getContentResolver().openInputStream(avatarUri);
FileOutputStream out=new FileOutputStream(avatarFile)){
byte[] buf=new byte[10240];
int read;
while((read=in.read(buf))>0){
out.write(buf, 0, read);
}
}catch(IOException x){
Log.w(TAG, "copyAvatar: error copying", x);
}
activity.runOnUiThread(onDone);
});
}
private void submit(){
if(avatarUri!=null && (avatarFile==null || !avatarFile.exists())){
copyAvatar(this::actuallySubmit);
}else{
actuallySubmit();
}
}
private void actuallySubmit(){
String username=this.username.getText().toString();
String email=this.email.getText().toString();
new RegisterAccount(username, email, password.getText().toString(), getResources().getConfiguration().locale.getLanguage(), null)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Token result){
progressDialog.dismiss();
progressDialog=null;
Account fakeAccount=new Account();
fakeAccount.acct=fakeAccount.username=username;
fakeAccount.id="tmp"+System.currentTimeMillis();
fakeAccount.displayName=displayName.getText().toString();
if(avatarFile!=null)
fakeAccount.avatar=avatarFile.getAbsolutePath();
AccountSessionManager.getInstance().addAccount(instance, result, fakeAccount, apiApplication, false);
Bundle args=new Bundle();
args.putString("account", AccountSessionManager.getInstance().getLastActiveAccountID());
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
progressDialog.dismiss();
progressDialog=null;
}
})
.exec(instance.uri, apiToken);
}
private void showProgressDialog(){
progressDialog=new ProgressDialog(getActivity());
progressDialog.setMessage(getString(R.string.loading));
progressDialog.setCancelable(false);
progressDialog.show();
}
private void updateButtonState(){
btn.setEnabled(username.length()>0 && email.length()>0 && email.getText().toString().contains("@") && password.length()>=8);
}
private void createAppAndGetToken(){
currentBackgroundRequest=new CreateOAuthApp()
.setCallback(new Callback<>(){
@Override
public void onSuccess(Application result){
apiApplication=result;
getToken();
}
@Override
public void onError(ErrorResponse error){
currentBackgroundRequest=null;
if(submitAfterGettingToken){
submitAfterGettingToken=false;
progressDialog.dismiss();
progressDialog=null;
error.showToast(getActivity());
}
}
})
.execNoAuth(instance.uri);
}
private void getToken(){
currentBackgroundRequest=new GetOauthToken(apiApplication.clientId, apiApplication.clientSecret, null, GetOauthToken.GrantType.CLIENT_CREDENTIALS)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Token result){
currentBackgroundRequest=null;
apiToken=result;
if(submitAfterGettingToken){
submitAfterGettingToken=false;
submit();
}
}
@Override
public void onError(ErrorResponse error){
currentBackgroundRequest=null;
if(submitAfterGettingToken){
submitAfterGettingToken=false;
progressDialog.dismiss();
progressDialog=null;
error.showToast(getActivity());
}
}
})
.execNoAuth(instance.uri);
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
if(Build.VERSION.SDK_INT>=27){
int inset=insets.getSystemWindowInsetBottom();
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
}else{
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
if(requestCode==AVATAR_RESULT && resultCode==Activity.RESULT_OK){
avatarUri=data.getData();
if(avatarFile!=null && avatarFile.exists())
avatarFile.delete();
ViewImageLoader.load(avatar, getResources().getDrawable(R.drawable.default_avatar), new UrlImageLoaderRequest(avatarUri, V.dp(100), V.dp(100)));
}
}
private void onAvatarClick(){
startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT).setType("image/*").addCategory(Intent.CATEGORY_OPENABLE), AVATAR_RESULT);
}
}

View File

@ -1,4 +1,4 @@
package org.joinmastodon.android.fragments;
package org.joinmastodon.android.fragments.report;
import android.app.Activity;
import android.os.Build;

View File

@ -1,4 +1,4 @@
package org.joinmastodon.android.fragments;
package org.joinmastodon.android.fragments.report;
import android.app.Activity;
import android.graphics.Canvas;
@ -15,10 +15,10 @@ import android.widget.TextView;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.events.FinishReportFragmentsEvent;
import org.joinmastodon.android.fragments.StatusListFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
@ -33,7 +33,6 @@ import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

View File

@ -1,4 +1,4 @@
package org.joinmastodon.android.fragments;
package org.joinmastodon.android.fragments.report;
import android.app.Activity;
import android.os.Build;

View File

@ -1,4 +1,4 @@
package org.joinmastodon.android.fragments;
package org.joinmastodon.android.fragments.report;
import android.app.Activity;
import android.os.Build;
@ -8,17 +8,11 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.requests.reports.SendReport;
import org.joinmastodon.android.events.FinishReportFragmentsEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.ReportReason;
@ -26,8 +20,6 @@ import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
import java.util.ArrayList;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;

View File

@ -1,4 +1,4 @@
package org.joinmastodon.android.fragments;
package org.joinmastodon.android.fragments.report;
import android.os.Bundle;

View File

@ -1,4 +1,4 @@
package org.joinmastodon.android.fragments;
package org.joinmastodon.android.fragments.report;
import android.os.Bundle;

View File

@ -1,18 +1,18 @@
package org.joinmastodon.android.model;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.Html;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.model.catalog.CatalogInstance;
import org.parceler.Parcel;
import java.net.IDN;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@Parcel
public class Instance extends BaseModel{
/**
* The domain name of the instance.
@ -84,6 +84,8 @@ public class Instance extends BaseModel{
super.postprocess();
if(contactAccount!=null)
contactAccount.postprocess();
if(rules==null)
rules=Collections.emptyList();
}
@Override
@ -123,93 +125,16 @@ public class Instance extends BaseModel{
return ci;
}
public static class Rule implements Parcelable{
@Parcel
public static class Rule{
public String id;
public String text;
@Override
public int describeContents(){
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags){
dest.writeString(this.id);
dest.writeString(this.text);
}
public void readFromParcel(Parcel source){
this.id=source.readString();
this.text=source.readString();
}
public Rule(){
}
protected Rule(Parcel in){
this.id=in.readString();
this.text=in.readString();
}
public static final Parcelable.Creator<Rule> CREATOR=new Parcelable.Creator<Rule>(){
@Override
public Rule createFromParcel(Parcel source){
return new Rule(source);
}
@Override
public Rule[] newArray(int size){
return new Rule[size];
}
};
}
public static class Stats implements Parcelable{
@Parcel
public static class Stats{
public int userCount;
public int statusCount;
public int domainCount;
@Override
public int describeContents(){
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags){
dest.writeInt(this.userCount);
dest.writeInt(this.statusCount);
dest.writeInt(this.domainCount);
}
public void readFromParcel(Parcel source){
this.userCount=source.readInt();
this.statusCount=source.readInt();
this.domainCount=source.readInt();
}
public Stats(){
}
protected Stats(Parcel in){
this.userCount=in.readInt();
this.statusCount=in.readInt();
this.domainCount=in.readInt();
}
public static final Parcelable.Creator<Stats> CREATOR=new Parcelable.Creator<Stats>(){
@Override
public Stats createFromParcel(Parcel source){
return new Stats(source);
}
@Override
public Stats[] newArray(int size){
return new Stats[size];
}
};
}
}

View File

@ -44,7 +44,8 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration{
View child=parent.getChildAt(i);
int pos=parent.getChildAdapterPosition(child);
if(pos<totalItems-1 && (drawDividerPredicate==null || drawDividerPredicate.test(parent.getChildViewHolder(child)))){
float y=Math.round(child.getY()+child.getHeight()-paint.getStrokeWidth()/2f);
float y=Math.round(child.getY()+child.getHeight());
y-=(y-paint.getStrokeWidth()/2f)%1f; // Make sure the line aligns with the pixel grid
paint.setAlpha(Math.round(255f*child.getAlpha()));
c.drawLine(padLeft+child.getX(), y, child.getX()+child.getWidth()-padRight, y, paint);
}

View File

@ -0,0 +1,134 @@
package org.joinmastodon.android.ui;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import java.util.ArrayList;
public class InterpolatingMotionEffect implements SensorEventListener{
private SensorManager sm;
private WindowManager wm;
private float[] rollBuffer = new float[9], pitchBuffer = new float[9];
private int bufferOffset;
private Sensor accelerometer;
private boolean accelerometerEnabled;
private ArrayList<ViewEffect> views=new ArrayList<>();
public InterpolatingMotionEffect(Context context){
sm=context.getSystemService(SensorManager.class);
wm=context.getSystemService(WindowManager.class);
accelerometer=sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
public void activate(){
if(accelerometer==null || accelerometerEnabled)
return;
sm.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME);
accelerometerEnabled=true;
}
public void deactivate(){
if(accelerometer==null || !accelerometerEnabled)
return;
sm.unregisterListener(this);
accelerometerEnabled=false;
}
@Override
public void onSensorChanged(SensorEvent event){
int rotation=wm.getDefaultDisplay().getRotation();
float x=event.values[0]/SensorManager.GRAVITY_EARTH;
float y=event.values[1]/SensorManager.GRAVITY_EARTH;
float z=event.values[2]/SensorManager.GRAVITY_EARTH;
float pitch=(float) (Math.atan2(x, Math.sqrt(y*y+z*z))/Math.PI*2.0);
float roll=(float) (Math.atan2(y, Math.sqrt(x*x+z*z))/Math.PI*2.0);
switch(rotation){
case Surface.ROTATION_0:
break;
case Surface.ROTATION_90:{
float tmp=pitch;
pitch=roll;
roll=tmp;
break;
}
case Surface.ROTATION_180:
roll=-roll;
pitch=-pitch;
break;
case Surface.ROTATION_270:{
float tmp=-pitch;
pitch=roll;
roll=tmp;
break;
}
}
rollBuffer[bufferOffset]=roll;
pitchBuffer[bufferOffset]=pitch;
bufferOffset=(bufferOffset+1)%rollBuffer.length;
roll=pitch=0;
for(int i=0; i<rollBuffer.length; i++){
roll+=rollBuffer[i];
pitch+=pitchBuffer[i];
}
roll/=rollBuffer.length;
pitch/=rollBuffer.length;
if(roll>1f){
roll=2f-roll;
}else if(roll<-1f){
roll=-2f-roll;
}
for(ViewEffect view:views){
view.update(pitch, roll);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy){
}
public void addViewEffect(ViewEffect effect){
views.add(effect);
}
public void removeViewEffect(ViewEffect effect){
views.remove(effect);
}
public void removeAllViewEffects(){
views.clear();
}
public static class ViewEffect{
private View view;
private float minX, maxX, minY, maxY;
public ViewEffect(View view, float minX, float maxX, float minY, float maxY){
this.view=view;
this.minX=minX;
this.maxX=maxX;
this.minY=minY;
this.maxY=maxY;
}
private void update(float x, float y){
view.setTranslationX(lerp(maxX, minX, (x+1f)/2f));
view.setTranslationY(lerp(minY, maxY, (y+1f)/2f));
}
private static float lerp(float startValue, float endValue, float fraction) {
return startValue + (fraction * (endValue - startValue));
}
}
}

View File

@ -19,7 +19,7 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.ReportReasonChoiceFragment;
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Status;

View File

@ -13,6 +13,7 @@ import android.os.Looper;
import android.provider.OpenableColumns;
import android.text.Spanned;
import android.view.View;
import android.webkit.MimeTypeMap;
import android.widget.Button;
import android.widget.TextView;
@ -33,6 +34,7 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
import java.io.File;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
@ -49,6 +51,7 @@ import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V;
import okhttp3.MediaType;
public class UiUtils{
private static Handler mainHandler=new Handler(Looper.getMainLooper());
@ -143,6 +146,11 @@ public class UiUtils{
return uri.getLastPathSegment();
}
public static MediaType getFileMediaType(File file){
String name=file.getName();
return MediaType.parse(MimeTypeMap.getSingleton().getMimeTypeFromExtension(name.substring(name.lastIndexOf('.')+1)));
}
public static void loadCustomEmojiInTextView(TextView view){
CharSequence _text=view.getText();
if(!(_text instanceof Spanned))

View File

@ -0,0 +1,38 @@
package org.joinmastodon.android.ui.views;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
public class SizeListenerFrameLayout extends FrameLayout{
private OnSizeChangedListener sizeListener;
public SizeListenerFrameLayout(Context context){
super(context);
}
public SizeListenerFrameLayout(Context context, @Nullable AttributeSet attrs){
super(context, attrs);
}
public SizeListenerFrameLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr){
super(context, attrs, defStyleAttr);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh){
if(sizeListener!=null)
sizeListener.onSizeChanged(w, h, oldw, oldh);
}
public void setSizeListener(OnSizeChangedListener sizeListener){
this.sizeListener=sizeListener;
}
@FunctionalInterface
public interface OnSizeChangedListener{
void onSizeChanged(int w, int h, int oldw, int oldh);
}
}

View File

@ -2,8 +2,6 @@ package org.joinmastodon.android.ui.views;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
@ -32,13 +30,6 @@ public class SizeListenerLinearLayout extends LinearLayout{
public void setSizeListener(OnSizeChangedListener sizeListener){
this.sizeListener=sizeListener;
}
//
// @Override
// public View findFocus(){
// View v=super.findFocus();
// Log.w("11", "findFocus() "+v);
// return v;
// }
@FunctionalInterface
public interface OnSizeChangedListener{

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<ripple android:color="@color/highlight_over_dark">
<item>
<shape>
<solid android:color="#58AC81"/>
<corners android:radius="10dp"/>
</shape>
</item>
</ripple>
</item>
</selector>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<ripple android:color="@color/highlight_over_dark">
<ripple android:color="@color/highlight_over_light">
<item>
<shape>
<solid android:color="@color/button_bg_primary_light_on_dark"/>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<ripple android:color="@color/highlight_over_dark">
<ripple android:color="@color/highlight_over_light">
<item>
<shape>
<solid android:color="@color/button_bg_secondary_dark_on_light"/>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:height="2dp" android:gravity="bottom">
<shape>
<solid android:color="?colorPollVoted"/>
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,42 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M15,31c0,2.209 -0.791,4 -3,4H5c-4,0 -4,-14 0,-14h7c2.209,0 3,1.791 3,4v6z"
android:fillColor="#553788"/>
<path
android:pathData="M34,33h-1V23h1c0.553,0 1,-0.447 1,-1s-0.447,-1 -1,-1H10c-4,0 -4,14 0,14h24c0.553,0 1,-0.447 1,-1s-0.447,-1 -1,-1z"
android:fillColor="#9266CC"/>
<path
android:pathData="M34.172,33H11c-2,0 -2,-10 0,-10h23.172c1.104,0 1.104,10 0,10z"
android:fillColor="#CCD6DD"/>
<path
android:pathData="M11.5,25h23.35c-0.135,-1.175 -0.36,-2 -0.678,-2H11c-1.651,0 -1.938,6.808 -0.863,9.188C9.745,29.229 10.199,25 11.5,25z"
android:fillColor="#99AAB5"/>
<path
android:pathData="M12,8c0,2.209 -1.791,4 -4,4H4C0,12 0,1 4,1h4c2.209,0 4,1.791 4,4v3z"
android:fillColor="#269"/>
<path
android:pathData="M31,10h-1V3h1c0.553,0 1,-0.447 1,-1s-0.447,-1 -1,-1H7C3,1 3,12 7,12h24c0.553,0 1,-0.447 1,-1s-0.447,-1 -1,-1z"
android:fillColor="#55ACEE"/>
<path
android:pathData="M31.172,10H8c-2,0 -2,-7 0,-7h23.172c1.104,0 1.104,7 0,7z"
android:fillColor="#CCD6DD"/>
<path
android:pathData="M8,5h23.925c-0.114,-1.125 -0.364,-2 -0.753,-2H8C6.807,3 6.331,5.489 6.562,7.5 6.718,6.142 7.193,5 8,5z"
android:fillColor="#99AAB5"/>
<path
android:pathData="M20,17c0,2.209 -1.791,4 -4,4H6c-4,0 -4,-9 0,-9h10c2.209,0 4,1.791 4,4v1z"
android:fillColor="#F4900C"/>
<path
android:pathData="M35,19h-1v-5h1c0.553,0 1,-0.447 1,-1s-0.447,-1 -1,-1H15c-4,0 -4,9 0,9h20c0.553,0 1,-0.447 1,-1s-0.447,-1 -1,-1z"
android:fillColor="#FFAC33"/>
<path
android:pathData="M35.172,19H16c-2,0 -2,-5 0,-5h19.172c1.104,0 1.104,5 0,5z"
android:fillColor="#CCD6DD"/>
<path
android:pathData="M16,16h19.984c-0.065,-1.062 -0.334,-2 -0.812,-2H16c-1.274,0 -1.733,2.027 -1.383,3.5 0.198,-0.839 0.657,-1.5 1.383,-1.5z"
android:fillColor="#99AAB5"/>
</vector>

View File

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M32.218,10.913c-0.81,-1.187 -2.172,-1.967 -3.718,-1.967H28s-5.353,-5.672 -5.15,-6.091C21.754,3.446 21,4.591 21,5.924c0,0 -5.509,-3.431 -5.247,-3.947C14.71,2.669 14,3.946 14,5.424v0.522S8.553,4.321 8.857,3.851C7.757,4.441 7,5.588 7,6.924V8.81l-6,6.272v2.645l17.16,14.439c0,0.001 13.128,-22.164 14.058,-21.253z"
android:fillColor="#EF9645"/>
<path
android:pathData="M4.124,18.946c1.474,0 2.738,-0.831 3.392,-2.043 0.678,1.212 1.958,2.043 3.446,2.043h0.076c1.153,0 2.169,-0.51 2.889,-1.298 0.023,-0.024 0.073,-0.082 0.073,-0.082 0.185,-0.212 0.343,-0.448 0.481,-0.695 0.04,-0.072 0.281,-0.621 0.342,-0.833 0.052,-0.173 0.106,-0.344 0.134,-0.526 0.141,0.167 0.296,0.319 0.46,0.46 0.069,0.059 0.45,0.339 0.576,0.413 0.589,0.351 1.271,0.56 2.008,0.56h3.166c-0.535,0.27 -0.999,0.614 -1.424,1 -1.729,1.568 -2.579,4.085 -2.579,7.663 0,0.276 0.224,0.5 0.5,0.5s0.5,-0.224 0.5,-0.5c0,-3.962 1.01,-6.427 3.24,-7.663 1.175,-0.651 2.682,-0.967 4.571,-0.967 0.059,0 0.526,-0.033 0.526,-0.033 0.276,0 0.5,-0.224 0.5,-0.5s-0.224,-0.5 -0.5,-0.5H18c-1.657,0 -3,-1.343 -3,-3s1.343,-3 3,-3h11c0.973,0 2.288,0.056 3.218,0.967 0.325,0.318 0.604,0.736 0.803,1.297l1.659,5.472c0.156,0.512 0.73,2.857 0.626,3.346 0,7.34 -8.7,14.972 -19.004,14.972C6.326,36 1,27.883 1,17.957v-0.229c0.01,0.01 0.021,0.016 0.031,0.026 0.881,0.882 1.799,1.192 2.845,1.192h0.248z"
android:fillColor="#FFDC5D"/>
<path
android:pathData="M3.864,5.946h0.271C5.718,5.946 7,7.229 7,8.81v6.272c0,1.582 -1.282,2.864 -2.864,2.864h-0.272C2.282,17.946 1,16.664 1,15.082L1,8.81c0,-1.581 1.282,-2.864 2.864,-2.864zM14,14.946c0,0.891 -0.396,1.683 -1.014,2.233 -0.53,0.472 -1.221,0.767 -1.986,0.767 -1.657,0 -3,-1.343 -3,-3v-9c0,-0.816 0.328,-1.554 0.857,-2.095 0.544,-0.557 1.302,-0.905 2.143,-0.905 1.657,0 3,1.343 3,3v9zM18,8.946c-1.201,0 -2.267,0.541 -3,1.38v-6.38c0,-0.758 0.29,-1.442 0.753,-1.97 0.55,-0.627 1.347,-1.03 2.247,-1.03 1.657,0 3,1.343 3,3v5h-3zM22,4.939c0,-0.812 0.326,-1.545 0.85,-2.085 0.544,-0.559 1.301,-0.909 2.143,-0.909h0.014C26.66,1.946 28,3.286 28,4.939v4.007h-6L22,4.939z"
android:fillColor="#FFDC5D"/>
</vector>

View File

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M18,1C8.059,1 0,7.268 0,15c0,4.368 2.574,8.268 6.604,10.835C6.08,28.144 4.859,31.569 2,35c5.758,-0.96 9.439,-3.761 11.716,-6.416 1.376,0.262 2.805,0.416 4.284,0.416 9.941,0 18,-6.268 18,-14S27.941,1 18,1z"
android:fillColor="#BDDDF4"/>
<path
android:pathData="M18,15m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
android:fillColor="#2A6797"/>
<path
android:pathData="M26,15m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
android:fillColor="#2A6797"/>
<path
android:pathData="M10,15m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
android:fillColor="#2A6797"/>
</vector>

View File

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M18,3.143c-9.941,0 -18,6.908 -18,15.428 0,1.066 0.126,2.107 0.367,3.112C2.146,24.744 3.377,22.812 9,20c5.727,-2.864 0,4 -2,8 -0.615,1.23 -0.282,2.271 0.56,3.124C10.506,32.928 14.104,34 18,34c9.941,0 18,-6.907 18,-15.429 0,-8.52 -8.059,-15.428 -18,-15.428zM20.849,27.59c-0.395,1.346 -2.46,1.924 -4.613,1.291 -2.153,-0.632 -3.578,-2.234 -3.183,-3.581 0.395,-1.346 2.46,-1.924 4.613,-1.29 2.153,0.631 3.578,2.233 3.183,3.58z"
android:fillColor="#D99E82"/>
<path
android:pathData="M10,11m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
android:fillColor="#5C913B"/>
<path
android:pathData="M20,9m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
android:fillColor="#269"/>
<path
android:pathData="M29,15m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
android:fillColor="#DD2E44"/>
<path
android:pathData="M28,24m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
android:fillColor="#FFCC4D"/>
</vector>

View File

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M18,4c7.257,0 13,4 14.699,2 0.197,-0.323 0.301,-0.657 0.301,-1 0,-2 -6.716,-5 -15,-5C9.716,0 3,3 3,5c0,0.343 0.104,0.677 0.301,1C5,8 10.743,4 18,4z"
android:fillColor="#F4900C"/>
<path
android:pathData="M18,3C11.787,3 7.384,4.81 5.727,5.618c-0.477,0.233 -0.539,0.84 -0.415,1.278S16,34 16,34s0.896,2 2,2 2,-2 2,-2L30.704,6.779s0.213,-0.842 -0.569,-1.229C28.392,4.689 24.047,3 18,3z"
android:fillColor="#FFCC4D"/>
<path
android:pathData="M18,31c0,-2.208 -1.791,-4 -4,-4 -0.254,0 -0.5,0.029 -0.741,0.075L16,34s0.071,0.14 0.19,0.342C17.279,33.627 18,32.399 18,31zM17,20c0,-2.209 -1.792,-4 -4,-4 -1.426,0 -2.67,0.752 -3.378,1.876l2.362,5.978c0.327,0.086 0.663,0.146 1.016,0.146 2.208,0 4,-1.792 4,-4z"
android:fillColor="#BE1931"/>
<path
android:pathData="M16,8m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"
android:fillColor="#BE1931"/>
<path
android:pathData="M25,9c-2.208,0 -4,1.791 -4,4s1.792,4 4,4c0.682,0 1.315,-0.187 1.877,-0.488l1.89,-4.806C28.227,10.135 26.752,9 25,9zM19,25c0,1.868 1.288,3.425 3.019,3.864l2.893,-7.357C24.342,21.194 23.697,21 23,21c-2.208,0 -4,1.792 -4,4zM10,12c0,-2.209 -1.791,-4 -4,-4 -0.087,0 -0.169,0.02 -0.255,0.026 0.55,1.412 1.575,4.016 2.775,7.057C9.416,14.349 10,13.248 10,12z"
android:fillColor="#BE1931"/>
</vector>

View File

@ -0,0 +1,54 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M32.325,10.958s2.315,0.024 3.511,1.177c-0.336,-4.971 -2.104,-8.249 -5.944,-10.13 -3.141,-1.119 -6.066,1.453 -6.066,1.453s0.862,-1.99 2.19,-2.746C23.789,0.236 21.146,0 18,0c-3.136,0 -5.785,0.227 -8.006,0.701 1.341,0.745 2.215,2.758 2.215,2.758S9.194,0.803 6,2.053C2.221,3.949 0.481,7.223 0.158,12.174c1.183,-1.19 3.55,-1.215 3.55,-1.215S-0.105,13.267 0.282,16.614c0.387,2.947 1.394,5.967 2.879,8.722C3.039,22.15 5.917,20 5.917,20s-2.492,5.96 -0.581,8.738c1.935,2.542 4.313,4.641 6.976,5.916 -0.955,-1.645 -0.136,-3.044 -0.103,-2.945 0.042,0.125 0.459,3.112 2.137,3.743 1.178,0.356 2.4,0.548 3.654,0.548 1.292,0 2.55,-0.207 3.761,-0.583 1.614,-0.691 2.024,-3.585 2.064,-3.708 0.032,-0.098 0.843,1.287 -0.09,2.921 2.706,-1.309 5.118,-3.463 7.064,-6.073 1.699,-2.846 -0.683,-8.557 -0.683,-8.557s2.85,2.13 2.757,5.288c1.556,-2.906 2.585,-6.104 2.911,-9.2 -0.035,-3.061 -3.459,-5.13 -3.459,-5.13z"
android:fillColor="#662113"/>
<path
android:pathData="M13.859,9.495c0.596,2.392 0.16,4.422 -2.231,5.017 -2.392,0.596 -6.363,0.087 -6.958,-2.304 -0.596,-2.392 0.469,-5.39 1.81,-5.724 1.341,-0.334 6.784,0.62 7.379,3.011zM22.963,27.927c0,2.74 -2.222,4.963 -4.963,4.963s-4.963,-2.223 -4.963,-4.963c0,-2.741 2.223,-4.964 4.963,-4.964 2.741,0 4.963,2.222 4.963,4.964z"
android:fillColor="#FFCC4D"/>
<path
android:pathData="M21.309,27.927c0,1.827 -1.481,3.309 -3.309,3.309s-3.309,-1.481 -3.309,-3.309c0,-1.827 1.481,-3.31 3.309,-3.31s3.309,1.483 3.309,3.31z"
android:fillColor="#DD2E44"/>
<path
android:pathData="M11.052,8.997c0.871,1.393 0.447,3.229 -0.946,4.1 -1.394,0.871 -2.608,0.797 -3.479,-0.596 -0.871,-1.394 -0.186,-4.131 0.324,-4.45 0.51,-0.319 3.23,-0.448 4.101,0.946z"
android:fillColor="#E6AAAA"/>
<path
android:pathData="M22.141,9.495c-0.596,2.392 -0.159,4.422 2.232,5.017 2.392,0.596 6.363,0.087 6.959,-2.304 0.596,-2.392 -0.47,-5.39 -1.811,-5.724 -1.342,-0.334 -6.786,0.62 -7.38,3.011z"
android:fillColor="#FFCC4D"/>
<path
android:pathData="M24.948,8.997c-0.871,1.393 -0.447,3.229 0.945,4.1 1.394,0.871 2.608,0.797 3.479,-0.596 0.871,-1.394 0.185,-4.131 -0.324,-4.45 -0.51,-0.319 -3.229,-0.448 -4.1,0.946z"
android:fillColor="#E6AAAA"/>
<path
android:pathData="M18,7.125h-0.002C5.167,7.126 7.125,12.083 8.5,18.667 9.875,25.25 10.384,27 10.384,27h15.228s0.51,-1.75 1.885,-8.333C28.872,12.083 30.829,7.126 18,7.125z"
android:fillColor="#FFCC4D"/>
<path
android:pathData="M12,16s0,-1.5 1.5,-1.5S15,16 15,16v1.5s0,1.5 -1.5,1.5 -1.5,-1.5 -1.5,-1.5L12,16zM21,16s0,-1.5 1.5,-1.5S24,16 24,16v1.5s0,1.5 -1.5,1.5 -1.5,-1.5 -1.5,-1.5L21,16z"
android:fillColor="#272B2B"/>
<path
android:pathData="M20.168,21.521c-1.598,0 -1.385,0.848 -2.168,2.113 -0.783,-1.266 -0.571,-2.113 -2.168,-2.113 -6.865,0 -6.837,0.375 -6.865,2.828 -0.058,4.986 2.802,6.132 5.257,6.06 1.597,-0.048 2.994,-0.88 3.777,-2.131 0.783,1.251 2.179,2.083 3.776,2.131 2.455,0.072 5.315,-1.073 5.257,-6.06 -0.029,-2.453 -0.001,-2.828 -6.866,-2.828z"
android:fillColor="#FFE8B6"/>
<path
android:pathData="M14.582,21.411c-1.14,0.233 2.279,4.431 3.418,4.431s4.559,-4.198 3.419,-4.431c-1.14,-0.232 -5.698,-0.232 -6.837,0z"
android:fillColor="#272B2B"/>
<path
android:pathData="M11.5,24.5m-0.5,0a0.5,0.5 0,1 1,1 0a0.5,0.5 0,1 1,-1 0"
android:fillColor="#D99E82"/>
<path
android:pathData="M10.5,26.5m-0.5,0a0.5,0.5 0,1 1,1 0a0.5,0.5 0,1 1,-1 0"
android:fillColor="#D99E82"/>
<path
android:pathData="M12.5,27.5m-0.5,0a0.5,0.5 0,1 1,1 0a0.5,0.5 0,1 1,-1 0"
android:fillColor="#D99E82"/>
<path
android:pathData="M24.5,24.5m-0.5,0a0.5,0.5 0,1 1,1 0a0.5,0.5 0,1 1,-1 0"
android:fillColor="#D99E82"/>
<path
android:pathData="M25.5,26.5m-0.5,0a0.5,0.5 0,1 1,1 0a0.5,0.5 0,1 1,-1 0"
android:fillColor="#D99E82"/>
<path
android:pathData="M23.5,27.5m-0.5,0a0.5,0.5 0,1 1,1 0a0.5,0.5 0,1 1,-1 0"
android:fillColor="#D99E82"/>
</vector>

View File

@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M16,9h4v17h-4z"
android:fillColor="#99AAB5"/>
<path
android:pathData="M10,24.5c0,0.828 -0.672,1.5 -1.5,1.5h-3c-0.828,0 -1.5,-0.672 -1.5,-1.5S4.672,23 5.5,23h3c0.828,0 1.5,0.672 1.5,1.5z"
android:fillColor="#DA2F47"/>
<path
android:pathData="M34,33c0,1.104 -0.896,2 -2,2H4c-1.104,0 -2,-0.896 -2,-2v-6c0,-1.104 0.896,-2 2,-2h28c1.104,0 2,0.896 2,2v6z"
android:fillColor="#31373D"/>
<path
android:pathData="M10,34.5c0,0.828 -0.671,1.5 -1.5,1.5h-3c-0.829,0 -1.5,-0.672 -1.5,-1.5S4.671,33 5.5,33h3c0.829,0 1.5,0.672 1.5,1.5zM32,34.5c0,0.828 -0.672,1.5 -1.5,1.5h-3c-0.828,0 -1.5,-0.672 -1.5,-1.5s0.672,-1.5 1.5,-1.5h3c0.828,0 1.5,0.672 1.5,1.5z"
android:fillColor="#31373D"/>
<path
android:pathData="M18,7m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"
android:fillColor="#DA2F47"/>
<path
android:pathData="M25,26c0,1.104 -0.896,2 -2,2H13c-1.104,0 -2,-0.896 -2,-2v-1c0,-1.104 3.896,-6 5,-6h4c1.104,0 5,4.896 5,6v1z"
android:fillColor="#31373D"/>
<path
android:pathData="M33,28c0,0.553 -0.447,1 -1,1H4c-0.552,0 -1,-0.447 -1,-1 0,-0.553 0.448,-1 1,-1h28c0.553,0 1,0.447 1,1z"
android:fillColor="#66757F"/>
</vector>

View File

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M34.453,15.573c-0.864,-7.3 -5.729,-10.447 -13.93,-10.447 -0.391,0 -0.763,0.017 -1.139,0.031 -0.013,-0.01 -0.022,-0.021 -0.035,-0.031C14.655,1.605 4.091,2.779 1.745,6.3c-3.255,4.883 -1.174,22.3 0,24.646 1.173,2.35 4.694,3.521 5.868,2.35 1.174,-1.176 0,-1.176 -1.173,-3.521 -0.85,-1.701 -0.466,-5.859 0.255,-8.471 0.028,0.168 0.068,0.322 0.1,0.486 0.39,2.871 1.993,7.412 1.993,9.744 0,3.564 2.102,4.107 4.694,4.107 2.593,0 4.695,-0.543 4.695,-4.107 0,-0.24 -0.008,-0.463 -0.012,-0.695 0.757,0.064 1.535,0.107 2.359,0.107 0.497,0 0.977,-0.016 1.448,-0.039 -0.004,0.209 -0.013,0.41 -0.013,0.627 0,3.564 2.103,4.107 4.694,4.107 2.593,0 4.695,-0.543 4.695,-4.107 0,-1.801 1.192,-4.625 2.039,-6.982 0.159,-0.354 0.291,-0.732 0.42,-1.117 0.118,1.307 0.193,2.706 0.193,4.206 0,0.553 0.447,1 1,1s1,-0.447 1,-1c0,-5.153 -0.771,-9.248 -1.547,-12.068z"
android:fillColor="#99AAB5"/>
<path
android:pathData="M19.35,5.126S23,10.641 20,15.641c-3,5 -7.838,5 -11,5 -2,0 -1,2 0,2 1.414,0 8.395,1.211 12,-6 3,-6 -1.65,-11.515 -1.65,-11.515z"
android:fillColor="#66757F"/>
<path
android:pathData="M6.5,14.141m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"
android:fillColor="#292F33"/>
</vector>

View File

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M33,36H4c4,0 3,-9 3,-9 0,-2.209 1.791,-15 4,-15h21s4,0 4,4v17s0,3 -3,3z"
android:fillColor="#99AAB5"/>
<path
android:pathData="M30,33c0,3 3,3 3,3H3s-3,0 -3,-4V4c0,-2.209 1.791,-4 4,-4h22c2.209,0 4,1.791 4,4v29z"
android:fillColor="#CCD6DD"/>
<path
android:pathData="M27,20c0,0.553 -0.447,1 -1,1h-8c-0.552,0 -1,-0.447 -1,-1 0,-0.553 0.448,-1 1,-1h8c0.553,0 1,0.447 1,1zM27,16c0,0.553 -0.447,1 -1,1h-8c-0.552,0 -1,-0.447 -1,-1 0,-0.553 0.448,-1 1,-1h8c0.553,0 1,0.447 1,1zM27,12c0,0.553 -0.447,1 -1,1h-8c-0.552,0 -1,-0.447 -1,-1 0,-0.553 0.448,-1 1,-1h8c0.553,0 1,0.447 1,1zM27,24c0,0.553 -0.447,1 -1,1L4,25c-0.552,0 -1,-0.447 -1,-1 0,-0.553 0.448,-1 1,-1h22c0.553,0 1,0.447 1,1zM27,28c0,0.553 -0.447,1 -1,1L4,29c-0.552,0 -1,-0.447 -1,-1 0,-0.553 0.448,-1 1,-1h22c0.553,0 1,0.447 1,1zM27,32c0,0.553 -0.447,1 -1,1L4,33c-0.552,0 -1,-0.447 -1,-1 0,-0.553 0.448,-1 1,-1h22c0.553,0 1,0.447 1,1zM25,9s2,0 2,-2L27,5s0,-2 -2,-2L5,3S3,3 3,5v2s0,2 2,2h20z"
android:fillColor="#99AAB5"/>
<path
android:pathData="M13,21s2,0 2,-2v-6s0,-2 -2,-2H5s-2,0 -2,2v6s0,2 2,2h8z"
android:fillColor="#55ACEE"/>
</vector>

View File

@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M0,27c0,2.209 1.791,4 4,4h28c2.209,0 4,-1.791 4,-4v-0.5H0v0.5z"
android:fillColor="#880082"/>
<path
android:pathData="M0,22.07h36v4.6H0z"
android:fillColor="#3558A0"/>
<path
android:pathData="M0,17.83h36v4.5H0z"
android:fillColor="#138F3E"/>
<path
android:pathData="M0,13.5h36V18H0z"
android:fillColor="#FAD220"/>
<path
android:pathData="M0,9.17h36v4.5H0z"
android:fillColor="#FF5000"/>
<path
android:pathData="M32,5H4C1.791,5 0,6.791 0,9v0.33h36V9c0,-2.209 -1.791,-4 -4,-4z"
android:fillColor="#FF000E"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M36,5V3H0v30h36v-2H6v-5h30v-2H6v-5h30v-2H6v-5h30v-2H6V5z"
android:fillColor="#8899A6"/>
<path
android:pathData="M27.827,20.562c-0.223,-0.646 -0.543,-1.227 -0.947,-1.723 -0.411,-0.513 -0.926,-0.928 -1.529,-1.232 -0.61,-0.31 -1.259,-0.46 -1.981,-0.46 -0.086,0 -0.208,0.009 -0.366,0.026l-0.533,-2.593c0.613,-0.603 1.156,-1.299 1.616,-2.077 0.499,-0.847 0.885,-1.77 1.145,-2.741 0.269,-0.978 0.399,-1.942 0.399,-2.945 0,-2.106 -0.542,-3.94 -1.627,-5.475 -0.158,-0.196 -0.288,-0.343 -0.387,-0.431 -0.394,-0.355 -0.788,-0.412 -1.704,0.266 -0.47,0.352 -0.883,0.86 -1.224,1.506 -0.352,0.644 -0.608,1.366 -0.786,2.2 -0.185,0.826 -0.283,1.723 -0.292,2.684 0.041,1.352 0.234,2.701 0.576,4.013 -0.77,0.779 -1.392,1.43 -1.881,1.951 -0.521,0.569 -1.038,1.222 -1.539,1.943 -0.495,0.711 -0.91,1.493 -1.233,2.324 -0.317,0.807 -0.497,1.693 -0.533,2.655 0.008,0.982 0.195,1.944 0.561,2.864 0.365,0.902 0.884,1.726 1.546,2.45 0.665,0.718 1.448,1.287 2.325,1.694 1.23,0.569 2.644,0.749 4.02,0.51 0.063,0.288 0.146,0.629 0.244,1.022 0.157,0.647 0.274,1.172 0.35,1.56 0.073,0.391 0.109,0.766 0.109,1.122 0,0.541 -0.059,0.955 -0.197,1.301 -0.05,0.202 -0.185,0.412 -0.401,0.62 -0.233,0.227 -0.508,0.401 -0.839,0.533 -0.468,0.184 -0.922,0.231 -1.289,0.181 0.195,-0.105 0.375,-0.241 0.537,-0.405 0.433,-0.443 0.652,-0.988 0.652,-1.622 0,-0.447 -0.109,-0.875 -0.328,-1.282 -0.215,-0.375 -0.508,-0.684 -0.886,-0.929 -0.378,-0.225 -0.778,-0.341 -1.226,-0.341h-0.006c-0.512,0.034 -0.954,0.195 -1.301,0.471 -0.36,0.266 -0.646,0.619 -0.852,1.051 -0.193,0.406 -0.307,0.847 -0.338,1.362 0.04,1.04 0.477,1.885 1.306,2.508 0.784,0.581 1.707,0.877 2.739,0.877 0.743,0 1.434,-0.164 2.053,-0.487 0.634,-0.326 1.148,-0.796 1.529,-1.402 0.37,-0.602 0.559,-1.291 0.559,-2.046 0,-0.495 -0.043,-0.96 -0.129,-1.419 -0.085,-0.458 -0.211,-1.08 -0.384,-1.869 -0.124,-0.535 -0.219,-0.987 -0.288,-1.35 0.965,-0.428 1.712,-1.049 2.226,-1.85 0.576,-0.898 0.867,-1.908 0.867,-3 -0.001,-0.665 -0.112,-1.342 -0.333,-2.015zM24.705,25.74l-1.047,-5.149c0.261,0.056 0.502,0.147 0.727,0.276 0.396,0.224 0.703,0.529 0.945,0.938 0.241,0.39 0.392,0.838 0.456,1.303 0,1.246 -0.354,2.115 -1.081,2.632zM23.547,7.101c-0.181,0.526 -0.43,1.057 -0.743,1.577 -0.307,0.517 -0.629,0.973 -0.959,1.359 -0.105,0.125 -0.202,0.231 -0.288,0.322 -0.078,-0.354 -0.128,-0.746 -0.149,-1.165 0.016,-0.685 0.116,-1.364 0.305,-2.081 0.17,-0.661 0.408,-1.259 0.695,-1.756 0.305,-0.468 0.621,-0.705 0.944,-0.705 0.115,0 0.196,0.028 0.259,0.087 0.07,0.071 0.119,0.208 0.15,0.454 0.03,0.133 0.047,0.303 0.047,0.494 0,0.443 -0.088,0.918 -0.261,1.414zM18.602,18.58c0.653,-0.943 1.452,-1.837 2.431,-2.718l0.339,1.739c-0.717,0.362 -1.321,0.9 -1.801,1.61 -0.552,0.82 -0.846,1.727 -0.872,2.717 0.017,0.913 0.309,1.736 0.871,2.451 0.566,0.708 1.294,1.181 2.169,1.4l0.12,0.03 0.122,-0.029c0.537,-0.127 0.594,-0.471 0.594,-0.613 0,-0.345 -0.224,-0.621 -0.579,-0.72 -0.367,-0.17 -0.637,-0.417 -0.82,-0.758 -0.202,-0.368 -0.309,-0.713 -0.309,-0.994 0,-0.476 0.17,-0.903 0.516,-1.303 0.214,-0.242 0.432,-0.432 0.654,-0.572l1.093,5.508c-0.382,0.081 -0.733,0.132 -1.046,0.152 -1.384,-0.045 -2.518,-0.5 -3.369,-1.35 -0.434,-0.442 -0.747,-0.907 -0.958,-1.423 -0.213,-0.516 -0.327,-1.059 -0.342,-1.584 0.029,-1.27 0.429,-2.463 1.187,-3.543z"
android:fillColor="#31373D"/>
</vector>

View File

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M14,34.5a4,1.5 0,1 0,8 0a4,1.5 0,1 0,-8 0z"
android:fillColor="#292F33"/>
<path
android:pathData="M14.339,10.725S16.894,34.998 18.001,35c1.106,0.001 3.66,-24.275 3.66,-24.275h-7.322z"
android:fillColor="#99AAB5"/>
<path
android:pathData="M18,8m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"
android:fillColor="#DD2E44"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M11,36s-4,0 -4,-4V4s0,-4 4,-4h14s4,0 4,4v28s0,4 -4,4H11z"
android:fillColor="#31373D"/>
<path
android:pathData="M9,5h18v26H9z"
android:fillColor="#55ACEE"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M17,27c-1.657,0 -3,-1.343 -3,-3v-4c0,-1.657 1.343,-3 3,-3 0.603,-0.006 6,-1 6,-5 0,-2 -2,-4 -5,-4 -2.441,0 -4,2 -4,3 0,1.657 -1.343,3 -3,3s-3,-1.343 -3,-3c0,-4.878 4.58,-9 10,-9 8,0 11,5.982 11,11 0,4.145 -2.277,7.313 -6.413,8.92 -0.9,0.351 -1.79,0.587 -2.587,0.747V24c0,1.657 -1.343,3 -3,3z"
android:fillColor="#BE1931"/>
<path
android:pathData="M17,32m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
android:fillColor="#BE1931"/>
</vector>

View File

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="16dp" android:height="16dp" android:viewportWidth="16" android:viewportHeight="16">
<path android:pathData="M12.5 1.996c0.276 0 0.5 0.224 0.5 0.5v2.506h0.5c0.276 0 0.5 0.224 0.5 0.5 0 0.277-0.224 0.5-0.5 0.5H13V10.5c0 0.276-0.224 0.5-0.5 0.5S12 10.776 12 10.5V2.496c0-0.276 0.224-0.5 0.5-0.5zm-5 1H10V4.25c0 0.413-0.158 0.708-0.4 0.908C9.35 5.367 8.97 5.5 8.504 5.5c-0.276 0-0.5 0.224-0.5 0.5s0.224 0.5 0.5 0.5c0.648 0 1.268-0.184 1.735-0.572C10.717 5.532 11 4.952 11 4.25V2.496c0-0.132-0.053-0.26-0.146-0.353-0.094-0.094-0.221-0.147-0.354-0.147h-3c-0.276 0-0.5 0.224-0.5 0.5s0.224 0.5 0.5 0.5zM6.454 5.3C6.374 5.117 6.194 5 5.996 5 5.797 5 5.617 5.119 5.538 5.3l-3.496 8c-0.11 0.254 0.005 0.548 0.258 0.659 0.253 0.11 0.548-0.005 0.658-0.258l1.18-2.7H7.86l1.183 2.7c0.11 0.253 0.406 0.369 0.659 0.258 0.253-0.111 0.368-0.406 0.257-0.659l-3.504-8zM7.421 10H4.575l1.422-3.252L7.42 10z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="16dp" android:height="16dp" android:viewportWidth="16" android:viewportHeight="16">
<path android:pathData="M3 3c0-1.105 0.895-2 2-2s2 0.895 2 2c0 0.645-0.305 1.218-0.779 1.584L6.18 4.616C5.849 4.857 5.44 5 5 5 3.895 5 3 4.105 3 3zm2-1C4.448 2 4 2.448 4 3s0.448 1 1 1 1-0.448 1-1-0.448-1-1-1zm4.779 2.584C9.305 4.218 9 3.644 9 3c0-1.105 0.895-2 2-2s2 0.895 2 2-0.895 2-2 2c-0.44 0-0.848-0.143-1.179-0.384L9.78 4.584zM11 2c-0.552 0-1 0.448-1 1s0.448 1 1 1 1-0.448 1-1-0.448-1-1-1zM2.5 6h2.67C5.06 6.313 5 6.65 5 7H2.5C2.224 7 2 7.224 2 7.5c0 0.817 0.325 1.423 0.838 1.835 0.236 0.19 0.519 0.343 0.839 0.454-0.232 0.248-0.415 0.543-0.531 0.869-0.34-0.139-0.655-0.32-0.934-0.543C1.46 9.51 1 8.616 1 7.5 1 6.672 1.672 6 2.5 6zm3.768 0C6.098 6.294 6 6.636 6 7c0 0.843 0.522 1.565 1.261 1.859C7.49 8.95 7.739 9 8 9s0.51-0.05 0.739-0.141C9.478 8.565 10 7.843 10 7c0-0.364-0.097-0.706-0.268-1C9.387 5.402 8.74 5 8 5S6.613 5.402 6.268 6zm1.508 0.025C7.848 6.01 7.923 6 8 6c0.077 0 0.152 0.009 0.224 0.025C8.67 6.127 9 6.525 9 7c0 0.077-0.009 0.152-0.025 0.224C8.873 7.67 8.475 8 8 8S7.127 7.669 7.025 7.224C7.01 7.152 7 7.077 7 7c0-0.475 0.331-0.873 0.776-0.975zm5.386 3.31c-0.236 0.19-0.519 0.343-0.839 0.454 0.232 0.248 0.415 0.543 0.531 0.869 0.34-0.139 0.655-0.32 0.934-0.543C14.54 9.51 15 8.616 15 7.5 15 6.672 14.328 6 13.5 6h-2.67C10.94 6.313 11 6.65 11 7h2.5C13.776 7 14 7.224 14 7.5c0 0.817-0.325 1.423-0.838 1.835zM10.5 10c0.626 0 1.162 0.383 1.387 0.928C11.96 11.104 12 11.298 12 11.5c0 1.116-0.459 2.01-1.212 2.615C10.047 14.71 9.053 15 8 15c-1.053 0-2.047-0.29-2.788-0.885C4.46 13.51 4 12.616 4 11.5c0-0.203 0.04-0.396 0.113-0.572C4.338 10.383 4.874 10 5.5 10h5zm0 1h-5C5.224 11 5 11.224 5 11.5c0 0.817 0.324 1.423 0.838 1.835C6.364 13.757 7.12 14 8 14c0.88 0 1.636-0.243 2.162-0.665C10.675 12.923 11 12.317 11 11.5c0-0.276-0.224-0.5-0.5-0.5z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
<path android:pathData="M8.5 3C11.538 3 14 5.462 14 8.5c0 1.339-0.478 2.566-1.273 3.52l4.127 4.126c0.195 0.196 0.195 0.512 0 0.708-0.174 0.173-0.443 0.192-0.638 0.057l-0.07-0.057-4.126-4.127C11.066 13.522 9.839 14 8.5 14 5.462 14 3 11.538 3 8.5S5.462 3 8.5 3zm0 1C6.015 4 4 6.015 4 8.5S6.015 13 8.5 13 13 10.985 13 8.5 10.985 4 8.5 4z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:drawable="@drawable/ic_fluent_checkmark_circle_24_filled"/>
<item android:state_checked="true" android:drawable="@drawable/ic_fluent_checkmark_circle_24_filled"/>
<item android:drawable="@drawable/ic_fluent_radio_button_24_regular"/>
</selector>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<me.grishka.appkit.views.FragmentRootLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="?colorBackgroundLight">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:clipChildren="false">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_headline_medium"
android:minHeight="36dp"
android:layout_marginTop="32dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="12dp"
android:gravity="center_vertical"
android:text="@string/confirm_email_title"/>
<TextView
android:id="@+id/subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:textAppearance="@style/m3_title_medium"
android:textColor="?android:textColorSecondary"
android:text="@string/confirm_email_subtitle"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="32dp"
android:src="@drawable/confirm_email_art"/>
</LinearLayout>
</ScrollView>
<LinearLayout
android:id="@+id/button_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorBackgroundLight"
android:outlineProvider="bounds"
android:orientation="horizontal"
android:clipToPadding="false"
android:elevation="3dp">
<Button
android:id="@+id/btn_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
style="?secondaryLargeButtonStyle"
android:text="@string/resend"/>
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"/>
<Button
android:id="@+id/btn_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
style="?primaryLargeButtonStyle"
android:text="@string/open_email_app" />
</LinearLayout>
</me.grishka.appkit.views.FragmentRootLinearLayout>

View File

@ -6,9 +6,7 @@
android:orientation="vertical"
android:id="@+id/appkit_loader_root"
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="?android:windowBackground">
<include layout="@layout/appkit_toolbar"/>
android:background="?colorBackgroundLight">
<FrameLayout
android:id="@+id/appkit_loader_content"
@ -31,10 +29,37 @@
android:id="@+id/content_stub"/>
</FrameLayout>
<Button
android:id="@+id/btn_next"
<LinearLayout
android:id="@+id/button_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
android:text="@string/next"/>
android:background="?colorBackgroundLight"
android:outlineProvider="bounds"
android:orientation="horizontal"
android:elevation="3dp">
<Button
android:id="@+id/btn_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:minWidth="145dp"
style="?secondaryLargeButtonStyle"
android:text="@string/back"/>
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"/>
<Button
android:id="@+id/btn_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:minWidth="145dp"
style="?primaryLargeButtonStyle"
android:text="@string/next" />
</LinearLayout>
</me.grishka.appkit.views.FragmentRootLinearLayout>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<me.grishka.appkit.views.FragmentRootLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<me.grishka.appkit.views.UsableRecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="?colorBackgroundLight"/>
<LinearLayout
android:id="@+id/button_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorBackgroundLight"
android:outlineProvider="bounds"
android:orientation="horizontal"
android:elevation="3dp">
<Button
android:id="@+id/btn_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:minWidth="145dp"
style="?secondaryLargeButtonStyle"
android:text="@string/back"/>
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"/>
<Button
android:id="@+id/btn_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:minWidth="145dp"
style="?primaryLargeButtonStyle"
android:text="@string/next" />
</LinearLayout>
</me.grishka.appkit.views.FragmentRootLinearLayout>

View File

@ -0,0 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<me.grishka.appkit.views.FragmentRootLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="?colorBackgroundLight">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:clipChildren="false">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:textAppearance="@style/m3_headline_medium"
android:minHeight="36dp"
android:layout_marginTop="32dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:gravity="center_vertical"
tools:text="@string/signup_title"/>
<FrameLayout
android:id="@+id/ava_wrap"
android:layout_width="88dp"
android:layout_height="88dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="24dp">
<ImageView
android:id="@+id/avatar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/default_avatar"/>
<TextView
android:layout_width="match_parent"
android:layout_height="22dp"
android:layout_gravity="bottom"
android:gravity="center"
android:background="@color/gray_800t"
android:textAppearance="@style/m3_label_large"
android:textColor="#eee"
android:text="@string/edit_photo"/>
</FrameLayout>
<EditText
android:id="@+id/display_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:inputType="textPersonName|textCapWords"
android:autofillHints="name"
android:singleLine="true"
android:hint="@string/display_name"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="24dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp">
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textFilter|textNoSuggestions"
android:autofillHints="username"
android:singleLine="true"
android:layoutDirection="ltr"
android:hint="@string/username"/>
<TextView
android:id="@+id/domain"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="5dp"
android:layout_gravity="right|center_vertical"
android:paddingLeft="8dp"
android:paddingRight="16dp"
android:textAppearance="@style/m3_title_medium"
tools:text="\@mastodon.social"/>
</FrameLayout>
<EditText
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="8dp"
android:inputType="textEmailAddress"
android:autofillHints="emailAddress"
android:singleLine="true"
android:hint="@string/email"/>
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:inputType="textPassword"
android:autofillHints="password"
android:singleLine="true"
android:fontFamily="sans-serif"
android:hint="@string/password"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:textAppearance="@style/m3_body_medium"
android:textColor="?android:textColorSecondary"
android:text="@string/password_note"/>
</LinearLayout>
</ScrollView>
<LinearLayout
android:id="@+id/button_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorBackgroundLight"
android:outlineProvider="bounds"
android:orientation="horizontal"
android:elevation="3dp">
<Button
android:id="@+id/btn_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:minWidth="145dp"
style="?secondaryLargeButtonStyle"
android:text="@string/back"/>
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"/>
<Button
android:id="@+id/btn_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:minWidth="145dp"
style="?primaryLargeButtonStyle"
android:text="@string/next" />
</LinearLayout>
</me.grishka.appkit.views.FragmentRootLinearLayout>

View File

@ -1,22 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
<org.joinmastodon.android.ui.views.SizeListenerFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:gravity="bottom"
android:background="#808080">
android:clipToPadding="false"
android:clipChildren="false">
<Button
android:id="@+id/btn_get_started"
<View
android:id="@+id/blue_fill"
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_gravity="top"
android:transformPivotY="0px"
android:background="#50D5ED"/>
<FrameLayout
android:id="@+id/art_container"
android:layout_width="450dp"
android:layout_height="631dp"
android:layout_marginTop="40dp"
android:layout_gravity="center">
<ImageView
android:id="@+id/art_clouds"
android:layout_width="450dp"
android:layout_height="589dp"
android:layout_gravity="bottom|center_horizontal"
android:src="@drawable/splash_art_layer0"/>
<ImageView
android:id="@+id/art_right_hill"
android:layout_width="218dp"
android:layout_height="255dp"
android:layout_gravity="bottom|right"
android:layout_marginBottom="156dp"
android:layout_marginRight="11dp"
android:src="@drawable/splash_art_layer1"/>
<ImageView
android:id="@+id/art_left_hill"
android:layout_width="285dp"
android:layout_height="222dp"
android:layout_gravity="bottom|left"
android:layout_marginLeft="-6dp"
android:layout_marginBottom="243dp"
android:src="@drawable/splash_art_layer2"/>
<ImageView
android:id="@+id/art_center_hill"
android:layout_width="457dp"
android:layout_height="397dp"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="51dp"
android:src="@drawable/splash_art_layer3"/>
<ImageView
android:id="@+id/art_plane_elephant"
android:layout_width="355dp"
android:layout_height="105dp"
android:layout_gravity="left|top"
android:src="@drawable/splash_art_layer4"/>
</FrameLayout>
<View
android:id="@+id/green_fill"
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_gravity="bottom"
android:transformPivotY="1px"
android:background="#478E6A"/>
<ImageView
android:layout_width="261dp"
android:layout_height="67dp"
android:layout_gravity="center_horizontal|top"
android:layout_marginTop="24dp"
android:src="@drawable/splash_logo"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/get_started"/>
<Button
android:id="@+id/btn_log_in"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/log_in"/>
android:layout_gravity="bottom"
android:padding="16dp"
android:clipToPadding="false"
android:orientation="vertical">
<Button
android:id="@+id/btn_get_started"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="13dp"
style="@style/Widget.Mastodon.Button.Large.Primary_LightOnDark"
android:text="@string/get_started"/>
<Button
android:id="@+id/btn_log_in"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.Mastodon.Button.Large.Primary_DarkOnLight"
android:background="@drawable/bg_button_green"
android:text="@string/log_in"/>
</LinearLayout>
</LinearLayout>
</org.joinmastodon.android.ui.views.SizeListenerFrameLayout>

View File

@ -1,24 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="title text"/>
android:layout_marginBottom="12dp"
android:layout_marginTop="32dp"
android:layout_marginLeft="19dp"
android:layout_marginRight="19dp"
android:textAppearance="@style/m3_headline_medium"
android:minHeight="36dp"
android:gravity="center_vertical"
android:text="@string/instance_catalog_title"/>
<TextView
android:id="@+id/subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="explanation text"/>
android:layout_marginLeft="19dp"
android:layout_marginRight="19dp"
android:layout_marginBottom="24dp"
android:textAppearance="@style/m3_title_medium"
android:textColor="?android:textColorSecondary"
android:text="@string/instance_catalog_subtitle"/>
<me.grishka.appkit.views.UsableRecyclerView
<org.joinmastodon.android.ui.tabs.TabLayout
android:id="@+id/categories_list"
android:layout_width="match_parent"
android:layout_height="50dp"/>
android:layout_height="72dp"
android:background="@drawable/bg_catalog_tabs"
app:tabIndicator="@drawable/mtrl_tabs_default_indicator"
app:tabIndicatorAnimationMode="elastic"
app:tabIndicatorColor="?android:textColorPrimary"
app:tabMinWidth="120dp"
app:tabMaxWidth="120dp"
app:tabMode="scrollable"/>
<EditText
android:id="@+id/search_edit"
@ -27,6 +49,13 @@
android:inputType="textFilter"
android:singleLine="true"
android:imeOptions="actionGo"
android:hint="search"/>
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="19dp"
android:layout_marginBottom="3dp"
android:drawableStart="@drawable/ic_fluent_search_20_regular"
android:drawablePadding="8dp"
android:drawableTint="?android:textColorSecondary"
android:hint="@string/search_communities"/>
</LinearLayout>

View File

@ -3,24 +3,31 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
android:padding="16dp">
<RadioButton
android:id="@+id/radiobtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="16dp"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:button="@drawable/ic_round_checkbox"
android:buttonTint="?android:textColorSecondary"
android:background="@null"
android:clickable="false"/>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="24dp"
android:layout_toEndOf="@id/radiobtn"
android:layout_alignParentTop="true"
android:textStyle="bold"
android:layout_marginBottom="4dp"
android:textAppearance="@style/m3_title_medium"
android:singleLine="true"
android:ellipsize="end"
android:gravity="center_vertical"
tools:text="mastodon.social"/>
<TextView
@ -29,6 +36,9 @@
android:layout_height="wrap_content"
android:layout_toEndOf="@id/radiobtn"
android:layout_below="@id/title"
android:layout_marginBottom="8dp"
android:textAppearance="@style/m3_body_medium"
android:textColor="?android:textColorSecondary"
tools:text="General-purpose server run by the lead developer of Mastodon"/>
<TextView
@ -37,6 +47,11 @@
android:layout_height="wrap_content"
android:layout_toEndOf="@id/radiobtn"
android:layout_below="@id/description"
android:textAppearance="@style/m3_label_medium"
android:textColor="?android:textColorSecondary"
android:drawableStart="@drawable/ic_fluent_people_community_16_regular"
android:drawableTint="?android:textColorSecondary"
android:drawablePadding="8dp"
tools:text="588.8K"/>
<TextView
@ -45,7 +60,12 @@
android:layout_height="wrap_content"
android:layout_toEndOf="@id/user_count"
android:layout_below="@id/description"
android:layout_marginStart="8dp"
android:layout_marginStart="24dp"
android:textAppearance="@style/m3_label_medium"
android:textColor="?android:textColorSecondary"
android:drawableStart="@drawable/ic_fluent_local_language_16_regular"
android:drawableTint="?android:textColorSecondary"
android:drawablePadding="8dp"
tools:text="EN"/>
</RelativeLayout>

View File

@ -1,14 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp">
android:layout_width="120dp"
android:layout_height="72dp"
android:paddingTop="13dp"
android:paddingBottom="13dp">
<RadioButton
android:id="@+id/radiobtn"
<ImageView
android:id="@+id/emoji"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginBottom="6dp"
android:layout_gravity="center_horizontal"/>
<TextView
android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"/>
android:layout_height="16dp"
android:layout_gravity="center_horizontal"
android:textAllCaps="true"
android:textColor="?android:textColorPrimary"
android:fontFamily="sans-serif-medium"
android:textSize="14dp"
android:gravity="center_vertical"
android:singleLine="true"
android:ellipsize="end"
tools:text="All"/>
</LinearLayout>

View File

@ -90,7 +90,8 @@
<color name="actionbar_bg">#FAFBFC</color>
<color name="actionbar_bg_dark">#4C4F58</color>
<color name="navigation_bar_bg">#282C37</color>
<color name="highlight_over_dark">#80FFFFFF</color>
<color name="highlight_over_dark">#30FFFFFF</color>
<color name="highlight_over_light">#18000000</color>
<color name="favorite_selected">@color/warning_500</color>
<color name="boost_selected">@color/primary_500</color>

View File

@ -172,4 +172,35 @@
<string name="block_user_explain">They will no longer be able to follow or see your posts, but thye can see if they\'ve been blocked.</string>
<string name="report_personal_title">Don\'t want to see this?</string>
<string name="report_personal_subtitle">When you see something you don\'t like on Mastodon, you can remove the person from your experience.</string>
<string name="back">Back</string>
<string name="instance_catalog_title">Mastodon is made of users in different communities.</string>
<string name="instance_catalog_subtitle">Pick a community based on your interests, region, or a general purpose one. You can still connect with everyone, regardless of community.</string>
<string name="search_communities">Search communities</string>
<string name="instance_rules_title">Some ground rules</string>
<string name="instance_rules_subtitle">Take a minute to review the rules set and enforced by %s admins.</string>
<string name="signup_title">Let\'s get you set up on %s</string>
<string name="edit_photo">edit</string>
<string name="display_name">display name</string>
<string name="username">username</string>
<string name="email">email</string>
<string name="password">password</string>
<string name="password_note">Include capital letters, special characters, and numbers to increase your password strength.</string>
<string name="category_academia">Academia</string>
<string name="category_activism">Activism</string>
<string name="category_all">All</string>
<string name="category_art">Art</string>
<string name="category_food">Food</string>
<string name="category_furry">Furry</string>
<string name="category_games">Games</string>
<string name="category_general">General</string>
<string name="category_journalism">Journalism</string>
<string name="category_lgbt">LGBT</string>
<string name="category_music">Music</string>
<string name="category_regional">Regional</string>
<string name="category_tech">Tech</string>
<string name="confirm_email_title">One last thing</string>
<string name="confirm_email_subtitle">Tap the link we emailed to you to verify your account.</string>
<string name="resend">Resend</string>
<string name="open_email_app">Open email app</string>
<string name="resent_email">Confirmation email sent</string>
</resources>