Onboarding & signup
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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]", "");
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
/*package*/ interface ScrollableToTop{
|
||||
public interface ScrollableToTop{
|
||||
void scrollToTop();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
package org.joinmastodon.android.fragments.report;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
package org.joinmastodon.android.fragments.report;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
package org.joinmastodon.android.fragments.report;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
package org.joinmastodon.android.fragments.report;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user