diff --git a/mastodon/build.gradle b/mastodon/build.gradle index 5aeb6a5d..de0a7b6f 100644 --- a/mastodon/build.gradle +++ b/mastodon/build.gradle @@ -10,7 +10,7 @@ android { applicationId "org.joinmastodon.android" minSdk 23 targetSdk 31 - versionCode 7 + versionCode 8 versionName "0.1" } diff --git a/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java b/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java index d736f043..46312ac9 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java +++ b/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java @@ -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); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/OAuthActivity.java b/mastodon/src/main/java/org/joinmastodon/android/OAuthActivity.java index f22cf76e..55721d7c 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/OAuthActivity.java +++ b/mastodon/src/main/java/org/joinmastodon/android/OAuthActivity.java @@ -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) diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java index e6a8c696..74d4bd16 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java @@ -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()); diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/RegisterAccount.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/RegisterAccount.java new file mode 100644 index 00000000..7656d6d4 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/RegisterAccount.java @@ -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{ + 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; + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/ResendConfirmationEmail.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/ResendConfirmationEmail.java new file mode 100644 index 00000000..f1058eeb --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/ResendConfirmationEmail.java @@ -0,0 +1,19 @@ +package org.joinmastodon.android.api.requests.accounts; + +import org.joinmastodon.android.api.MastodonAPIRequest; + +public class ResendConfirmationEmail extends MastodonAPIRequest{ + 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; + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/UpdateAccountCredentials.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/UpdateAccountCredentials.java index 73bb1885..0bd821d7 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/UpdateAccountCredentials.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/UpdateAccountCredentials.java @@ -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{ private String displayName, bio; private Uri avatar, cover; + private File avatarFile, coverFile; private List fields; public UpdateAccountCredentials(String displayName, String bio, Uri avatar, Uri cover, List fields){ @@ -27,6 +29,15 @@ public class UpdateAccountCredentials extends MastodonAPIRequest{ this.fields=fields; } + public UpdateAccountCredentials(String displayName, String bio, File avatar, File cover, List 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{ 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]", ""); diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/oauth/GetOauthToken.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/oauth/GetOauthToken.java index 26b429eb..dde85af1 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/oauth/GetOauthToken.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/oauth/GetOauthToken.java @@ -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{ - 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{ } 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 + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java index ec37a541..fecfd9a6 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java @@ -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(); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java index ea100444..bd7b98a4 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java @@ -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(){ + .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); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java index 4dcc8345..4551d054 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java @@ -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; diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java index b597101a..2c8408a1 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java @@ -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; diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ScrollableToTop.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ScrollableToTop.java index 0eaf3adf..f5e753f9 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ScrollableToTop.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ScrollableToTop.java @@ -1,5 +1,5 @@ package org.joinmastodon.android.fragments; -/*package*/ interface ScrollableToTop{ +public interface ScrollableToTop{ void scrollToTop(); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java index fe950df4..9ad3532c 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java @@ -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 && bottomInsetonButtonClick()); + 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); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceCatalogFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceCatalogFragment.java index 4e04f2db..50ed1dfe 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceCatalogFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceCatalogFragment.java @@ -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{ @@ -59,7 +66,7 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment 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 instancesCache=new HashMap<>(); private ProgressDialog instanceProgressDialog; + private View buttonBar; private boolean isSignup; @@ -145,8 +153,14 @@ public class InstanceCatalogFragment extends BaseRecyclerFragmentcc.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 BaseRecyclerFragmentNav.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 "💬"; +// 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=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{ public InstancesAdapter(){ super(imgLoader); @@ -399,13 +508,17 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment{ - @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 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(); - } - } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceRulesFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceRulesFragment.java new file mode 100644 index 00000000..c464e8eb --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceRulesFragment.java @@ -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{ + + @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{ + 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); + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/SignupFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/SignupFragment.java new file mode 100644 index 00000000..1000fe4e --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/SignupFragment.java @@ -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); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseReportChoiceFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/BaseReportChoiceFragment.java similarity index 99% rename from mastodon/src/main/java/org/joinmastodon/android/fragments/BaseReportChoiceFragment.java rename to mastodon/src/main/java/org/joinmastodon/android/fragments/report/BaseReportChoiceFragment.java index bdf75c21..cfa110e3 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseReportChoiceFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/BaseReportChoiceFragment.java @@ -1,4 +1,4 @@ -package org.joinmastodon.android.fragments; +package org.joinmastodon.android.fragments.report; import android.app.Activity; import android.os.Build; diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ReportAddPostsChoiceFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java similarity index 98% rename from mastodon/src/main/java/org/joinmastodon/android/fragments/ReportAddPostsChoiceFragment.java rename to mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java index 44ece59e..e68ad564 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ReportAddPostsChoiceFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java @@ -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; diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ReportCommentFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportCommentFragment.java similarity index 98% rename from mastodon/src/main/java/org/joinmastodon/android/fragments/ReportCommentFragment.java rename to mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportCommentFragment.java index 63a8942c..01ef0323 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ReportCommentFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportCommentFragment.java @@ -1,4 +1,4 @@ -package org.joinmastodon.android.fragments; +package org.joinmastodon.android.fragments.report; import android.app.Activity; import android.os.Build; diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ReportDoneFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportDoneFragment.java similarity index 94% rename from mastodon/src/main/java/org/joinmastodon/android/fragments/ReportDoneFragment.java rename to mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportDoneFragment.java index a2077b11..096ebae3 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ReportDoneFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportDoneFragment.java @@ -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; diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ReportReasonChoiceFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportReasonChoiceFragment.java similarity index 97% rename from mastodon/src/main/java/org/joinmastodon/android/fragments/ReportReasonChoiceFragment.java rename to mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportReasonChoiceFragment.java index 6fd8cb3d..190ddb56 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ReportReasonChoiceFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportReasonChoiceFragment.java @@ -1,4 +1,4 @@ -package org.joinmastodon.android.fragments; +package org.joinmastodon.android.fragments.report; import android.os.Bundle; diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ReportRuleChoiceFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportRuleChoiceFragment.java similarity index 96% rename from mastodon/src/main/java/org/joinmastodon/android/fragments/ReportRuleChoiceFragment.java rename to mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportRuleChoiceFragment.java index d6d1a685..cd61bf81 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ReportRuleChoiceFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportRuleChoiceFragment.java @@ -1,4 +1,4 @@ -package org.joinmastodon.android.fragments; +package org.joinmastodon.android.fragments.report; import android.os.Bundle; diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java b/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java index 21d040f5..05009877 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java @@ -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 CREATOR=new Parcelable.Creator(){ - @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 CREATOR=new Parcelable.Creator(){ - @Override - public Stats createFromParcel(Parcel source){ - return new Stats(source); - } - - @Override - public Stats[] newArray(int size){ - return new Stats[size]; - } - }; } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/DividerItemDecoration.java b/mastodon/src/main/java/org/joinmastodon/android/ui/DividerItemDecoration.java index da710419..7a8bf605 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/DividerItemDecoration.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/DividerItemDecoration.java @@ -44,7 +44,8 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration{ View child=parent.getChildAt(i); int pos=parent.getChildAdapterPosition(child); if(pos 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; i1f){ + 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)); + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java index 72a5e73c..e21f7351 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java @@ -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; diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java index 8d684e60..7b4167d5 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java @@ -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)) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/SizeListenerFrameLayout.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/SizeListenerFrameLayout.java new file mode 100644 index 00000000..3834d212 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/SizeListenerFrameLayout.java @@ -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); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/SizeListenerLinearLayout.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/SizeListenerLinearLayout.java index 8a3a568b..920f92b8 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/views/SizeListenerLinearLayout.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/SizeListenerLinearLayout.java @@ -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{ diff --git a/mastodon/src/main/res/drawable-mdpi/splash_logo.webp b/mastodon/src/main/res/drawable-mdpi/splash_logo.webp new file mode 100644 index 00000000..c1e14d87 Binary files /dev/null and b/mastodon/src/main/res/drawable-mdpi/splash_logo.webp differ diff --git a/mastodon/src/main/res/drawable-nodpi/default_avatar.webp b/mastodon/src/main/res/drawable-nodpi/default_avatar.webp new file mode 100644 index 00000000..9974d95a Binary files /dev/null and b/mastodon/src/main/res/drawable-nodpi/default_avatar.webp differ diff --git a/mastodon/src/main/res/drawable-nodpi/splash_art_layer0.webp b/mastodon/src/main/res/drawable-nodpi/splash_art_layer0.webp new file mode 100644 index 00000000..b49576f6 Binary files /dev/null and b/mastodon/src/main/res/drawable-nodpi/splash_art_layer0.webp differ diff --git a/mastodon/src/main/res/drawable-nodpi/splash_art_layer1.webp b/mastodon/src/main/res/drawable-nodpi/splash_art_layer1.webp new file mode 100644 index 00000000..66d29db8 Binary files /dev/null and b/mastodon/src/main/res/drawable-nodpi/splash_art_layer1.webp differ diff --git a/mastodon/src/main/res/drawable-nodpi/splash_art_layer2.webp b/mastodon/src/main/res/drawable-nodpi/splash_art_layer2.webp new file mode 100644 index 00000000..63b2be0a Binary files /dev/null and b/mastodon/src/main/res/drawable-nodpi/splash_art_layer2.webp differ diff --git a/mastodon/src/main/res/drawable-nodpi/splash_art_layer3.webp b/mastodon/src/main/res/drawable-nodpi/splash_art_layer3.webp new file mode 100644 index 00000000..c3ce8346 Binary files /dev/null and b/mastodon/src/main/res/drawable-nodpi/splash_art_layer3.webp differ diff --git a/mastodon/src/main/res/drawable-nodpi/splash_art_layer4.webp b/mastodon/src/main/res/drawable-nodpi/splash_art_layer4.webp new file mode 100644 index 00000000..cd7ac5ee Binary files /dev/null and b/mastodon/src/main/res/drawable-nodpi/splash_art_layer4.webp differ diff --git a/mastodon/src/main/res/drawable-xhdpi/confirm_email_art.webp b/mastodon/src/main/res/drawable-xhdpi/confirm_email_art.webp new file mode 100644 index 00000000..696ade02 Binary files /dev/null and b/mastodon/src/main/res/drawable-xhdpi/confirm_email_art.webp differ diff --git a/mastodon/src/main/res/drawable-xhdpi/splash_logo.webp b/mastodon/src/main/res/drawable-xhdpi/splash_logo.webp new file mode 100644 index 00000000..2fb3893d Binary files /dev/null and b/mastodon/src/main/res/drawable-xhdpi/splash_logo.webp differ diff --git a/mastodon/src/main/res/drawable-xxhdpi/splash_logo.webp b/mastodon/src/main/res/drawable-xxhdpi/splash_logo.webp new file mode 100644 index 00000000..cb53038e Binary files /dev/null and b/mastodon/src/main/res/drawable-xxhdpi/splash_logo.webp differ diff --git a/mastodon/src/main/res/drawable-xxxhdpi/confirm_email_art.webp b/mastodon/src/main/res/drawable-xxxhdpi/confirm_email_art.webp new file mode 100644 index 00000000..1c1c046e Binary files /dev/null and b/mastodon/src/main/res/drawable-xxxhdpi/confirm_email_art.webp differ diff --git a/mastodon/src/main/res/drawable-xxxhdpi/splash_logo.webp b/mastodon/src/main/res/drawable-xxxhdpi/splash_logo.webp new file mode 100644 index 00000000..b31d361b Binary files /dev/null and b/mastodon/src/main/res/drawable-xxxhdpi/splash_logo.webp differ diff --git a/mastodon/src/main/res/drawable/bg_button_green.xml b/mastodon/src/main/res/drawable/bg_button_green.xml new file mode 100644 index 00000000..83f39926 --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_button_green.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/bg_button_primary_light_on_dark.xml b/mastodon/src/main/res/drawable/bg_button_primary_light_on_dark.xml index d5622755..cebd59ec 100644 --- a/mastodon/src/main/res/drawable/bg_button_primary_light_on_dark.xml +++ b/mastodon/src/main/res/drawable/bg_button_primary_light_on_dark.xml @@ -1,7 +1,7 @@ - + diff --git a/mastodon/src/main/res/drawable/bg_button_secondary_dark_on_light.xml b/mastodon/src/main/res/drawable/bg_button_secondary_dark_on_light.xml index 6867808c..5db8670c 100644 --- a/mastodon/src/main/res/drawable/bg_button_secondary_dark_on_light.xml +++ b/mastodon/src/main/res/drawable/bg_button_secondary_dark_on_light.xml @@ -1,7 +1,7 @@ - + diff --git a/mastodon/src/main/res/drawable/bg_catalog_tabs.xml b/mastodon/src/main/res/drawable/bg_catalog_tabs.xml new file mode 100644 index 00000000..452039e2 --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_catalog_tabs.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/ic_category_academia.xml b/mastodon/src/main/res/drawable/ic_category_academia.xml new file mode 100644 index 00000000..4e0ac054 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_category_academia.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + diff --git a/mastodon/src/main/res/drawable/ic_category_activism.xml b/mastodon/src/main/res/drawable/ic_category_activism.xml new file mode 100644 index 00000000..5001d06f --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_category_activism.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/mastodon/src/main/res/drawable/ic_category_all.xml b/mastodon/src/main/res/drawable/ic_category_all.xml new file mode 100644 index 00000000..f94bf550 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_category_all.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/mastodon/src/main/res/drawable/ic_category_art.xml b/mastodon/src/main/res/drawable/ic_category_art.xml new file mode 100644 index 00000000..f0b8d0bc --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_category_art.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/mastodon/src/main/res/drawable/ic_category_food.xml b/mastodon/src/main/res/drawable/ic_category_food.xml new file mode 100644 index 00000000..a514aeb3 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_category_food.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/mastodon/src/main/res/drawable/ic_category_furry.xml b/mastodon/src/main/res/drawable/ic_category_furry.xml new file mode 100644 index 00000000..b97a508f --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_category_furry.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + diff --git a/mastodon/src/main/res/drawable/ic_category_games.xml b/mastodon/src/main/res/drawable/ic_category_games.xml new file mode 100644 index 00000000..e09596a0 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_category_games.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/mastodon/src/main/res/drawable/ic_category_general.xml b/mastodon/src/main/res/drawable/ic_category_general.xml new file mode 100644 index 00000000..d0a2de42 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_category_general.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/mastodon/src/main/res/drawable/ic_category_journalism.xml b/mastodon/src/main/res/drawable/ic_category_journalism.xml new file mode 100644 index 00000000..7decfc89 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_category_journalism.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/mastodon/src/main/res/drawable/ic_category_lgbt.xml b/mastodon/src/main/res/drawable/ic_category_lgbt.xml new file mode 100644 index 00000000..80b9af04 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_category_lgbt.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/mastodon/src/main/res/drawable/ic_category_music.xml b/mastodon/src/main/res/drawable/ic_category_music.xml new file mode 100644 index 00000000..a2018b4e --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_category_music.xml @@ -0,0 +1,12 @@ + + + + diff --git a/mastodon/src/main/res/drawable/ic_category_regional.xml b/mastodon/src/main/res/drawable/ic_category_regional.xml new file mode 100644 index 00000000..bd128fba --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_category_regional.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/mastodon/src/main/res/drawable/ic_category_tech.xml b/mastodon/src/main/res/drawable/ic_category_tech.xml new file mode 100644 index 00000000..e9e82f89 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_category_tech.xml @@ -0,0 +1,12 @@ + + + + diff --git a/mastodon/src/main/res/drawable/ic_category_unknown.xml b/mastodon/src/main/res/drawable/ic_category_unknown.xml new file mode 100644 index 00000000..8f76682f --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_category_unknown.xml @@ -0,0 +1,12 @@ + + + + diff --git a/mastodon/src/main/res/drawable/ic_fluent_local_language_16_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_local_language_16_regular.xml new file mode 100644 index 00000000..84c8f088 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_local_language_16_regular.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_fluent_people_community_16_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_people_community_16_regular.xml new file mode 100644 index 00000000..ee02ac96 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_people_community_16_regular.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_fluent_search_20_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_search_20_regular.xml new file mode 100644 index 00000000..c89a2388 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_search_20_regular.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_round_checkbox.xml b/mastodon/src/main/res/drawable/ic_round_checkbox.xml index 59527d1b..3897b6ce 100644 --- a/mastodon/src/main/res/drawable/ic_round_checkbox.xml +++ b/mastodon/src/main/res/drawable/ic_round_checkbox.xml @@ -1,5 +1,6 @@ + \ No newline at end of file diff --git a/mastodon/src/main/res/layout/fragment_onboarding_activation.xml b/mastodon/src/main/res/layout/fragment_onboarding_activation.xml new file mode 100644 index 00000000..f68b0da5 --- /dev/null +++ b/mastodon/src/main/res/layout/fragment_onboarding_activation.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + +