Signup flow redesign WIP
This commit is contained in:
parent
bcb3e217cd
commit
b153a64373
|
@ -16,4 +16,4 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
# Automatically convert third-party libraries to use AndroidX
|
# Automatically convert third-party libraries to use AndroidX
|
||||||
android.enableJetifier=true
|
android.enableJetifier=false
|
|
@ -9,7 +9,7 @@ android {
|
||||||
applicationId "org.joinmastodon.android"
|
applicationId "org.joinmastodon.android"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 47
|
versionCode 48
|
||||||
versionName "1.1.5"
|
versionName "1.1.5"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "nl-rNL", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
|
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "nl-rNL", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
|
||||||
|
|
|
@ -795,18 +795,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check to see if Android platform photopicker is available on the device\
|
|
||||||
* @return whether the device supports photopicker intents.
|
|
||||||
*/
|
|
||||||
private boolean isPhotoPickerAvailable() {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
return true;
|
|
||||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
return getExtensionVersion(Build.VERSION_CODES.R) >= 2;
|
|
||||||
} else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the correct intent for the device version to select media.
|
* Builds the correct intent for the device version to select media.
|
||||||
|
@ -818,24 +806,24 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
*/
|
*/
|
||||||
private void openFilePicker(){
|
private void openFilePicker(){
|
||||||
Intent intent;
|
Intent intent;
|
||||||
boolean usePhotoPicker = isPhotoPickerAvailable();
|
boolean usePhotoPicker=UiUtils.isPhotoPickerAvailable();
|
||||||
if (usePhotoPicker) {
|
if(usePhotoPicker){
|
||||||
intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
|
intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
|
||||||
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
|
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MAX_ATTACHMENTS-getMediaAttachmentsCount());
|
||||||
} else {
|
}else{
|
||||||
intent = new Intent(Intent.ACTION_GET_CONTENT);
|
intent=new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
intent.setType("*/*");
|
intent.setType("*/*");
|
||||||
}
|
}
|
||||||
if (!usePhotoPicker && instance.configuration != null &&
|
if(!usePhotoPicker && instance.configuration!=null &&
|
||||||
instance.configuration.mediaAttachments != null &&
|
instance.configuration.mediaAttachments!=null &&
|
||||||
instance.configuration.mediaAttachments.supportedMimeTypes != null &&
|
instance.configuration.mediaAttachments.supportedMimeTypes!=null &&
|
||||||
!instance.configuration.mediaAttachments.supportedMimeTypes.isEmpty()) {
|
!instance.configuration.mediaAttachments.supportedMimeTypes.isEmpty()){
|
||||||
intent.putExtra(Intent.EXTRA_MIME_TYPES,
|
intent.putExtra(Intent.EXTRA_MIME_TYPES,
|
||||||
instance.configuration.mediaAttachments.supportedMimeTypes.toArray(
|
instance.configuration.mediaAttachments.supportedMimeTypes.toArray(
|
||||||
new String[0]));
|
new String[0]));
|
||||||
} else {
|
}else{
|
||||||
if (!usePhotoPicker) {
|
if(!usePhotoPicker){
|
||||||
// If photo picker is being used these are the default mimetypes.
|
// If photo picker is being used these are the default mimetypes.
|
||||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
|
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
||||||
|
import org.joinmastodon.android.fragments.onboarding.OnboardingFollowSuggestionsFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
@ -31,6 +32,7 @@ import java.util.ArrayList;
|
||||||
import androidx.annotation.IdRes;
|
import androidx.annotation.IdRes;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import me.grishka.appkit.FragmentStackActivity;
|
import me.grishka.appkit.FragmentStackActivity;
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.fragments.AppKitFragment;
|
import me.grishka.appkit.fragments.AppKitFragment;
|
||||||
import me.grishka.appkit.fragments.LoaderFragment;
|
import me.grishka.appkit.fragments.LoaderFragment;
|
||||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||||
|
@ -235,6 +237,11 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||||
new AccountSwitcherSheet(getActivity()).show();
|
new AccountSwitcherSheet(getActivity()).show();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if(tab==R.id.tab_home){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
Nav.go(getActivity(), OnboardingFollowSuggestionsFragment.class, args);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,9 +37,11 @@ import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||||
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||||
|
import org.joinmastodon.android.api.session.AccountActivationInfo;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||||
|
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||||
import org.joinmastodon.android.model.PushNotification;
|
import org.joinmastodon.android.model.PushNotification;
|
||||||
import org.joinmastodon.android.model.PushSubscription;
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
|
@ -55,6 +57,7 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.imageloader.ImageCache;
|
import me.grishka.appkit.imageloader.ImageCache;
|
||||||
|
@ -122,6 +125,19 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||||
items.add(new TextItem(R.string.settings_clear_cache, this::clearImageCache));
|
items.add(new TextItem(R.string.settings_clear_cache, this::clearImageCache));
|
||||||
items.add(new TextItem(R.string.log_out, this::confirmLogOut));
|
items.add(new TextItem(R.string.log_out, this::confirmLogOut));
|
||||||
|
|
||||||
|
if(BuildConfig.DEBUG){
|
||||||
|
items.add(new RedHeaderItem("Debug options"));
|
||||||
|
items.add(new TextItem("Test e-mail confirmation flow", ()->{
|
||||||
|
AccountSession sess=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
|
sess.activated=false;
|
||||||
|
sess.activationInfo=new AccountActivationInfo("test@email", System.currentTimeMillis());
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putBoolean("debug", true);
|
||||||
|
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
items.add(new FooterItem(getString(R.string.settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
|
items.add(new FooterItem(getString(R.string.settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,6 +362,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||||
this.text=getString(text);
|
this.text=getString(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HeaderItem(String text){
|
||||||
|
this.text=text;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getViewType(){
|
public int getViewType(){
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -405,6 +425,11 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||||
this.onClick=onClick;
|
this.onClick=onClick;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TextItem(String text, Runnable onClick){
|
||||||
|
this.text=text;
|
||||||
|
this.onClick=onClick;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getViewType(){
|
public int getViewType(){
|
||||||
return 4;
|
return 4;
|
||||||
|
@ -417,6 +442,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||||
super(text);
|
super(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RedHeaderItem(String text){
|
||||||
|
super(text);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getViewType(){
|
public int getViewType(){
|
||||||
return 5;
|
return 5;
|
||||||
|
|
|
@ -193,30 +193,24 @@ public class AccountActivationFragment extends ToolbarFragment{
|
||||||
mgr.removeAccount(accountID);
|
mgr.removeAccount(accountID);
|
||||||
mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, null);
|
mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, null);
|
||||||
String newID=mgr.getLastActiveAccountID();
|
String newID=mgr.getLastActiveAccountID();
|
||||||
Bundle args=new Bundle();
|
accountID=newID;
|
||||||
args.putString("account", newID);
|
if((session.self.avatar!=null || session.self.displayName!=null) && !getArguments().getBoolean("debug")){
|
||||||
if(session.self.avatar!=null || session.self.displayName!=null){
|
new UpdateAccountCredentials(session.self.displayName, "", (File)null, null, Collections.emptyList())
|
||||||
File avaFile=session.self.avatar!=null ? new File(session.self.avatar) : null;
|
|
||||||
new UpdateAccountCredentials(session.self.displayName, "", avaFile, null, Collections.emptyList())
|
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Account result){
|
public void onSuccess(Account result){
|
||||||
if(avaFile!=null)
|
|
||||||
avaFile.delete();
|
|
||||||
mgr.updateAccountInfo(newID, result);
|
mgr.updateAccountInfo(newID, result);
|
||||||
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
proceed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
if(avaFile!=null)
|
proceed();
|
||||||
avaFile.delete();
|
|
||||||
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(newID);
|
.exec(newID);
|
||||||
}else{
|
}else{
|
||||||
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
proceed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,4 +243,11 @@ public class AccountActivationFragment extends ToolbarFragment{
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
resendBtn.removeCallbacks(resendTimer);
|
resendBtn.removeCallbacks(resendTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void proceed(){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
// Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
||||||
|
Nav.goClearingStack(getActivity(), OnboardingProfileSetupFragment.class, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,8 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
||||||
private ItemsAdapter itemsAdapter;
|
private ItemsAdapter itemsAdapter;
|
||||||
private ElevationOnScrollListener onScrollListener;
|
private ElevationOnScrollListener onScrollListener;
|
||||||
|
|
||||||
|
private static final int SIGNUP_REQUEST=722;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -139,7 +141,16 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
||||||
protected void onButtonClick(){
|
protected void onButtonClick(){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putParcelable("instance", Parcels.wrap(instance));
|
args.putParcelable("instance", Parcels.wrap(instance));
|
||||||
Nav.go(getActivity(), SignupFragment.class, args);
|
Nav.goForResult(getActivity(), SignupFragment.class, args, SIGNUP_REQUEST, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFragmentResult(int reqCode, boolean success, Bundle result){
|
||||||
|
super.onFragmentResult(reqCode, success, result);
|
||||||
|
if(reqCode==SIGNUP_REQUEST && !success){
|
||||||
|
setResult(false, null);
|
||||||
|
Nav.finish(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -92,7 +92,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||||
protected boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){
|
protected boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){
|
||||||
if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN)
|
if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN)
|
||||||
return true;
|
return true;
|
||||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase();
|
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
|
||||||
updateFilteredList();
|
updateFilteredList();
|
||||||
searchEdit.removeCallbacks(searchDebouncer);
|
searchEdit.removeCallbacks(searchDebouncer);
|
||||||
Instance instance=instancesCache.get(normalizeInstanceDomain(currentSearchQuery));
|
Instance instance=instancesCache.get(normalizeInstanceDomain(currentSearchQuery));
|
||||||
|
@ -106,7 +106,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onSearchChangedDebounced(){
|
protected void onSearchChangedDebounced(){
|
||||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase();
|
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
|
||||||
updateFilteredList();
|
updateFilteredList();
|
||||||
loadInstanceInfo(currentSearchQuery, false);
|
loadInstanceInfo(currentSearchQuery, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -527,6 +527,15 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||||
updateFilteredList();
|
updateFilteredList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onShown(){
|
||||||
|
super.onShown();
|
||||||
|
if(!searchQueryMode){
|
||||||
|
// Prevent search view automatically getting focused when the user returns to this fragment
|
||||||
|
focusThing.requestFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceCatalogSignupFragment.InstanceViewHolder>{
|
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceCatalogSignupFragment.InstanceViewHolder>{
|
||||||
public InstancesAdapter(){
|
public InstancesAdapter(){
|
||||||
super(imgLoader);
|
super(imgLoader);
|
||||||
|
|
|
@ -0,0 +1,350 @@
|
||||||
|
package org.joinmastodon.android.fragments.onboarding;
|
||||||
|
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.graphics.drawable.Animatable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowInsets;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
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.api.requests.accounts.SetAccountFollowed;
|
||||||
|
import org.joinmastodon.android.fragments.HomeFragment;
|
||||||
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
|
import org.joinmastodon.android.model.FollowSuggestion;
|
||||||
|
import org.joinmastodon.android.model.ParsedAccount;
|
||||||
|
import org.joinmastodon.android.model.Relationship;
|
||||||
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||||
|
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||||
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
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.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||||
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
|
public class OnboardingFollowSuggestionsFragment extends BaseRecyclerFragment<ParsedAccount>{
|
||||||
|
private String accountID;
|
||||||
|
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||||
|
private GetAccountRelationships relationshipsRequest;
|
||||||
|
private View buttonBar;
|
||||||
|
private ElevationOnScrollListener onScrollListener;
|
||||||
|
private int numRunningFollowRequests=0;
|
||||||
|
|
||||||
|
public OnboardingFollowSuggestionsFragment(){
|
||||||
|
super(R.layout.fragment_onboarding_follow_suggestions, 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setRetainInstance(true);
|
||||||
|
setTitle(R.string.popular_on_mastodon);
|
||||||
|
accountID=getArguments().getString("account");
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
buttonBar=view.findViewById(R.id.button_bar);
|
||||||
|
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||||
|
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||||
|
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||||
|
|
||||||
|
view.findViewById(R.id.btn_next).setOnClickListener(UiUtils.rateLimitedClickListener(this::onFollowAllClick));
|
||||||
|
view.findViewById(R.id.btn_skip).setOnClickListener(UiUtils.rateLimitedClickListener(v->proceed()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onUpdateToolbar(){
|
||||||
|
super.onUpdateToolbar();
|
||||||
|
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
|
||||||
|
getToolbar().setElevation(0);
|
||||||
|
if(onScrollListener!=null){
|
||||||
|
onScrollListener.setViews(buttonBar, getToolbar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(int offset, int count){
|
||||||
|
new GetFollowSuggestions(40)
|
||||||
|
.setCallback(new SimpleCallback<>(this){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<FollowSuggestion> result){
|
||||||
|
onDataLoaded(result.stream().map(fs->new ParsedAccount(fs.account, accountID)).collect(Collectors.toList()), false);
|
||||||
|
loadRelationships();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadRelationships(){
|
||||||
|
relationships=Collections.emptyMap();
|
||||||
|
relationshipsRequest=new GetAccountRelationships(data.stream().map(fs->fs.account.id).collect(Collectors.toList()));
|
||||||
|
relationshipsRequest.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Relationship> result){
|
||||||
|
relationshipsRequest=null;
|
||||||
|
relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity()));
|
||||||
|
if(list==null)
|
||||||
|
return;
|
||||||
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||||
|
if(holder instanceof SuggestionViewHolder svh)
|
||||||
|
svh.rebind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
relationshipsRequest=null;
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 RecyclerView.Adapter getAdapter(){
|
||||||
|
return new SuggestionsAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onFollowAllClick(View v){
|
||||||
|
if(!loaded || relationships.isEmpty())
|
||||||
|
return;
|
||||||
|
if(data.isEmpty()){
|
||||||
|
proceed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ArrayList<String> accountIdsToFollow=new ArrayList<>();
|
||||||
|
for(ParsedAccount acc:data){
|
||||||
|
Relationship rel=relationships.get(acc.account.id);
|
||||||
|
if(rel==null)
|
||||||
|
continue;
|
||||||
|
if(rel.canFollow())
|
||||||
|
accountIdsToFollow.add(acc.account.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ProgressDialog progress=new ProgressDialog(getActivity());
|
||||||
|
progress.setIndeterminate(false);
|
||||||
|
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||||
|
progress.setMax(accountIdsToFollow.size());
|
||||||
|
progress.setCancelable(false);
|
||||||
|
progress.setMessage(getString(R.string.sending_follows));
|
||||||
|
progress.show();
|
||||||
|
|
||||||
|
for(int i=0;i<Math.min(accountIdsToFollow.size(), 5);i++){ // Send up to 5 requests in parallel
|
||||||
|
followNextAccount(accountIdsToFollow, progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void followNextAccount(ArrayList<String> accountIdsToFollow, ProgressDialog progress){
|
||||||
|
if(accountIdsToFollow.isEmpty()){
|
||||||
|
if(numRunningFollowRequests==0){
|
||||||
|
progress.dismiss();
|
||||||
|
proceed();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
numRunningFollowRequests++;
|
||||||
|
String id=accountIdsToFollow.remove(0);
|
||||||
|
new SetAccountFollowed(id, true, true)
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Relationship result){
|
||||||
|
numRunningFollowRequests--;
|
||||||
|
progress.setProgress(progress.getMax()-accountIdsToFollow.size()-numRunningFollowRequests);
|
||||||
|
followNextAccount(accountIdsToFollow, progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
numRunningFollowRequests--;
|
||||||
|
progress.setProgress(progress.getMax()-accountIdsToFollow.size()-numRunningFollowRequests);
|
||||||
|
followNextAccount(accountIdsToFollow, progress);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void proceed(){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
Nav.go(getActivity(), HomeFragment.class, args);
|
||||||
|
getActivity().getWindow().getDecorView().postDelayed(()->Nav.finish(this), 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canGoBack(){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onToolbarNavigationClick(){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SuggestionsAdapter extends UsableRecyclerView.Adapter<SuggestionViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||||
|
|
||||||
|
public SuggestionsAdapter(){
|
||||||
|
super(imgLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public SuggestionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
return new SuggestionViewHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount(){
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(SuggestionViewHolder holder, int position){
|
||||||
|
holder.bind(data.get(position));
|
||||||
|
super.onBindViewHolder(holder, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getImageCountForItem(int position){
|
||||||
|
return data.get(position).emojiHelper.getImageCount()+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||||
|
ParsedAccount account=data.get(position);
|
||||||
|
if(image==0)
|
||||||
|
return account.avatarRequest;
|
||||||
|
return account.emojiHelper.getImageRequest(image-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SuggestionViewHolder extends BindableViewHolder<ParsedAccount> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
|
||||||
|
private final TextView name, username, bio;
|
||||||
|
private final ImageView avatar;
|
||||||
|
private final ProgressBarButton actionButton;
|
||||||
|
private final ProgressBar actionProgress;
|
||||||
|
private final View actionWrap;
|
||||||
|
|
||||||
|
private Relationship relationship;
|
||||||
|
|
||||||
|
public SuggestionViewHolder(){
|
||||||
|
super(getActivity(), R.layout.item_user_row_m3, list);
|
||||||
|
name=findViewById(R.id.name);
|
||||||
|
username=findViewById(R.id.username);
|
||||||
|
bio=findViewById(R.id.bio);
|
||||||
|
avatar=findViewById(R.id.avatar);
|
||||||
|
actionButton=findViewById(R.id.action_btn);
|
||||||
|
actionProgress=findViewById(R.id.action_progress);
|
||||||
|
actionWrap=findViewById(R.id.action_btn_wrap);
|
||||||
|
|
||||||
|
avatar.setOutlineProvider(OutlineProviders.roundedRect(10));
|
||||||
|
avatar.setClipToOutline(true);
|
||||||
|
actionButton.setOnClickListener(UiUtils.rateLimitedClickListener(this::onActionButtonClick));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(ParsedAccount item){
|
||||||
|
name.setText(item.parsedName);
|
||||||
|
username.setText(item.account.getDisplayUsername());
|
||||||
|
if(TextUtils.isEmpty(item.parsedBio)){
|
||||||
|
bio.setVisibility(View.GONE);
|
||||||
|
}else{
|
||||||
|
bio.setVisibility(View.VISIBLE);
|
||||||
|
bio.setText(item.parsedBio);
|
||||||
|
}
|
||||||
|
|
||||||
|
relationship=relationships.get(item.account.id);
|
||||||
|
if(relationship==null){
|
||||||
|
actionWrap.setVisibility(View.GONE);
|
||||||
|
}else{
|
||||||
|
actionWrap.setVisibility(View.VISIBLE);
|
||||||
|
UiUtils.setRelationshipToActionButtonM3(relationship, actionButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setImage(int index, Drawable image){
|
||||||
|
if(index==0){
|
||||||
|
avatar.setImageDrawable(image);
|
||||||
|
}else{
|
||||||
|
item.emojiHelper.setImageDrawable(index-1, image);
|
||||||
|
name.invalidate();
|
||||||
|
bio.invalidate();
|
||||||
|
}
|
||||||
|
if(image instanceof Animatable a && !a.isRunning())
|
||||||
|
a.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearImage(int index){
|
||||||
|
setImage(index, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putParcelable("profileAccount", Parcels.wrap(item.account));
|
||||||
|
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onActionButtonClick(View v){
|
||||||
|
itemView.setHasTransientState(true);
|
||||||
|
UiUtils.performAccountAction(getActivity(), item.account, accountID, relationship, actionButton, this::setActionProgressVisible, rel->{
|
||||||
|
itemView.setHasTransientState(false);
|
||||||
|
relationships.put(item.account.id, rel);
|
||||||
|
rebind();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setActionProgressVisible(boolean visible){
|
||||||
|
actionButton.setTextVisible(!visible);
|
||||||
|
actionProgress.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||||
|
actionButton.setClickable(!visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,237 @@
|
||||||
|
package org.joinmastodon.android.fragments.onboarding;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
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.EditText;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.fragments.HomeFragment;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.AccountField;
|
||||||
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.ui.views.ReorderableLinearLayout;
|
||||||
|
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||||
|
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||||
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||||
|
|
||||||
|
public class OnboardingProfileSetupFragment extends ToolbarFragment implements ReorderableLinearLayout.OnDragListener{
|
||||||
|
private Button btn;
|
||||||
|
private View buttonBar;
|
||||||
|
private String accountID;
|
||||||
|
private ElevationOnScrollListener onScrollListener;
|
||||||
|
private ScrollView scroller;
|
||||||
|
private EditText nameEdit, bioEdit;
|
||||||
|
private ImageView avaImage, coverImage;
|
||||||
|
private Button addRow;
|
||||||
|
private ReorderableLinearLayout profileFieldsLayout;
|
||||||
|
private Uri avatarUri, coverUri;
|
||||||
|
|
||||||
|
private static final int AVATAR_RESULT=348;
|
||||||
|
private static final int COVER_RESULT=183;
|
||||||
|
|
||||||
|
@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));
|
||||||
|
accountID=getArguments().getString("account");
|
||||||
|
setTitle(R.string.profile_setup);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||||
|
View view=inflater.inflate(R.layout.fragment_onboarding_profile_setup, container, false);
|
||||||
|
|
||||||
|
scroller=view.findViewById(R.id.scroller);
|
||||||
|
nameEdit=view.findViewById(R.id.display_name);
|
||||||
|
bioEdit=view.findViewById(R.id.bio);
|
||||||
|
avaImage=view.findViewById(R.id.avatar);
|
||||||
|
coverImage=view.findViewById(R.id.header);
|
||||||
|
addRow=view.findViewById(R.id.add_row);
|
||||||
|
profileFieldsLayout=view.findViewById(R.id.profile_fields);
|
||||||
|
|
||||||
|
btn=view.findViewById(R.id.btn_next);
|
||||||
|
btn.setOnClickListener(v->onButtonClick());
|
||||||
|
buttonBar=view.findViewById(R.id.button_bar);
|
||||||
|
|
||||||
|
avaImage.setOutlineProvider(OutlineProviders.roundedRect(24));
|
||||||
|
avaImage.setClipToOutline(true);
|
||||||
|
|
||||||
|
Account account=AccountSessionManager.getInstance().getAccount(accountID).self;
|
||||||
|
if(savedInstanceState==null){
|
||||||
|
nameEdit.setText(account.displayName);
|
||||||
|
makeFieldsRow();
|
||||||
|
}else{
|
||||||
|
ArrayList<String> fieldTitles=savedInstanceState.getStringArrayList("fieldTitles");
|
||||||
|
ArrayList<String> fieldValues=savedInstanceState.getStringArrayList("fieldValues");
|
||||||
|
for(int i=0;i<fieldTitles.size();i++){
|
||||||
|
View row=makeFieldsRow();
|
||||||
|
EditText title=row.findViewById(R.id.title);
|
||||||
|
EditText content=row.findViewById(R.id.content);
|
||||||
|
title.setText(fieldTitles.get(i));
|
||||||
|
content.setText(fieldValues.get(i));
|
||||||
|
}
|
||||||
|
if(fieldTitles.size()==4)
|
||||||
|
addRow.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
addRow.setOnClickListener(v->{
|
||||||
|
makeFieldsRow();
|
||||||
|
if(profileFieldsLayout.getChildCount()==4){
|
||||||
|
addRow.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
profileFieldsLayout.setDragListener(this);
|
||||||
|
avaImage.setOnClickListener(v->startActivityForResult(UiUtils.getMediaPickerIntent(new String[]{"image/*"}, 1), AVATAR_RESULT));
|
||||||
|
coverImage.setOnClickListener(v->startActivityForResult(UiUtils.getMediaPickerIntent(new String[]{"image/*"}, 1), COVER_RESULT));
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||||
|
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||||
|
scroller.setOnScrollChangeListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onUpdateToolbar(){
|
||||||
|
super.onUpdateToolbar();
|
||||||
|
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
|
||||||
|
getToolbar().setElevation(0);
|
||||||
|
if(onScrollListener!=null){
|
||||||
|
onScrollListener.setViews(buttonBar, getToolbar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onButtonClick(){
|
||||||
|
ArrayList<AccountField> fields=new ArrayList<>();
|
||||||
|
for(int i=0;i<profileFieldsLayout.getChildCount();i++){
|
||||||
|
View row=profileFieldsLayout.getChildAt(i);
|
||||||
|
EditText title=row.findViewById(R.id.title);
|
||||||
|
EditText content=row.findViewById(R.id.content);
|
||||||
|
AccountField fld=new AccountField();
|
||||||
|
fld.name=title.getText().toString();
|
||||||
|
fld.value=content.getText().toString();
|
||||||
|
fields.add(fld);
|
||||||
|
}
|
||||||
|
new UpdateAccountCredentials(nameEdit.getText().toString(), bioEdit.getText().toString(), avatarUri, coverUri, fields)
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Account result){
|
||||||
|
AccountSessionManager.getInstance().updateAccountInfo(accountID, result);
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
Nav.go(getActivity(), OnboardingFollowSuggestionsFragment.class, args);
|
||||||
|
getActivity().getWindow().getDecorView().postDelayed(()->Nav.finish(OnboardingProfileSetupFragment.this), 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
error.showToast(getActivity());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.wrapProgress(getActivity(), R.string.saving, true)
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 View makeFieldsRow(){
|
||||||
|
View view=LayoutInflater.from(getActivity()).inflate(R.layout.onboarding_profile_field, profileFieldsLayout, false);
|
||||||
|
profileFieldsLayout.addView(view);
|
||||||
|
view.findViewById(R.id.dragger_thingy).setOnLongClickListener(v->{
|
||||||
|
profileFieldsLayout.startDragging(view);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSwapItems(int oldIndex, int newIndex){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState){
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
ArrayList<String> fieldTitles=new ArrayList<>(), fieldValues=new ArrayList<>();
|
||||||
|
for(int i=0;i<profileFieldsLayout.getChildCount();i++){
|
||||||
|
View row=profileFieldsLayout.getChildAt(i);
|
||||||
|
EditText title=row.findViewById(R.id.title);
|
||||||
|
EditText content=row.findViewById(R.id.content);
|
||||||
|
fieldTitles.add(title.getText().toString());
|
||||||
|
fieldValues.add(content.getText().toString());
|
||||||
|
}
|
||||||
|
outState.putStringArrayList("fieldTitles", fieldTitles);
|
||||||
|
outState.putStringArrayList("fieldValues", fieldValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||||
|
if(resultCode!=Activity.RESULT_OK)
|
||||||
|
return;
|
||||||
|
ImageView img;
|
||||||
|
Uri uri=data.getData();
|
||||||
|
int size;
|
||||||
|
if(requestCode==AVATAR_RESULT){
|
||||||
|
img=avaImage;
|
||||||
|
avatarUri=uri;
|
||||||
|
size=V.dp(100);
|
||||||
|
}else{
|
||||||
|
img=coverImage;
|
||||||
|
coverUri=uri;
|
||||||
|
size=V.dp(1000);
|
||||||
|
}
|
||||||
|
img.setForeground(null);
|
||||||
|
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(uri, size, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canGoBack(){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onToolbarNavigationClick(){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,18 @@
|
||||||
package org.joinmastodon.android.fragments.onboarding;
|
package org.joinmastodon.android.fragments.onboarding;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Intent;
|
import android.graphics.Typeface;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
|
import android.text.Html;
|
||||||
|
import android.text.SpannableString;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.util.Log;
|
import android.text.style.TypefaceSpan;
|
||||||
|
import android.text.style.URLSpan;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
@ -16,11 +20,9 @@ import android.view.ViewTreeObserver;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
|
||||||
import org.joinmastodon.android.api.MastodonDetailedErrorResponse;
|
import org.joinmastodon.android.api.MastodonDetailedErrorResponse;
|
||||||
import org.joinmastodon.android.api.requests.accounts.RegisterAccount;
|
import org.joinmastodon.android.api.requests.accounts.RegisterAccount;
|
||||||
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
||||||
|
@ -31,18 +33,22 @@ import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Application;
|
import org.joinmastodon.android.model.Application;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Token;
|
import org.joinmastodon.android.model.Token;
|
||||||
|
import org.joinmastodon.android.ui.text.LinkSpan;
|
||||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
|
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
|
||||||
|
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.jsoup.nodes.Node;
|
||||||
|
import org.jsoup.nodes.TextNode;
|
||||||
|
import org.jsoup.select.NodeVisitor;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
@ -51,12 +57,10 @@ import me.grishka.appkit.api.APIRequest;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||||
|
|
||||||
public class SignupFragment extends ToolbarFragment{
|
public class SignupFragment extends ToolbarFragment{
|
||||||
private static final int AVATAR_RESULT=198;
|
|
||||||
private static final String TAG="SignupFragment";
|
private static final String TAG="SignupFragment";
|
||||||
|
|
||||||
private Instance instance;
|
private Instance instance;
|
||||||
|
@ -73,6 +77,7 @@ public class SignupFragment extends ToolbarFragment{
|
||||||
private boolean submitAfterGettingToken;
|
private boolean submitAfterGettingToken;
|
||||||
private ProgressDialog progressDialog;
|
private ProgressDialog progressDialog;
|
||||||
private HashSet<EditText> errorFields=new HashSet<>();
|
private HashSet<EditText> errorFields=new HashSet<>();
|
||||||
|
private ElevationOnScrollListener onScrollListener;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
@ -145,19 +150,22 @@ public class SignupFragment extends ToolbarFragment{
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||||
|
view.findViewById(R.id.scroller).setOnScrollChangeListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onUpdateToolbar(){
|
protected void onUpdateToolbar(){
|
||||||
super.onUpdateToolbar();
|
super.onUpdateToolbar();
|
||||||
getToolbar().setBackground(null);
|
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
|
||||||
getToolbar().setElevation(0);
|
getToolbar().setElevation(0);
|
||||||
|
if(onScrollListener!=null){
|
||||||
|
onScrollListener.setViews(buttonBar, getToolbar());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onButtonClick(){
|
private void onButtonClick(){
|
||||||
if(!password.getText().toString().equals(passwordConfirm.getText().toString())){
|
if(!password.getText().toString().equals(passwordConfirm.getText().toString())){
|
||||||
passwordConfirm.setError(getString(R.string.signup_passwords_dont_match));
|
passwordConfirmWrap.setErrorState(getString(R.string.signup_passwords_dont_match));
|
||||||
passwordConfirmWrap.setErrorState();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showProgressDialog();
|
showProgressDialog();
|
||||||
|
@ -212,8 +220,22 @@ public class SignupFragment extends ToolbarFragment{
|
||||||
anyFieldsSkipped=true;
|
anyFieldsSkipped=true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
field.setError(fieldErrors.get(fieldName).stream().map(err->err.description).collect(Collectors.joining("\n")));
|
List<MastodonDetailedErrorResponse.FieldError> errors=Objects.requireNonNull(fieldErrors.get(fieldName));
|
||||||
getFieldWrapByName(fieldName).setErrorState();
|
if(errors.size()==1){
|
||||||
|
getFieldWrapByName(fieldName).setErrorState(getErrorDescription(errors.get(0), fieldName));
|
||||||
|
}else{
|
||||||
|
SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||||
|
boolean firstErr=true;
|
||||||
|
for(MastodonDetailedErrorResponse.FieldError err:errors){
|
||||||
|
if(firstErr){
|
||||||
|
firstErr=false;
|
||||||
|
}else{
|
||||||
|
ssb.append('\n');
|
||||||
|
}
|
||||||
|
ssb.append(getErrorDescription(err, fieldName));
|
||||||
|
}
|
||||||
|
getFieldWrapByName(fieldName).setErrorState(getErrorDescription(errors.get(0), fieldName));
|
||||||
|
}
|
||||||
errorFields.add(field);
|
errorFields.add(field);
|
||||||
if(first){
|
if(first){
|
||||||
first=false;
|
first=false;
|
||||||
|
@ -231,6 +253,40 @@ public class SignupFragment extends ToolbarFragment{
|
||||||
.exec(instance.uri, apiToken);
|
.exec(instance.uri, apiToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CharSequence getErrorDescription(MastodonDetailedErrorResponse.FieldError error, String fieldName){
|
||||||
|
return switch(fieldName){
|
||||||
|
case "email" -> switch(error.error){
|
||||||
|
case "ERR_BLOCKED" -> {
|
||||||
|
String emailAddr=email.getText().toString();
|
||||||
|
String s=getResources().getString(R.string.signup_email_domain_blocked, TextUtils.htmlEncode(instance.uri), TextUtils.htmlEncode(emailAddr.substring(emailAddr.lastIndexOf('@')+1)));
|
||||||
|
SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||||
|
Jsoup.parseBodyFragment(s).body().traverse(new NodeVisitor(){
|
||||||
|
private int spanStart;
|
||||||
|
@Override
|
||||||
|
public void head(Node node, int depth){
|
||||||
|
if(node instanceof TextNode tn){
|
||||||
|
ssb.append(tn.text());
|
||||||
|
}else if(node instanceof Element){
|
||||||
|
spanStart=ssb.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tail(Node node, int depth){
|
||||||
|
if(node instanceof Element){
|
||||||
|
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
yield ssb;
|
||||||
|
}
|
||||||
|
default -> error.description;
|
||||||
|
};
|
||||||
|
default -> error.description;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private EditText getFieldByName(String name){
|
private EditText getFieldByName(String name){
|
||||||
return switch(name){
|
return switch(name){
|
||||||
case "email" -> email;
|
case "email" -> email;
|
||||||
|
@ -323,6 +379,11 @@ public class SignupFragment extends ToolbarFragment{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onGoBackLinkClick(LinkSpan span){
|
||||||
|
setResult(false, null);
|
||||||
|
Nav.finish(this);
|
||||||
|
}
|
||||||
|
|
||||||
private class ErrorClearingListener implements TextWatcher{
|
private class ErrorClearingListener implements TextWatcher{
|
||||||
public final EditText editText;
|
public final EditText editText;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
|
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
|
public class ParsedAccount{
|
||||||
|
public Account account;
|
||||||
|
public CharSequence parsedName, parsedBio;
|
||||||
|
public CustomEmojiHelper emojiHelper;
|
||||||
|
public ImageLoaderRequest avatarRequest;
|
||||||
|
|
||||||
|
public ParsedAccount(Account account, String accountID){
|
||||||
|
this.account=account;
|
||||||
|
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
|
||||||
|
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||||
|
|
||||||
|
emojiHelper=new CustomEmojiHelper();
|
||||||
|
SpannableStringBuilder ssb=new SpannableStringBuilder(parsedName);
|
||||||
|
ssb.append(parsedBio);
|
||||||
|
emojiHelper.setText(ssb);
|
||||||
|
|
||||||
|
avatarRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(40), V.dp(40));
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,10 @@ public class Relationship extends BaseModel{
|
||||||
public boolean blockedBy;
|
public boolean blockedBy;
|
||||||
public String note;
|
public String note;
|
||||||
|
|
||||||
|
public boolean canFollow(){
|
||||||
|
return !(following || blocking || blockedBy || domainBlocking);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
return "Relationship{"+
|
return "Relationship{"+
|
||||||
|
|
|
@ -35,6 +35,7 @@ public class LinkSpan extends CharacterStyle {
|
||||||
case URL -> UiUtils.openURL(context, accountID, link);
|
case URL -> UiUtils.openURL(context, accountID, link);
|
||||||
case MENTION -> UiUtils.openProfileByID(context, accountID, link);
|
case MENTION -> UiUtils.openProfileByID(context, accountID, link);
|
||||||
case HASHTAG -> UiUtils.openHashtagTimeline(context, accountID, link);
|
case HASHTAG -> UiUtils.openHashtagTimeline(context, accountID, link);
|
||||||
|
case CUSTOM -> listener.onLinkClick(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +58,7 @@ public class LinkSpan extends CharacterStyle {
|
||||||
public enum Type{
|
public enum Type{
|
||||||
URL,
|
URL,
|
||||||
MENTION,
|
MENTION,
|
||||||
HASHTAG
|
HASHTAG,
|
||||||
|
CUSTOM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,9 @@ import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.os.ext.SdkExtensions;
|
||||||
|
import android.provider.MediaStore;
|
||||||
import android.provider.OpenableColumns;
|
import android.provider.OpenableColumns;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
|
@ -443,6 +446,38 @@ public class UiUtils{
|
||||||
ta.recycle();
|
ta.recycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setRelationshipToActionButtonM3(Relationship relationship, Button button){
|
||||||
|
boolean secondaryStyle;
|
||||||
|
if(relationship.blocking){
|
||||||
|
button.setText(R.string.button_blocked);
|
||||||
|
secondaryStyle=true;
|
||||||
|
}else if(relationship.blockedBy){
|
||||||
|
button.setText(R.string.button_follow);
|
||||||
|
secondaryStyle=false;
|
||||||
|
}else if(relationship.requested){
|
||||||
|
button.setText(R.string.button_follow_pending);
|
||||||
|
secondaryStyle=true;
|
||||||
|
}else if(!relationship.following){
|
||||||
|
button.setText(relationship.followedBy ? R.string.follow_back : R.string.button_follow);
|
||||||
|
secondaryStyle=false;
|
||||||
|
}else{
|
||||||
|
button.setText(R.string.button_following);
|
||||||
|
secondaryStyle=true;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.setEnabled(!relationship.blockedBy);
|
||||||
|
int styleRes=secondaryStyle ? R.style.Widget_Mastodon_M3_Button_Tonal : R.style.Widget_Mastodon_M3_Button_Filled;
|
||||||
|
TypedArray ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
|
||||||
|
button.setBackground(ta.getDrawable(0));
|
||||||
|
ta.recycle();
|
||||||
|
ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
|
||||||
|
if(relationship.blocking)
|
||||||
|
button.setTextColor(button.getResources().getColorStateList(R.color.error_600));
|
||||||
|
else
|
||||||
|
button.setTextColor(ta.getColorStateList(0));
|
||||||
|
ta.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
public static void performAccountAction(Activity activity, Account account, String accountID, Relationship relationship, Button button, Consumer<Boolean> progressCallback, Consumer<Relationship> resultCallback){
|
public static void performAccountAction(Activity activity, Account account, String accountID, Relationship relationship, Button button, Consumer<Boolean> progressCallback, Consumer<Relationship> resultCallback){
|
||||||
if(relationship.blocking){
|
if(relationship.blocking){
|
||||||
confirmToggleBlockUser(activity, accountID, account, true, resultCallback);
|
confirmToggleBlockUser(activity, accountID, account, true, resultCallback);
|
||||||
|
@ -609,4 +644,63 @@ public class UiUtils{
|
||||||
int b=Math.round((color1 & 0xFF)*alpha0+(color2 & 0xFF)*alpha);
|
int b=Math.round((color1 & 0xFF)*alpha0+(color2 & 0xFF)*alpha);
|
||||||
return 0xFF000000 | (r << 16) | (g << 8) | b;
|
return 0xFF000000 | (r << 16) | (g << 8) | b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see if Android platform photopicker is available on the device\
|
||||||
|
*
|
||||||
|
* @return whether the device supports photopicker intents.
|
||||||
|
*/
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public static boolean isPhotoPickerAvailable(){
|
||||||
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU){
|
||||||
|
return true;
|
||||||
|
}else if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.R){
|
||||||
|
return SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R)>=2;
|
||||||
|
}else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
public static Intent getMediaPickerIntent(String[] mimeTypes, int maxCount){
|
||||||
|
Intent intent;
|
||||||
|
if(isPhotoPickerAvailable()){
|
||||||
|
intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
|
||||||
|
if(maxCount>1)
|
||||||
|
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxCount);
|
||||||
|
}else{
|
||||||
|
intent=new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
}
|
||||||
|
if(mimeTypes.length>1){
|
||||||
|
intent.setType("*/*");
|
||||||
|
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
|
||||||
|
}else if(mimeTypes.length==1){
|
||||||
|
intent.setType(mimeTypes[0]);
|
||||||
|
}else{
|
||||||
|
intent.setType("*/*");
|
||||||
|
}
|
||||||
|
if(maxCount>1)
|
||||||
|
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a View.OnClickListener to filter multiple clicks in succession.
|
||||||
|
* Useful for buttons that perform some action that changes their state asynchronously.
|
||||||
|
* @param l
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static View.OnClickListener rateLimitedClickListener(View.OnClickListener l){
|
||||||
|
return new View.OnClickListener(){
|
||||||
|
private long lastClickTime;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v){
|
||||||
|
if(SystemClock.uptimeMillis()-lastClickTime>500L){
|
||||||
|
lastClickTime=SystemClock.uptimeMillis();
|
||||||
|
l.onClick(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.text.Editable;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
@ -47,6 +48,7 @@ public class FloatingHintEditTextLayout extends FrameLayout{
|
||||||
private RectF tmpRect=new RectF();
|
private RectF tmpRect=new RectF();
|
||||||
private ColorStateList labelColors, origHintColors;
|
private ColorStateList labelColors, origHintColors;
|
||||||
private boolean errorState;
|
private boolean errorState;
|
||||||
|
private TextView errorView;
|
||||||
|
|
||||||
public FloatingHintEditTextLayout(Context context){
|
public FloatingHintEditTextLayout(Context context){
|
||||||
this(context, null);
|
this(context, null);
|
||||||
|
@ -95,12 +97,22 @@ public class FloatingHintEditTextLayout extends FrameLayout{
|
||||||
label.setAlpha(0f);
|
label.setAlpha(0f);
|
||||||
|
|
||||||
edit.addTextChangedListener(new SimpleTextWatcher(this::onTextChanged));
|
edit.addTextChangedListener(new SimpleTextWatcher(this::onTextChanged));
|
||||||
|
|
||||||
|
errorView=new LinkedTextView(getContext());
|
||||||
|
errorView.setTextAppearance(R.style.m3_body_small);
|
||||||
|
errorView.setTextColor(UiUtils.getThemeColor(getContext(), R.attr.colorM3OnSurfaceVariant));
|
||||||
|
errorView.setLinkTextColor(UiUtils.getThemeColor(getContext(), R.attr.colorM3Primary));
|
||||||
|
errorView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||||
|
errorView.setPadding(V.dp(16), V.dp(4), V.dp(16), 0);
|
||||||
|
errorView.setVisibility(View.GONE);
|
||||||
|
addView(errorView);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onTextChanged(Editable text){
|
private void onTextChanged(Editable text){
|
||||||
if(errorState){
|
if(errorState){
|
||||||
|
errorView.setVisibility(View.GONE);
|
||||||
errorState=false;
|
errorState=false;
|
||||||
setForeground(getResources().getDrawable(R.drawable.bg_m3_outlined_text_field));
|
setForeground(getResources().getDrawable(R.drawable.bg_m3_outlined_text_field, getContext().getTheme()));
|
||||||
refreshDrawableState();
|
refreshDrawableState();
|
||||||
}
|
}
|
||||||
boolean newHintVisible=text.length()==0;
|
boolean newHintVisible=text.length()==0;
|
||||||
|
@ -211,12 +223,34 @@ public class FloatingHintEditTextLayout extends FrameLayout{
|
||||||
label.setTextColor(color.getColorForState(getDrawableState(), 0xff00ff00));
|
label.setTextColor(color.getColorForState(getDrawableState(), 0xff00ff00));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setErrorState(){
|
public void setErrorState(CharSequence error){
|
||||||
if(errorState)
|
if(errorState)
|
||||||
return;
|
return;
|
||||||
errorState=true;
|
errorState=true;
|
||||||
setForeground(getResources().getDrawable(R.drawable.bg_m3_outlined_text_field_error, getContext().getTheme()));
|
setForeground(getResources().getDrawable(R.drawable.bg_m3_outlined_text_field_error, getContext().getTheme()));
|
||||||
label.setTextColor(UiUtils.getThemeColor(getContext(), R.attr.colorM3Error));
|
label.setTextColor(UiUtils.getThemeColor(getContext(), R.attr.colorM3Error));
|
||||||
|
errorView.setVisibility(VISIBLE);
|
||||||
|
errorView.setText(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||||
|
if(errorView.getVisibility()!=GONE){
|
||||||
|
int width=MeasureSpec.getSize(widthMeasureSpec)-getPaddingLeft()-getPaddingRight();
|
||||||
|
LayoutParams editLP=(LayoutParams) edit.getLayoutParams();
|
||||||
|
width-=editLP.leftMargin+editLP.rightMargin;
|
||||||
|
errorView.measure(width | MeasureSpec.EXACTLY, MeasureSpec.UNSPECIFIED);
|
||||||
|
LayoutParams lp=(LayoutParams) errorView.getLayoutParams();
|
||||||
|
lp.width=width;
|
||||||
|
lp.height=errorView.getMeasuredHeight();
|
||||||
|
lp.gravity=Gravity.LEFT | Gravity.BOTTOM;
|
||||||
|
lp.leftMargin=editLP.leftMargin;
|
||||||
|
editLP.bottomMargin=errorView.getMeasuredHeight();
|
||||||
|
}else{
|
||||||
|
LayoutParams editLP=(LayoutParams) edit.getLayoutParams();
|
||||||
|
editLP.bottomMargin=0;
|
||||||
|
}
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PaddedForegroundDrawable extends Drawable{
|
private class PaddedForegroundDrawable extends Drawable{
|
||||||
|
@ -313,8 +347,8 @@ public class FloatingHintEditTextLayout extends FrameLayout{
|
||||||
@Override
|
@Override
|
||||||
protected void onBoundsChange(@NonNull Rect bounds){
|
protected void onBoundsChange(@NonNull Rect bounds){
|
||||||
super.onBoundsChange(bounds);
|
super.onBoundsChange(bounds);
|
||||||
LayoutParams lp=(LayoutParams) edit.getLayoutParams();
|
int offset=V.dp(12);
|
||||||
wrapped.setBounds(bounds.left+lp.leftMargin-V.dp(12), bounds.top, bounds.right-lp.rightMargin+V.dp(12), bounds.bottom);
|
wrapped.setBounds(edit.getLeft()-offset, edit.getTop()-offset, edit.getRight()+offset, edit.getBottom()+offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ public class ElevationOnScrollListener extends RecyclerView.OnScrollListener imp
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
||||||
handleScroll(v.getContext(), scrollY==0);
|
handleScroll(v.getContext(), scrollY<=0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleScroll(Context context, boolean newAtTop){
|
private void handleScroll(Context context, boolean newAtTop){
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="?colorM3OnSecondaryContainer" android:state_enabled="true"/>
|
||||||
|
<item android:color="?colorM3OnSurface" android:alpha="0.38"/>
|
||||||
|
</selector>
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_enabled="true">
|
||||||
|
<ripple android:color="@color/m3_on_secondary_container_overlay">
|
||||||
|
<item>
|
||||||
|
<shape>
|
||||||
|
<solid android:color="?colorM3SecondaryContainer"/>
|
||||||
|
<corners android:radius="20dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape>
|
||||||
|
<solid android:color="?colorM3DisabledBackground"/>
|
||||||
|
<corners android:radius="20dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<corners android:radius="28dp"/>
|
||||||
|
<solid android:color="?colorM3Surface"/>
|
||||||
|
</shape>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M11,19V13H5V11H11V5H13V11H19V13H13V19Z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="48dp"
|
||||||
|
android:height="48dp"
|
||||||
|
android:viewportWidth="48"
|
||||||
|
android:viewportHeight="48">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M29.45,6V9H9Q9,9 9,9Q9,9 9,9V39Q9,39 9,39Q9,39 9,39H39Q39,39 39,39Q39,39 39,39V18.6H42V39Q42,40.2 41.1,41.1Q40.2,42 39,42H9Q7.8,42 6.9,41.1Q6,40.2 6,39V9Q6,7.8 6.9,6.9Q7.8,6 9,6ZM38,6V10.05H42.05V13.05H38V17.1H35V13.05H30.95V10.05H35V6ZM12,33.9H36L28.8,24.3L22.45,32.65L17.75,26.45ZM9,9V14.55V18.6V39Q9,39 9,39Q9,39 9,39Q9,39 9,39Q9,39 9,39V9Q9,9 9,9Q9,9 9,9Z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M4,15V13H20V15ZM4,11V9H20V11Z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<me.grishka.appkit.views.FragmentRootLinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:id="@+id/appkit_loader_root"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:background="?android:colorBackground">
|
||||||
|
|
||||||
|
<include layout="@layout/appkit_toolbar"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/appkit_loader_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<include layout="@layout/loading"
|
||||||
|
android:id="@+id/loading"/>
|
||||||
|
|
||||||
|
<ViewStub android:layout="?errorViewLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/error"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/content_stub"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/button_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="end"
|
||||||
|
android:background="@drawable/bg_onboarding_panel">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_skip"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
style="@style/Widget.Mastodon.M3.Button.Tonal"
|
||||||
|
android:text="@string/skip"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_next"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||||
|
android:text="@string/follow_all"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</me.grishka.appkit.views.FragmentRootLinearLayout>
|
|
@ -0,0 +1,164 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/scroller"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:layout_marginStart="56dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:textAppearance="@style/m3_body_large"
|
||||||
|
android:textColor="?colorM3OnSurface"
|
||||||
|
android:text="@string/profile_setup_subtitle"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="144dp"
|
||||||
|
android:foreground="@drawable/ic_add_photo_alternate_48px"
|
||||||
|
android:foregroundGravity="center"
|
||||||
|
android:foregroundTint="?colorM3OnSecondaryContainer"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:background="?colorM3SecondaryContainer"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="104dp"
|
||||||
|
android:layout_height="104dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginTop="-44dp"
|
||||||
|
android:background="@drawable/bg_onboarding_avatar">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/avatar"
|
||||||
|
android:layout_width="96dp"
|
||||||
|
android:layout_height="96dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:foreground="@drawable/ic_add_photo_alternate_48px"
|
||||||
|
android:foregroundGravity="center"
|
||||||
|
android:foregroundTint="?colorM3OnSecondaryContainer"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:background="?colorM3SecondaryContainer"/>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||||
|
android:id="@+id/display_name_wrap"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
app:labelTextColor="@color/m3_outlined_text_field_label"
|
||||||
|
android:foreground="@drawable/bg_m3_outlined_text_field">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/display_name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:background="@null"
|
||||||
|
android:elevation="0dp"
|
||||||
|
android:inputType="textPersonName|textCapWords"
|
||||||
|
android:autofillHints="name"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:hint="@string/display_name"/>
|
||||||
|
|
||||||
|
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
||||||
|
|
||||||
|
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||||
|
android:id="@+id/bio_wrap"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:layout_marginTop="-8dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
app:labelTextColor="@color/m3_outlined_text_field_label"
|
||||||
|
android:foreground="@drawable/bg_m3_outlined_text_field">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/bio"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:background="@null"
|
||||||
|
android:elevation="0dp"
|
||||||
|
android:inputType="textMultiLine|textCapSentences"
|
||||||
|
android:hint="@string/profile_bio"/>
|
||||||
|
|
||||||
|
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
||||||
|
|
||||||
|
<org.joinmastodon.android.ui.views.ReorderableLinearLayout
|
||||||
|
android:id="@+id/profile_fields"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/add_row"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="24dp"
|
||||||
|
android:textAppearance="@style/m3_body_large"
|
||||||
|
android:textColor="?colorM3OnSurface"
|
||||||
|
android:drawableEnd="@drawable/ic_add_24px"
|
||||||
|
android:drawableTint="?colorM3OnSurface"
|
||||||
|
android:drawablePadding="16dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:stateListAnimator="@null"
|
||||||
|
android:text="@string/profile_add_row"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:textAppearance="@style/m3_body_small"
|
||||||
|
android:textColor="?colorM3OnSurfaceVariant"
|
||||||
|
android:text="@string/profile_setup_explanation"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:background="@drawable/bg_onboarding_panel"
|
||||||
|
android:id="@+id/button_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_next"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:minWidth="145dp"
|
||||||
|
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||||
|
android:text="@string/next" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -7,6 +7,7 @@
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
|
android:id="@+id/scroller"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
|
@ -20,7 +21,8 @@
|
||||||
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||||
android:id="@+id/display_name_wrap"
|
android:id="@+id/display_name_wrap"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="80dp"
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="80dp"
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
app:labelTextColor="@color/m3_outlined_text_field_label"
|
app:labelTextColor="@color/m3_outlined_text_field_label"
|
||||||
android:foreground="@drawable/bg_m3_outlined_text_field">
|
android:foreground="@drawable/bg_m3_outlined_text_field">
|
||||||
|
@ -78,12 +80,14 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/domain"
|
android:id="@+id/domain"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="56dp"
|
||||||
android:layout_gravity="right|center_vertical"
|
android:layout_gravity="right|top"
|
||||||
android:layout_marginRight="20dp"
|
android:layout_marginRight="20dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
android:paddingLeft="8dp"
|
android:paddingLeft="8dp"
|
||||||
android:paddingRight="16dp"
|
android:paddingRight="16dp"
|
||||||
android:textAppearance="@style/m3_body_large"
|
android:textAppearance="@style/m3_body_large"
|
||||||
|
android:gravity="center_vertical"
|
||||||
tools:text="\@mastodon.social"/>
|
tools:text="\@mastodon.social"/>
|
||||||
|
|
||||||
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
||||||
|
@ -91,8 +95,9 @@
|
||||||
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||||
android:id="@+id/email_wrap"
|
android:id="@+id/email_wrap"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="80dp"
|
android:layout_height="wrap_content"
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
app:labelTextColor="@color/m3_outlined_text_field_label"
|
app:labelTextColor="@color/m3_outlined_text_field_label"
|
||||||
android:foreground="@drawable/bg_m3_outlined_text_field">
|
android:foreground="@drawable/bg_m3_outlined_text_field">
|
||||||
|
|
||||||
|
@ -125,8 +130,9 @@
|
||||||
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||||
android:id="@+id/password_wrap"
|
android:id="@+id/password_wrap"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="80dp"
|
android:layout_height="wrap_content"
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
app:labelTextColor="@color/m3_outlined_text_field_label"
|
app:labelTextColor="@color/m3_outlined_text_field_label"
|
||||||
android:foreground="@drawable/bg_m3_outlined_text_field">
|
android:foreground="@drawable/bg_m3_outlined_text_field">
|
||||||
|
|
||||||
|
@ -160,8 +166,9 @@
|
||||||
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||||
android:id="@+id/password_confirm_wrap"
|
android:id="@+id/password_confirm_wrap"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="80dp"
|
android:layout_height="wrap_content"
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
app:labelTextColor="@color/m3_outlined_text_field_label"
|
app:labelTextColor="@color/m3_outlined_text_field_label"
|
||||||
android:foreground="@drawable/bg_m3_outlined_text_field">
|
android:foreground="@drawable/bg_m3_outlined_text_field">
|
||||||
|
|
||||||
|
@ -188,6 +195,9 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="56dp"
|
android:layout_marginStart="56dp"
|
||||||
android:layout_marginEnd="20dp"
|
android:layout_marginEnd="20dp"
|
||||||
|
android:layout_marginTop="-8dp"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
android:textAppearance="@style/m3_body_small"
|
android:textAppearance="@style/m3_body_small"
|
||||||
android:textColor="?colorM3OnSurfaceVariant"
|
android:textColor="?colorM3OnSurfaceVariant"
|
||||||
android:text="@string/password_note"/>
|
android:text="@string/password_note"/>
|
||||||
|
@ -198,6 +208,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
app:labelTextColor="@color/m3_outlined_text_field_label"
|
app:labelTextColor="@color/m3_outlined_text_field_label"
|
||||||
android:foreground="@drawable/bg_m3_outlined_text_field">
|
android:foreground="@drawable/bg_m3_outlined_text_field">
|
||||||
|
|
||||||
|
@ -223,6 +234,9 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="56dp"
|
android:layout_marginStart="56dp"
|
||||||
android:layout_marginEnd="20dp"
|
android:layout_marginEnd="20dp"
|
||||||
|
android:layout_marginTop="-8dp"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
android:textAppearance="@style/m3_body_small"
|
android:textAppearance="@style/m3_body_small"
|
||||||
android:textColor="?colorM3OnSurfaceVariant"
|
android:textColor="?colorM3OnSurfaceVariant"
|
||||||
android:text="@string/signup_reason_note"/>
|
android:text="@string/signup_reason_note"/>
|
||||||
|
@ -235,7 +249,8 @@
|
||||||
android:id="@+id/button_bar"
|
android:id="@+id/button_bar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
android:background="@drawable/bg_onboarding_panel">
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btn_next"
|
android:id="@+id/btn_next"
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/avatar"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
tools:src="#0f0"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/action_btn_wrap"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginTop="18dp"
|
||||||
|
android:layout_marginStart="-8dp">
|
||||||
|
|
||||||
|
<org.joinmastodon.android.ui.views.ProgressBarButton
|
||||||
|
android:id="@+id/action_btn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||||
|
tools:text="Follow"/>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/action_progress"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:indeterminate="true"
|
||||||
|
style="?android:progressBarStyleSmall"
|
||||||
|
android:elevation="10dp"
|
||||||
|
android:outlineProvider="none"
|
||||||
|
android:indeterminateTint="?colorM3OnPrimary"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_toEndOf="@id/avatar"
|
||||||
|
android:layout_toStartOf="@id/action_btn_wrap"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_marginTop="14dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:textAppearance="@style/m3_title_medium"
|
||||||
|
android:textColor="?colorM3OnSurface"
|
||||||
|
tools:text="User Name"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/username"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_toEndOf="@id/avatar"
|
||||||
|
android:layout_below="@id/name"
|
||||||
|
android:layout_toStartOf="@id/action_btn_wrap"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textAppearance="@style/m3_title_small"
|
||||||
|
android:textColor="?colorM3OnSurfaceVariant"
|
||||||
|
tools:text="\@username@server.social"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bio"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/avatar"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:textAppearance="@style/m3_body_medium"
|
||||||
|
android:textColor="?colorM3OnSurface"
|
||||||
|
tools:text="Description"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:background="?colorM3Background">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/dragger_thingy"
|
||||||
|
android:layout_width="56dp"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:tint="?colorM3OnSurface"
|
||||||
|
android:src="@drawable/ic_drag_handle_24px"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_toStartOf="@id/dragger_thingy"
|
||||||
|
android:background="@null"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:textAppearance="@style/m3_body_large"
|
||||||
|
android:textColor="?colorM3OnSurfaceVariant"
|
||||||
|
android:textColorHint="?colorM3OnSurfaceVariant"
|
||||||
|
android:inputType="textCapSentences"
|
||||||
|
android:hint="@string/field_content"
|
||||||
|
android:saveEnabled="false"
|
||||||
|
android:singleLine="true"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_toStartOf="@id/dragger_thingy"
|
||||||
|
android:layout_below="@id/content"
|
||||||
|
android:background="@null"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:textAppearance="@style/m3_body_medium"
|
||||||
|
android:textColor="?colorM3OnSurfaceVariant"
|
||||||
|
android:textColorHint="?colorM3OnSurfaceVariant"
|
||||||
|
android:inputType="textCapSentences"
|
||||||
|
android:hint="@string/field_label"
|
||||||
|
android:saveEnabled="false"
|
||||||
|
android:singleLine="true"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
|
@ -432,4 +432,10 @@
|
||||||
<string name="privacy_policy_explanation">TL;DR: We don\'t collect or process anything.</string>
|
<string name="privacy_policy_explanation">TL;DR: We don\'t collect or process anything.</string>
|
||||||
<!-- %s is server domain -->
|
<!-- %s is server domain -->
|
||||||
<string name="server_policy_disagree">Disagree with %s</string>
|
<string name="server_policy_disagree">Disagree with %s</string>
|
||||||
|
<string name="profile_bio">Bio</string>
|
||||||
|
<!-- Shown in a progress dialog when you tap "follow all" -->
|
||||||
|
<string name="sending_follows">Following users…</string>
|
||||||
|
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
|
||||||
|
<string name="signup_email_domain_blocked">%1$s doesn\'t allow signups from %2$s. Try a different one or <a>pick a different server</a>.</string>
|
||||||
|
<string name="signup_username_taken">This username is taken.</string>
|
||||||
</resources>
|
</resources>
|
|
@ -312,6 +312,13 @@
|
||||||
<item name="android:paddingRight">24dp</item>
|
<item name="android:paddingRight">24dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Mastodon.M3.Button.Tonal">
|
||||||
|
<item name="android:background">@drawable/bg_button_m3_tonal</item>
|
||||||
|
<item name="android:textColor">@color/button_text_m3_text</item>
|
||||||
|
<item name="android:paddingLeft">24dp</item>
|
||||||
|
<item name="android:paddingRight">24dp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="alert_title">
|
<style name="alert_title">
|
||||||
<item name="android:textColor">?android:textColorPrimary</item>
|
<item name="android:textColor">?android:textColorPrimary</item>
|
||||||
<item name="android:textSize">24dp</item>
|
<item name="android:textSize">24dp</item>
|
||||||
|
|
Loading…
Reference in New Issue