diff --git a/mastodon/build.gradle b/mastodon/build.gradle index 8e73b2dd..34056816 100644 --- a/mastodon/build.gradle +++ b/mastodon/build.gradle @@ -12,10 +12,7 @@ android { versionCode 45 versionName "1.1.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - resConfigs "en", "ar-rSA", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", - "eu-rES", "fi-rFI", "fr-rFR", "gl-rES", "hr-rHR", "hy-rAM", "it-rIT", "iw-rIL", - "ja-rJP", "kab", "ko-rKR", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ru-rRU", - "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" } buildTypes { @@ -91,4 +88,4 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.4-alpha05' androidTestImplementation 'androidx.test:runner:1.5.0-alpha02' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05' -} \ No newline at end of file +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/OAuthActivity.java b/mastodon/src/main/java/org/joinmastodon/android/OAuthActivity.java index b5c06de1..55307459 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/OAuthActivity.java +++ b/mastodon/src/main/java/org/joinmastodon/android/OAuthActivity.java @@ -64,7 +64,7 @@ public class OAuthActivity extends Activity{ .setCallback(new Callback<>(){ @Override public void onSuccess(Account account){ - AccountSessionManager.getInstance().addAccount(instance, token, account, app, true); + AccountSessionManager.getInstance().addAccount(instance, token, account, app, null); progress.dismiss(); finish(); // not calling restartMainActivity() here on purpose to have it recreated (notice different flags) diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountActivationInfo.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountActivationInfo.java new file mode 100644 index 00000000..43e5739a --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountActivationInfo.java @@ -0,0 +1,11 @@ +package org.joinmastodon.android.api.session; + +public class AccountActivationInfo{ + public String email; + public long lastEmailConfirmationResend; + + public AccountActivationInfo(String email, long lastEmailConfirmationResend){ + this.email=email; + this.lastEmailConfirmationResend=lastEmailConfirmationResend; + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java index 2bd33157..c1930eab 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java @@ -28,17 +28,19 @@ public class AccountSession{ public long filtersLastUpdated; public List wordFilters=new ArrayList<>(); public String pushAccountID; + public AccountActivationInfo activationInfo; private transient MastodonAPIController apiController; private transient StatusInteractionController statusInteractionController; private transient CacheController cacheController; private transient PushSubscriptionManager pushSubscriptionManager; - AccountSession(Token token, Account self, Application app, String domain, boolean activated){ + AccountSession(Token token, Account self, Application app, String domain, boolean activated, AccountActivationInfo activationInfo){ this.token=token; this.self=self; this.domain=domain; this.app=app; this.activated=activated; + this.activationInfo=activationInfo; infoLastUpdated=System.currentTimeMillis(); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java index 52c27480..396e1731 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java @@ -100,9 +100,9 @@ public class AccountSessionManager{ maybeUpdateShortcuts(); } - public void addAccount(Instance instance, Token token, Account self, Application app, boolean active){ + public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){ instances.put(instance.uri, instance); - AccountSession session=new AccountSession(token, self, app, instance.uri, active); + AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo); sessions.put(session.getID(), session); lastActiveAccountID=session.getID(); writeAccountsFile(); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java index 8b222c6d..35d691b6 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java @@ -5,7 +5,6 @@ import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.SpannableString; -import android.text.style.ImageSpan; import android.text.style.ReplacementSpan; import android.view.LayoutInflater; import android.view.View; @@ -15,17 +14,14 @@ import android.view.WindowInsets; import android.widget.LinearLayout; import android.widget.TextView; -import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.R; import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment; import org.joinmastodon.android.fragments.onboarding.InstanceChooserLoginFragment; -import org.joinmastodon.android.ui.InterpolatingMotionEffect; import org.joinmastodon.android.ui.views.SizeListenerFrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; -import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager2.widget.ViewPager2; import me.grishka.appkit.Nav; import me.grishka.appkit.fragments.AppKitFragment; diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java index c2c53889..dd86fdae 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java @@ -1,8 +1,8 @@ package org.joinmastodon.android.fragments.onboarding; +import android.annotation.SuppressLint; import android.content.ActivityNotFoundException; import android.content.Intent; -import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -12,19 +12,21 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; import android.widget.Button; +import android.widget.TextView; import android.widget.Toast; import org.joinmastodon.android.MainActivity; -import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.accounts.GetOwnAccount; import org.joinmastodon.android.api.requests.accounts.ResendConfirmationEmail; import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials; +import org.joinmastodon.android.api.session.AccountActivationInfo; import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.fragments.HomeFragment; import org.joinmastodon.android.fragments.SettingsFragment; import org.joinmastodon.android.model.Account; +import org.joinmastodon.android.ui.AccountSwitcherSheet; import org.joinmastodon.android.ui.utils.UiUtils; import java.io.File; @@ -35,40 +37,50 @@ import me.grishka.appkit.Nav; import me.grishka.appkit.api.APIRequest; import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.ErrorResponse; -import me.grishka.appkit.fragments.AppKitFragment; +import me.grishka.appkit.fragments.ToolbarFragment; import me.grishka.appkit.utils.V; -public class AccountActivationFragment extends AppKitFragment{ +public class AccountActivationFragment extends ToolbarFragment{ private String accountID; - private Button btn, backBtn; - private View buttonBar; + private Button openEmailBtn, resendBtn; + private View contentView; private Handler uiHandler=new Handler(Looper.getMainLooper()); private Runnable pollRunnable=this::tryGetAccount; private APIRequest currentRequest; + private Runnable resendTimer=this::updateResendTimer; + private long lastResendTime; @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); accountID=getArguments().getString("account"); + setTitle(R.string.confirm_email_title); + AccountSession session=AccountSessionManager.getInstance().getAccount(accountID); + lastResendTime=session.activationInfo!=null ? session.activationInfo.lastEmailConfirmationResend : 0; } @Nullable @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){ + public View onCreateContentView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){ View view=inflater.inflate(R.layout.fragment_onboarding_activation, container, false); - btn=view.findViewById(R.id.btn_next); - btn.setOnClickListener(v->onButtonClick()); - btn.setOnLongClickListener(v->{ + openEmailBtn=view.findViewById(R.id.btn_next); + openEmailBtn.setOnClickListener(this::onOpenEmailClick); + openEmailBtn.setOnLongClickListener(v->{ Bundle args=new Bundle(); args.putString("account", accountID); Nav.go(getActivity(), SettingsFragment.class, args); return true; }); - buttonBar=view.findViewById(R.id.button_bar); - view.findViewById(R.id.btn_back).setOnClickListener(v->onBackButtonClick()); + resendBtn=view.findViewById(R.id.btn_resend); + resendBtn.setOnClickListener(this::onResendClick); + TextView text=view.findViewById(R.id.subtitle); + AccountSession session=AccountSessionManager.getInstance().getAccount(accountID); + text.setText(getString(R.string.confirm_email_subtitle, session.activationInfo!=null ? session.activationInfo.email : "?")); + updateResendTimer(); + contentView=view; return view; } @@ -80,14 +92,32 @@ public class AccountActivationFragment extends AppKitFragment{ @Override public void onViewCreated(View view, Bundle savedInstanceState){ super.onViewCreated(view, savedInstanceState); - setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight)); + setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); + view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); + } + + @Override + protected void onUpdateToolbar(){ + super.onUpdateToolbar(); + getToolbar().setBackground(null); + getToolbar().setElevation(0); + } + + @Override + protected boolean canGoBack(){ + return true; + } + + @Override + public void onToolbarNavigationClick(){ + new AccountSwitcherSheet(getActivity()).show(); } @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); + contentView.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())); @@ -111,7 +141,7 @@ public class AccountActivationFragment extends AppKitFragment{ } } - private void onButtonClick(){ + private void onOpenEmailClick(View v){ try{ startActivity(Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_EMAIL).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); }catch(ActivityNotFoundException x){ @@ -119,12 +149,21 @@ public class AccountActivationFragment extends AppKitFragment{ } } - private void onBackButtonClick(){ + private void onResendClick(View v){ new ResendConfirmationEmail(null) .setCallback(new Callback<>(){ @Override public void onSuccess(Object result){ Toast.makeText(getActivity(), R.string.resent_email, Toast.LENGTH_SHORT).show(); + AccountSession session=AccountSessionManager.getInstance().getAccount(accountID); + if(session.activationInfo==null){ + session.activationInfo=new AccountActivationInfo("?", System.currentTimeMillis()); + }else{ + session.activationInfo.lastEmailConfirmationResend=System.currentTimeMillis(); + } + lastResendTime=session.activationInfo.lastEmailConfirmationResend; + AccountSessionManager.getInstance().writeAccountsFile(); + updateResendTimer(); } @Override @@ -152,7 +191,7 @@ public class AccountActivationFragment extends AppKitFragment{ AccountSessionManager mgr=AccountSessionManager.getInstance(); AccountSession session=mgr.getAccount(accountID); mgr.removeAccount(accountID); - mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, true); + mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, null); String newID=mgr.getLastActiveAccountID(); Bundle args=new Bundle(); args.putString("account", newID); @@ -189,4 +228,25 @@ public class AccountActivationFragment extends AppKitFragment{ }) .exec(accountID); } + + @SuppressLint("DefaultLocale") + private void updateResendTimer(){ + long sinceResend=System.currentTimeMillis()-lastResendTime; + if(sinceResend>59_000L){ + resendBtn.setText(R.string.resend); + resendBtn.setEnabled(true); + return; + } + int seconds=(int)((60_000L-sinceResend)/1000L); + resendBtn.setText(String.format("%s (%d)", getString(R.string.resend), seconds)); + if(resendBtn.isEnabled()) + resendBtn.setEnabled(false); + resendBtn.postDelayed(resendTimer, 500); + } + + @Override + public void onDestroyView(){ + super.onDestroyView(); + resendBtn.removeCallbacks(resendTimer); + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/GoogleMadeMeAddThisFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/GoogleMadeMeAddThisFragment.java index 23cfb2e5..3cf0767c 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/GoogleMadeMeAddThisFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/GoogleMadeMeAddThisFragment.java @@ -1,6 +1,7 @@ package org.joinmastodon.android.fragments.onboarding; import android.app.Activity; +import android.graphics.Paint; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; @@ -15,6 +16,7 @@ import android.widget.TextView; import org.joinmastodon.android.R; import org.joinmastodon.android.api.MastodonAPIController; import org.joinmastodon.android.model.Instance; +import org.joinmastodon.android.ui.DividerItemDecoration; import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.utils.UiUtils; import org.jsoup.Jsoup; @@ -33,6 +35,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import me.grishka.appkit.Nav; import me.grishka.appkit.fragments.AppKitFragment; +import me.grishka.appkit.fragments.ToolbarFragment; import me.grishka.appkit.imageloader.ViewImageLoader; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.utils.BindableViewHolder; @@ -46,7 +49,7 @@ import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; -public class GoogleMadeMeAddThisFragment extends AppKitFragment{ +public class GoogleMadeMeAddThisFragment extends ToolbarFragment{ private UsableRecyclerView list; private MergeRecyclerAdapter adapter; private Button btn; @@ -60,6 +63,7 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{ public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setRetainInstance(true); + setTitle(R.string.privacy_policy_title); } @Override @@ -82,37 +86,24 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{ } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ + public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false); list=view.findViewById(R.id.list); list.setLayoutManager(new LinearLayoutManager(getActivity())); - View headerView=inflater.inflate(R.layout.item_list_header, list, false); - TextView title=headerView.findViewById(R.id.title); - TextView subtitle=headerView.findViewById(R.id.subtitle); - headerView.findViewById(R.id.step_counter).setVisibility(View.GONE); - title.setText(R.string.privacy_policy_title); - subtitle.setText(R.string.privacy_policy_subtitle); + View headerView=inflater.inflate(R.layout.item_list_header_simple, list, false); + TextView text=headerView.findViewById(R.id.text); + text.setText(R.string.privacy_policy_subtitle); adapter=new MergeRecyclerAdapter(); adapter.addAdapter(new SingleViewRecyclerAdapter(headerView)); adapter.addAdapter(itemsAdapter=new ItemsAdapter()); list.setAdapter(adapter); - list.setSelector(null); - list.addItemDecoration(new RecyclerView.ItemDecoration(){ - @Override - public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){ - if(parent.getChildViewHolder(view) instanceof ItemViewHolder){ - outRect.left=outRect.right=V.dp(18.5f); - outRect.top=V.dp(16); - } - } - }); + list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3SurfaceVariant, 1, 56, 0, DividerItemDecoration.NOT_FIRST)); btn=view.findViewById(R.id.btn_next); btn.setOnClickListener(v->onButtonClick()); buttonBar=view.findViewById(R.id.button_bar); - view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this)); return view; } @@ -120,7 +111,15 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{ @Override public void onViewCreated(View view, Bundle savedInstanceState){ super.onViewCreated(view, savedInstanceState); - setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight)); + setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); + view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); + } + + @Override + protected void onUpdateToolbar(){ + super.onUpdateToolbar(); + getToolbar().setBackground(null); + getToolbar().setElevation(0); } protected void onButtonClick(){ @@ -192,24 +191,17 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{ } private class ItemViewHolder extends BindableViewHolder implements UsableRecyclerView.Clickable{ - private final TextView domain, title; - private final ImageView favicon; + private final TextView title; public ItemViewHolder(){ super(getActivity(), R.layout.item_privacy_policy_link, list); - domain=findViewById(R.id.domain); title=findViewById(R.id.title); - favicon=findViewById(R.id.favicon); - itemView.setOutlineProvider(OutlineProviders.roundedRect(10)); - itemView.setClipToOutline(true); + title.setPaintFlags(title.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); } @Override public void onBind(Item item){ - domain.setText(item.domain); title.setText(item.title); - - ViewImageLoader.load(favicon, null, new UrlImageLoaderRequest(item.faviconUrl)); } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceCatalogFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceCatalogFragment.java index a188711b..93d8264d 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceCatalogFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceCatalogFragment.java @@ -187,6 +187,8 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment getCategoriesRequest; - private TabLayout categoriesList; private String currentCategory="all"; private List categories=new ArrayList<>(); + private View topBar; + private List languages=Collections.emptyList(); + private PopupMenu langFilterMenu, speedFilterMenu; + private SignupSpeedFilter currentSignupSpeedFilter=SignupSpeedFilter.INSTANT; + private String currentLanguage=null; + private boolean searchQueryMode; + private LinearLayout filtersWrap; + private HorizontalScrollView filtersScroll; + private ImageButton backBtn, clearSearchBtn; + + private FilterChipView categoryGeneral, categorySpecialInterests; + private List regionalFilters; + private CatalogInstance.Region chosenRegion; + private CategoryChoice categoryChoice; public InstanceCatalogSignupFragment(){ super(R.layout.fragment_onboarding_common, 10); @@ -63,6 +94,12 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{ public void onAttach(Context context){ super.onAttach(context); setRefreshEnabled(false); + setRetainInstance(true); + } + + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); loadData(); } @@ -75,6 +112,25 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{ if(getActivity()==null) return; onDataLoaded(sortInstances(result), false); + + if(langFilterMenu!=null){ + Menu menu=langFilterMenu.getMenu(); + menu.clear(); + menu.add(0, 0, 0, R.string.server_filter_any_language); + languages=result.stream().map(i->i.language).distinct().filter(s->s.length()>0).sorted().collect(Collectors.toList()); + int i=1; + for(String lang:languages){ + Locale locale=Locale.forLanguageTag(lang); + String name=locale.getDisplayLanguage(locale); + if(name.equals(lang)) + name=lang.toUpperCase(); + else + name=name.substring(0, 1).toUpperCase()+name.substring(1); + menu.add(0, i, 0, name); + i++; + } + } + updateFilteredList(); } @@ -111,14 +167,14 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{ } private void updateCategories(){ - categoriesList.removeAllTabs(); - for(CatalogCategory cat:categories){ - int titleRes=getTitleForCategory(cat.category); - TabLayout.Tab tab=categoriesList.newTab().setText(titleRes!=0 ? getString(titleRes) : cat.category).setCustomView(R.layout.item_instance_category); - ImageView emoji=tab.getCustomView().findViewById(R.id.emoji); - emoji.setImageResource(getEmojiForCategory(cat.category)); - categoriesList.addTab(tab); - } +// categoriesList.removeAllTabs(); +// for(CatalogCategory cat:categories){ +// int titleRes=getTitleForCategory(cat.category); +// TabLayout.Tab tab=categoriesList.newTab().setText(titleRes!=0 ? getString(titleRes) : cat.category).setCustomView(R.layout.item_instance_category); +// ImageView emoji=tab.getCustomView().findViewById(R.id.emoji); +// emoji.setImageResource(getEmojiForCategory(cat.category)); +// categoriesList.addTab(tab); +// } } @Override @@ -130,27 +186,77 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{ @Override protected RecyclerView.Adapter getAdapter(){ - headerView=getActivity().getLayoutInflater().inflate(R.layout.header_onboarding_instance_catalog, list, false); - searchEdit=headerView.findViewById(R.id.search_edit); - categoriesList=headerView.findViewById(R.id.categories_list); - categoriesList.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){ - @Override - public void onTabSelected(TabLayout.Tab tab){ - CatalogCategory category=categories.get(tab.getPosition()); - currentCategory=category.category; - updateFilteredList(); - } + View headerView=new View(getActivity()); + headerView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1)); - @Override - public void onTabUnselected(TabLayout.Tab tab){ - - } - - @Override - public void onTabReselected(TabLayout.Tab tab){ + mergeAdapter=new MergeRecyclerAdapter(); + mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView)); + mergeAdapter.addAdapter(adapter=new InstancesAdapter()); + return mergeAdapter; + } + @SuppressLint("ClickableViewAccessibility") + @Override + public void onViewCreated(View view, Bundle savedInstanceState){ + super.onViewCreated(view, savedInstanceState); + backBtn=view.findViewById(R.id.btn_back); + backBtn.setOnClickListener(v->{ + if(searchQueryMode){ + setSearchQueryMode(false); + }else{ + Nav.finish(this); } }); + clearSearchBtn=view.findViewById(R.id.clear); + clearSearchBtn.setOnClickListener(v->searchEdit.setText("")); + nextButton.setEnabled(true); + list.setItemAnimator(new BetterItemAnimator()); + setStatusBarColor(0); + topBar=view.findViewById(R.id.top_bar); + + LayerDrawable topBg=(LayerDrawable) topBar.getBackground().mutate(); + topBar.setBackground(topBg); + Drawable topOverlay=topBg.findDrawableByLayerId(R.id.color_overlay); + topOverlay.setAlpha(0); + + LayerDrawable btmBg=(LayerDrawable) buttonBar.getBackground().mutate(); + buttonBar.setBackground(btmBg); + Drawable btmOverlay=btmBg.findDrawableByLayerId(R.id.color_overlay); + btmOverlay.setAlpha(0); + + list.addOnScrollListener(new RecyclerView.OnScrollListener(){ + private boolean isAtTop=true; + private Animator currentPanelsAnim; + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){ + boolean newAtTop=recyclerView.getChildCount()==0 || (recyclerView.getChildAdapterPosition(recyclerView.getChildAt(0))==0 && recyclerView.getChildAt(0).getTop()==recyclerView.getPaddingTop()); + if(newAtTop!=isAtTop){ + isAtTop=newAtTop; + if(currentPanelsAnim!=null) + currentPanelsAnim.cancel(); + + AnimatorSet set=new AnimatorSet(); + set.playTogether( + ObjectAnimator.ofInt(topOverlay, "alpha", isAtTop ? 0 : 20), + ObjectAnimator.ofInt(btmOverlay, "alpha", isAtTop ? 0 : 20), + ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Z, isAtTop ? 0 : V.dp(3)), + ObjectAnimator.ofFloat(buttonBar, View.TRANSLATION_Z, isAtTop ? 0 : V.dp(3)) + ); + set.setDuration(150); + set.setInterpolator(CubicBezierInterpolator.DEFAULT); + set.addListener(new AnimatorListenerAdapter(){ + @Override + public void onAnimationEnd(Animator animation){ + currentPanelsAnim=null; + } + }); + set.start(); + currentPanelsAnim=set; + } + } + }); + + searchEdit=view.findViewById(R.id.search_edit); searchEdit.setOnEditorActionListener(this::onSearchEnterPressed); searchEdit.addTextChangedListener(new TextWatcher(){ @Override @@ -166,42 +272,145 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{ @Override public void afterTextChanged(Editable s){ + if((clearSearchBtn.getVisibility()==View.VISIBLE)!=(s.length()>0)){ + clearSearchBtn.setVisibility(s.length()>0 ? View.VISIBLE : View.GONE); + } + } + }); + searchEdit.setOnFocusChangeListener((v, hasFocus)->{ + if(hasFocus && !searchQueryMode){ + setSearchQueryMode(true); } }); - mergeAdapter=new MergeRecyclerAdapter(); - mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView)); - mergeAdapter.addAdapter(adapter=new InstancesAdapter()); - return mergeAdapter; + FilterChipView langFilter=new FilterChipView(getActivity()); + langFilter.setDrawableEnd(R.drawable.ic_baseline_arrow_drop_down_18); + langFilter.setText(R.string.server_filter_any_language); + langFilterMenu=new PopupMenu(getContext(), langFilter); + langFilter.setOnTouchListener(langFilterMenu.getDragToOpenListener()); + langFilter.setOnClickListener(v->langFilterMenu.show()); + filtersWrap=view.findViewById(R.id.filters_container); + filtersScroll=view.findViewById(R.id.filters_scroll); + filtersWrap.addView(langFilter, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + FilterChipView speedFilter=new FilterChipView(getActivity()); + speedFilter.setDrawableEnd(R.drawable.ic_baseline_arrow_drop_down_18); + speedFilterMenu=new PopupMenu(getContext(), speedFilter); + speedFilterMenu.getMenu().add(0, 0, 0, R.string.server_filter_any_signup_speed); + speedFilterMenu.getMenu().add(0, 1, 0, R.string.server_filter_instant_signup); + speedFilterMenu.getMenu().add(0, 2, 0, R.string.server_filter_manual_review); + speedFilter.setOnTouchListener(speedFilterMenu.getDragToOpenListener()); + speedFilter.setOnClickListener(v->speedFilterMenu.show()); + speedFilter.setText(R.string.server_filter_instant_signup); + speedFilter.setSelected(true); + filtersWrap.addView(speedFilter, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + speedFilterMenu.setOnMenuItemClickListener(item->{ + speedFilter.setText(item.getTitle()); + speedFilter.setSelected(item.getItemId()>0); + currentSignupSpeedFilter=SignupSpeedFilter.values()[item.getItemId()]; + updateFilteredList(); + return true; + }); + langFilterMenu.setOnMenuItemClickListener(item->{ + langFilter.setText(item.getTitle()); + langFilter.setSelected(item.getItemId()>0); + currentLanguage=item.getItemId()==0 ? null : languages.get(item.getItemId()-1); + updateFilteredList(); + return true; + }); + + View divider=new View(getActivity()); + divider.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Outline)); + filtersWrap.addView(divider, new LinearLayout.LayoutParams(V.dp(.5f), ViewGroup.LayoutParams.MATCH_PARENT)); + + categoryGeneral=new FilterChipView(getActivity()); + categoryGeneral.setText(R.string.category_general); + categoryGeneral.setTag(CategoryChoice.GENERAL); + categoryGeneral.setOnClickListener(this::onCategoryFilterClick); + filtersWrap.addView(categoryGeneral, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + categorySpecialInterests=new FilterChipView(getActivity()); + categorySpecialInterests.setText(R.string.category_special_interests); + categorySpecialInterests.setTag(CategoryChoice.SPECIAL); + categorySpecialInterests.setOnClickListener(this::onCategoryFilterClick); + filtersWrap.addView(categorySpecialInterests, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + regionalFilters=Arrays.stream(CatalogInstance.Region.values()).map(r->{ + FilterChipView fv=new FilterChipView(getActivity()); + fv.setTag(r); + fv.setText(switch(r){ + case EUROPE -> R.string.server_filter_region_europe; + case NORTH_AMERICA -> R.string.server_filter_region_north_america; + case SOUTH_AMERICA -> R.string.server_filter_region_south_america; + case AFRICA -> R.string.server_filter_region_africa; + case ASIA -> R.string.server_filter_region_asia; + case OCEANIA -> R.string.server_filter_region_oceania; + }); + fv.setSelected(r==chosenRegion); + fv.setOnClickListener(this::onRegionFilterClick); + filtersWrap.addView(fv, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + return fv; + }).collect(Collectors.toList()); + } + + private void onRegionFilterClick(View v){ + CatalogInstance.Region r=(CatalogInstance.Region) v.getTag(); + if(chosenRegion==r){ + chosenRegion=null; + v.setSelected(false); + }else{ + if(chosenRegion!=null) + filtersWrap.findViewWithTag(chosenRegion).setSelected(false); + chosenRegion=r; + v.setSelected(true); + } + updateFilteredList(); + } + + private void onCategoryFilterClick(View v){ + CategoryChoice c=(CategoryChoice) v.getTag(); + if(categoryChoice==c){ + categoryChoice=null; + v.setSelected(false); + }else{ + if(categoryChoice!=null) + filtersWrap.findViewWithTag(categoryChoice).setSelected(false); + categoryChoice=c; + v.setSelected(true); + } + updateFilteredList(); } @Override - public void onViewCreated(View view, Bundle savedInstanceState){ - super.onViewCreated(view, savedInstanceState); - view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this)); - list.setItemAnimator(new BetterItemAnimator()); - list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 16, 16, DividerItemDecoration.NOT_FIRST)); - view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight)); - setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight)); + protected void onNextClick(View v){ + if(chosenInstance==null){ + String lang=Locale.getDefault().getLanguage(); + List instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general"))) && (lang.equals(ci.language) || (ci.languages!=null && ci.languages.contains(lang)))).collect(Collectors.toList()); + if(instances.isEmpty()){ + instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList()); + } + if(instances.isEmpty()){ + return; + } + chosenInstance=instances.get(new Random().nextInt(instances.size())); + } + super.onNextClick(v); } @Override protected void proceedWithAuthOrSignup(Instance instance){ getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0); - if(isSignup){ - if(!instance.registrations){ - new M3AlertDialogBuilder(getActivity()) - .setTitle(R.string.error) - .setMessage(R.string.instance_signup_closed) - .setPositiveButton(R.string.ok, null) - .show(); - return; - } - Bundle args=new Bundle(); - args.putParcelable("instance", Parcels.wrap(instance)); - Nav.go(getActivity(), InstanceRulesFragment.class, args); - }else{ + if(!instance.registrations){ + new M3AlertDialogBuilder(getActivity()) + .setTitle(R.string.error) + .setMessage(R.string.instance_signup_closed) + .setPositiveButton(R.string.ok, null) + .show(); + return; } + Bundle args=new Bundle(); + args.putParcelable("instance", Parcels.wrap(instance)); + Nav.go(getActivity(), InstanceRulesFragment.class, args); } // private String getEmojiForCategory(String category){ @@ -265,11 +474,29 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{ protected void updateFilteredList(){ ArrayList prevData=new ArrayList<>(filteredData); filteredData.clear(); - for(CatalogInstance instance:data){ - if(currentCategory.equals("all") || instance.categories.contains(currentCategory)){ - if(TextUtils.isEmpty(currentSearchQuery) || instance.domain.contains(currentSearchQuery)){ - if(instance.domain.equals(currentSearchQuery) || !isSignup || !instance.approvalRequired) + if(searchQueryMode){ + if(!TextUtils.isEmpty(currentSearchQuery)){ + for(CatalogInstance instance:data){ + if(instance.domain.contains(currentSearchQuery)){ filteredData.add(instance); + } + } + } + }else{ + for(CatalogInstance instance:data){ + if(categoryChoice==null || categoryChoice.matches(instance.category)){ + if(chosenRegion==null || instance.region==chosenRegion){ + boolean signupSpeedMatches=switch(currentSignupSpeedFilter){ + case ANY -> true; + case INSTANT -> !instance.approvalRequired; + case REVIEWED -> instance.approvalRequired; + }; + if(signupSpeedMatches){ + if(currentLanguage==null || instance.languages.contains(currentLanguage)){ + filteredData.add(instance); + } + } + } } } } @@ -296,8 +523,46 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{ }).dispatchUpdatesTo(adapter); } + @Override + public void onApplyWindowInsets(WindowInsets insets){ + topBar.setPadding(0, insets.getSystemWindowInsetTop(), 0, 0); + super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom())); + } - private class InstancesAdapter extends UsableRecyclerView.Adapter{ + @Override + public boolean onBackPressed(){ + if(searchQueryMode){ + setSearchQueryMode(false); + return true; + } + return false; + } + + private void setSearchQueryMode(boolean enabled){ + searchQueryMode=enabled; + RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) searchEdit.getLayoutParams(); + if(searchQueryMode){ + filtersScroll.setVisibility(View.GONE); + lp.removeRule(RelativeLayout.END_OF); + backBtn.setScaleX(0.83333333f); + backBtn.setScaleY(0.83333333f); + backBtn.setTranslationX(V.dp(8)); + searchEdit.setCompoundDrawableTintList(ColorStateList.valueOf(0)); + }else{ + filtersScroll.setVisibility(View.VISIBLE); + searchEdit.clearFocus(); + searchEdit.setText(""); + lp.addRule(RelativeLayout.END_OF, R.id.btn_back); + getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(searchEdit.getWindowToken(), 0); + backBtn.setScaleX(1); + backBtn.setScaleY(1); + backBtn.setTranslationX(0); + searchEdit.setCompoundDrawableTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant))); + } + updateFilteredList(); + } + + private class InstancesAdapter extends UsableRecyclerView.Adapter implements ImageLoaderRecyclerAdapter{ public InstancesAdapter(){ super(imgLoader); } @@ -323,32 +588,53 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{ public int getItemViewType(int position){ return -1; } + + @Override + public int getImageCountForItem(int position){ + return filteredData.get(position).thumbnailRequest!=null ? 1 : 0; + } + + @Override + public ImageLoaderRequest getImageRequest(int position, int image){ + return filteredData.get(position).thumbnailRequest; + } } - private class InstanceViewHolder extends BindableViewHolder implements UsableRecyclerView.Clickable{ - private final TextView title, description, userCount, lang; + private class InstanceViewHolder extends BindableViewHolder implements UsableRecyclerView.DisableableClickable, ImageLoaderViewHolder{ + private final TextView title, description; private final RadioButton radioButton; + private final ImageView thumbnail; + private boolean enabled; public InstanceViewHolder(){ super(getActivity(), R.layout.item_instance_catalog, list); title=findViewById(R.id.title); description=findViewById(R.id.description); - userCount=findViewById(R.id.user_count); - lang=findViewById(R.id.lang); radioButton=findViewById(R.id.radiobtn); - if(Build.VERSION.SDK_INTonButtonClick()); buttonBar=view.findViewById(R.id.button_bar); - view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this)); return view; } @@ -79,7 +77,15 @@ public class InstanceRulesFragment extends AppKitFragment{ @Override public void onViewCreated(View view, Bundle savedInstanceState){ super.onViewCreated(view, savedInstanceState); - setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight)); + setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); + view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); + } + + @Override + protected void onUpdateToolbar(){ + super.onUpdateToolbar(); + getToolbar().setBackground(null); + getToolbar().setElevation(0); } protected void onButtonClick(){ @@ -119,23 +125,22 @@ public class InstanceRulesFragment extends AppKitFragment{ } private class ItemViewHolder extends BindableViewHolder{ - private final TextView title, subtitle; - private final ImageView checkbox; + private final TextView text, number; public ItemViewHolder(){ - super(getActivity(), R.layout.item_report_choice, list); - title=findViewById(R.id.title); - subtitle=findViewById(R.id.subtitle); - checkbox=findViewById(R.id.checkbox); - subtitle.setVisibility(View.GONE); + super(getActivity(), R.layout.item_server_rule, list); + text=findViewById(R.id.text); + number=findViewById(R.id.number); } + @SuppressLint("DefaultLocale") @Override public void onBind(Instance.Rule item){ if(item.parsedText==null){ item.parsedText=HtmlParser.parseLinks(item.text); } - title.setText(item.parsedText); + text.setText(item.parsedText); + number.setText(String.format("%d", getAbsoluteAdapterPosition())); } } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/SignupFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/SignupFragment.java index 041627c0..987cc0b8 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/SignupFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/SignupFragment.java @@ -25,14 +25,15 @@ import org.joinmastodon.android.api.MastodonDetailedErrorResponse; import org.joinmastodon.android.api.requests.accounts.RegisterAccount; import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp; import org.joinmastodon.android.api.requests.oauth.GetOauthToken; +import org.joinmastodon.android.api.session.AccountActivationInfo; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Application; import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Token; -import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.utils.SimpleTextWatcher; import org.joinmastodon.android.ui.utils.UiUtils; +import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout; import org.parceler.Parcels; import java.io.File; @@ -49,30 +50,28 @@ import me.grishka.appkit.Nav; import me.grishka.appkit.api.APIRequest; import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.ErrorResponse; -import me.grishka.appkit.fragments.AppKitFragment; +import me.grishka.appkit.fragments.ToolbarFragment; import me.grishka.appkit.imageloader.ViewImageLoader; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.utils.V; -public class SignupFragment extends AppKitFragment{ +public class SignupFragment extends ToolbarFragment{ private static final int AVATAR_RESULT=198; private static final String TAG="SignupFragment"; private Instance instance; - private EditText displayName, username, email, password, reason; + private EditText displayName, username, email, password, passwordConfirm, reason; + private FloatingHintEditTextLayout displayNameWrap, usernameWrap, emailWrap, passwordWrap, passwordConfirmWrap, reasonWrap; private TextView reasonExplain; private Button btn; private View buttonBar; private TextWatcher buttonStateUpdater=new SimpleTextWatcher(e->updateButtonState()); - private ImageView avatar; private APIRequest currentBackgroundRequest; private Application apiApplication; private Token apiToken; private boolean submitAfterGettingToken; private ProgressDialog progressDialog; - private Uri avatarUri; - private File avatarFile; private HashSet errorFields=new HashSet<>(); @Override @@ -81,25 +80,30 @@ public class SignupFragment extends AppKitFragment{ setRetainInstance(true); instance=Parcels.unwrap(getArguments().getParcelable("instance")); createAppAndGetToken(); + setTitle(R.string.signup_title); } @Nullable @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){ + public View onCreateContentView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){ View view=inflater.inflate(R.layout.fragment_onboarding_signup, container, false); - TextView title=view.findViewById(R.id.title); TextView domain=view.findViewById(R.id.domain); displayName=view.findViewById(R.id.display_name); username=view.findViewById(R.id.username); email=view.findViewById(R.id.email); password=view.findViewById(R.id.password); - avatar=view.findViewById(R.id.avatar); + passwordConfirm=view.findViewById(R.id.password_confirm); reason=view.findViewById(R.id.reason); reasonExplain=view.findViewById(R.id.reason_explain); - View avaWrap=view.findViewById(R.id.ava_wrap); - title.setText(getString(R.string.signup_title, instance.uri)); + displayNameWrap=view.findViewById(R.id.display_name_wrap); + usernameWrap=view.findViewById(R.id.username_wrap); + emailWrap=view.findViewById(R.id.email_wrap); + passwordWrap=view.findViewById(R.id.password_wrap); + passwordConfirmWrap=view.findViewById(R.id.password_confirm_wrap); + reasonWrap=view.findViewById(R.id.reason_wrap); + domain.setText('@'+instance.uri); username.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){ @@ -114,23 +118,20 @@ public class SignupFragment extends AppKitFragment{ btn=view.findViewById(R.id.btn_next); btn.setOnClickListener(v->onButtonClick()); buttonBar=view.findViewById(R.id.button_bar); - view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this)); updateButtonState(); username.addTextChangedListener(buttonStateUpdater); email.addTextChangedListener(buttonStateUpdater); password.addTextChangedListener(buttonStateUpdater); + passwordConfirm.addTextChangedListener(buttonStateUpdater); reason.addTextChangedListener(buttonStateUpdater); username.addTextChangedListener(new ErrorClearingListener(username)); email.addTextChangedListener(new ErrorClearingListener(email)); password.addTextChangedListener(new ErrorClearingListener(password)); + passwordConfirm.addTextChangedListener(new ErrorClearingListener(passwordConfirm)); reason.addTextChangedListener(new ErrorClearingListener(reason)); - avaWrap.setOutlineProvider(OutlineProviders.roundedRect(22)); - avaWrap.setClipToOutline(true); - avaWrap.setOnClickListener(v->onAvatarClick()); - if(!instance.approvalRequired){ reason.setVisibility(View.GONE); reasonExplain.setVisibility(View.GONE); @@ -142,10 +143,23 @@ public class SignupFragment extends AppKitFragment{ @Override public void onViewCreated(View view, Bundle savedInstanceState){ super.onViewCreated(view, savedInstanceState); - setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight)); + setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); + view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); + } + + @Override + protected void onUpdateToolbar(){ + super.onUpdateToolbar(); + getToolbar().setBackground(null); + getToolbar().setElevation(0); } private void onButtonClick(){ + if(!password.getText().equals(passwordConfirm.getText())){ + passwordConfirm.setError(getString(R.string.signup_passwords_dont_match)); + passwordConfirmWrap.setErrorState(); + return; + } showProgressDialog(); if(currentBackgroundRequest!=null){ submitAfterGettingToken=true; @@ -160,32 +174,8 @@ public class SignupFragment extends AppKitFragment{ } } - private void copyAvatar(Runnable onDone){ - // Need to copy the avatar from the content provider to somewhere accessible in case the app gets killed between signup and account activation - Activity activity=getActivity(); - MastodonAPIController.runInBackground(()->{ - String origName=UiUtils.getFileName(avatarUri); - avatarFile=new File(activity.getCacheDir(), System.currentTimeMillis()+origName.substring(origName.lastIndexOf('.'))); - try(InputStream in=activity.getContentResolver().openInputStream(avatarUri); - FileOutputStream out=new FileOutputStream(avatarFile)){ - byte[] buf=new byte[10240]; - int read; - while((read=in.read(buf))>0){ - out.write(buf, 0, read); - } - }catch(IOException x){ - Log.w(TAG, "copyAvatar: error copying", x); - } - activity.runOnUiThread(onDone); - }); - } - private void submit(){ - if(avatarUri!=null && (avatarFile==null || !avatarFile.exists())){ - copyAvatar(this::actuallySubmit); - }else{ - actuallySubmit(); - } + actuallySubmit(); } private void actuallySubmit(){ @@ -204,9 +194,7 @@ public class SignupFragment extends AppKitFragment{ fakeAccount.acct=fakeAccount.username=username; fakeAccount.id="tmp"+System.currentTimeMillis(); fakeAccount.displayName=displayName.getText().toString(); - if(avatarFile!=null) - fakeAccount.avatar=avatarFile.getAbsolutePath(); - AccountSessionManager.getInstance().addAccount(instance, result, fakeAccount, apiApplication, false); + AccountSessionManager.getInstance().addAccount(instance, result, fakeAccount, apiApplication, new AccountActivationInfo(email, System.currentTimeMillis())); Bundle args=new Bundle(); args.putString("account", AccountSessionManager.getInstance().getLastActiveAccountID()); Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args); @@ -225,6 +213,7 @@ public class SignupFragment extends AppKitFragment{ continue; } field.setError(fieldErrors.get(fieldName).stream().map(err->err.description).collect(Collectors.joining("\n"))); + getFieldWrapByName(fieldName).setErrorState(); errorFields.add(field); if(first){ first=false; @@ -252,6 +241,16 @@ public class SignupFragment extends AppKitFragment{ }; } + private FloatingHintEditTextLayout getFieldWrapByName(String name){ + return switch(name){ + case "email" -> emailWrap; + case "username" -> usernameWrap; + case "password" -> passwordWrap; + case "reason" -> reasonWrap; + default -> null; + }; + } + private void showProgressDialog(){ if(progressDialog==null){ progressDialog=new ProgressDialog(getActivity()); @@ -262,7 +261,7 @@ public class SignupFragment extends AppKitFragment{ } private void updateButtonState(){ - btn.setEnabled(username.length()>0 && email.length()>0 && email.getText().toString().contains("@") && password.length()>=8 && (!instance.approvalRequired || reason.length()>0)); + btn.setEnabled(username.length()>0 && email.length()>0 && email.getText().toString().contains("@") && password.length()>=8 && passwordConfirm.length()>=8 && (!instance.approvalRequired || reason.length()>0)); } private void createAppAndGetToken(){ @@ -324,20 +323,6 @@ public class SignupFragment extends AppKitFragment{ } } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data){ - if(requestCode==AVATAR_RESULT && resultCode==Activity.RESULT_OK){ - avatarUri=data.getData(); - if(avatarFile!=null && avatarFile.exists()) - avatarFile.delete(); - ViewImageLoader.load(avatar, getResources().getDrawable(R.drawable.default_avatar), new UrlImageLoaderRequest(avatarUri, V.dp(100), V.dp(100))); - } - } - - private void onAvatarClick(){ - startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT).setType("image/*").addCategory(Intent.CATEGORY_OPENABLE), AVATAR_RESULT); - } - private class ErrorClearingListener implements TextWatcher{ public final EditText editText; diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/catalog/CatalogInstance.java b/mastodon/src/main/java/org/joinmastodon/android/model/catalog/CatalogInstance.java index 9d16dc7c..d6f8a7db 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/catalog/CatalogInstance.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/catalog/CatalogInstance.java @@ -1,5 +1,10 @@ package org.joinmastodon.android.model.catalog; +import android.graphics.Region; +import android.text.TextUtils; + +import com.google.gson.annotations.SerializedName; + import org.joinmastodon.android.api.AllFieldsAreRequired; import org.joinmastodon.android.api.ObjectValidationException; import org.joinmastodon.android.model.BaseModel; @@ -7,13 +12,17 @@ import org.joinmastodon.android.model.BaseModel; import java.net.IDN; import java.util.List; +import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; +import me.grishka.appkit.utils.V; + @AllFieldsAreRequired public class CatalogInstance extends BaseModel{ public String domain; public String version; public String description; public List languages; - public String region; + @SerializedName("region") + private String _region; public List categories; public String proxiedThumbnail; public int totalUsers; @@ -22,7 +31,9 @@ public class CatalogInstance extends BaseModel{ public String language; public String category; + public transient Region region; public transient String normalizedDomain; + public transient UrlImageLoaderRequest thumbnailRequest; @Override public void postprocess() throws ObjectValidationException{ @@ -31,6 +42,14 @@ public class CatalogInstance extends BaseModel{ normalizedDomain=IDN.toUnicode(domain); else normalizedDomain=domain; + if(!TextUtils.isEmpty(_region)){ + try{ + region=Region.valueOf(_region.toUpperCase()); + }catch(IllegalArgumentException ignore){} + } + if(!TextUtils.isEmpty(proxiedThumbnail)){ + thumbnailRequest=new UrlImageLoaderRequest(proxiedThumbnail, 0, V.dp(56)); + } } @Override @@ -50,4 +69,13 @@ public class CatalogInstance extends BaseModel{ ", category='"+category+'\''+ '}'; } + + public enum Region{ + EUROPE, + NORTH_AMERICA, + SOUTH_AMERICA, + AFRICA, + ASIA, + OCEANIA + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java b/mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java index 87fb0457..7c85a3dc 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java @@ -243,7 +243,10 @@ public class AccountSwitcherSheet extends BottomSheet{ public WrappedAccount(AccountSession session){ this.session=session; - req=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? session.self.avatar : session.self.avatarStatic, V.dp(50), V.dp(50)); + if(session.self.avatar!=null) + req=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? session.self.avatar : session.self.avatarStatic, V.dp(50), V.dp(50)); + else + req=null; } } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/FilterChipView.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/FilterChipView.java new file mode 100644 index 00000000..56038826 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/FilterChipView.java @@ -0,0 +1,59 @@ +package org.joinmastodon.android.ui.views; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.widget.Button; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.ui.utils.UiUtils; + +import androidx.annotation.DrawableRes; +import me.grishka.appkit.utils.V; + +public class FilterChipView extends Button{ + + private boolean currentlySelected; + + public FilterChipView(Context context){ + this(context, null); + } + + public FilterChipView(Context context, AttributeSet attrs){ + this(context, attrs, 0); + } + + public FilterChipView(Context context, AttributeSet attrs, int defStyle){ + super(context, attrs, defStyle); + setCompoundDrawablePadding(V.dp(8)); + setBackgroundResource(R.drawable.bg_filter_chip); + setTextAppearance(R.style.m3_label_large); + setTextColor(getResources().getColorStateList(R.color.filter_chip_text, context.getTheme())); + setCompoundDrawableTintList(ColorStateList.valueOf(UiUtils.getThemeColor(context, R.attr.colorM3OnSurface))); + updatePadding(); + } + + @Override + protected void drawableStateChanged(){ + super.drawableStateChanged(); + if(currentlySelected==isSelected()) + return; + currentlySelected=isSelected(); + Drawable start=currentlySelected ? getResources().getDrawable(R.drawable.ic_baseline_check_18, getContext().getTheme()) : null; + Drawable end=getCompoundDrawablesRelative()[2]; + setCompoundDrawablesRelativeWithIntrinsicBounds(start, null, end, null); + updatePadding(); + } + + private void updatePadding(){ + int vertical=V.dp(6); + Drawable[] drawables=getCompoundDrawablesRelative(); + setPaddingRelative(V.dp(drawables[0]==null ? 16 : 8), vertical, V.dp(drawables[2]==null ? 16 : 8), vertical); + } + + public void setDrawableEnd(@DrawableRes int drawable){ + setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, drawable, 0); + updatePadding(); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/FloatingHintEditTextLayout.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/FloatingHintEditTextLayout.java index 6e59ead5..bb92feee 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/views/FloatingHintEditTextLayout.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/FloatingHintEditTextLayout.java @@ -5,19 +5,34 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.InsetDrawable; +import android.os.Build; import android.text.Editable; import android.util.AttributeSet; import android.util.TypedValue; import android.view.Gravity; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.TextView; import org.joinmastodon.android.R; import org.joinmastodon.android.ui.utils.SimpleTextWatcher; +import org.joinmastodon.android.ui.utils.UiUtils; +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import me.grishka.appkit.utils.CubicBezierInterpolator; import me.grishka.appkit.utils.V; @@ -28,6 +43,10 @@ public class FloatingHintEditTextLayout extends FrameLayout{ private int offsetY; private boolean hintVisible; private Animator currentAnim; + private float animProgress; + private RectF tmpRect=new RectF(); + private ColorStateList labelColors, origHintColors; + private boolean errorState; public FloatingHintEditTextLayout(Context context){ this(context, null); @@ -44,7 +63,9 @@ public class FloatingHintEditTextLayout extends FrameLayout{ TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.FloatingHintEditTextLayout); labelTextSize=ta.getDimensionPixelSize(R.styleable.FloatingHintEditTextLayout_android_labelTextSize, V.dp(12)); offsetY=ta.getDimensionPixelOffset(R.styleable.FloatingHintEditTextLayout_editTextOffsetY, 0); + labelColors=ta.getColorStateList(R.styleable.FloatingHintEditTextLayout_labelTextColor); ta.recycle(); + setAddStatesFromChildren(true); } @Override @@ -58,13 +79,15 @@ public class FloatingHintEditTextLayout extends FrameLayout{ label=new TextView(getContext()); label.setTextSize(TypedValue.COMPLEX_UNIT_PX, labelTextSize); - label.setTextColor(edit.getHintTextColors()); +// label.setTextColor(labelColors==null ? edit.getHintTextColors() : labelColors); + origHintColors=edit.getHintTextColors(); label.setText(edit.getHint()); label.setSingleLine(); label.setPivotX(0f); label.setPivotY(0f); + label.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); LayoutParams lp=new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.TOP); - lp.setMarginStart(edit.getPaddingStart()); + lp.setMarginStart(edit.getPaddingStart()+((LayoutParams)edit.getLayoutParams()).getMarginStart()); addView(label, lp); hintVisible=edit.getText().length()==0; @@ -75,6 +98,11 @@ public class FloatingHintEditTextLayout extends FrameLayout{ } private void onTextChanged(Editable text){ + if(errorState){ + errorState=false; + setForeground(getResources().getDrawable(R.drawable.bg_m3_outlined_text_field)); + refreshDrawableState(); + } boolean newHintVisible=text.length()==0; if(newHintVisible==hintVisible) return; @@ -83,42 +111,210 @@ public class FloatingHintEditTextLayout extends FrameLayout{ hintVisible=newHintVisible; label.setAlpha(1); - float scale=edit.getLineHeight()/(float)label.getLineHeight(); - float transY=edit.getHeight()/2f-edit.getLineHeight()/2f+(edit.getTop()-label.getTop())-(label.getHeight()/2f-label.getLineHeight()/2f); - - AnimatorSet anim=new AnimatorSet(); - if(hintVisible){ - anim.playTogether( - ObjectAnimator.ofFloat(edit, TRANSLATION_Y, 0), - ObjectAnimator.ofFloat(label, SCALE_X, scale), - ObjectAnimator.ofFloat(label, SCALE_Y, scale), - ObjectAnimator.ofFloat(label, TRANSLATION_Y, transY) - ); - edit.setHintTextColor(0); - }else{ - label.setScaleX(scale); - label.setScaleY(scale); - label.setTranslationY(transY); - anim.playTogether( - ObjectAnimator.ofFloat(edit, TRANSLATION_Y, offsetY), - ObjectAnimator.ofFloat(label, SCALE_X, 1f), - ObjectAnimator.ofFloat(label, SCALE_Y, 1f), - ObjectAnimator.ofFloat(label, TRANSLATION_Y, 0f) - ); - } - anim.setDuration(150); - anim.setInterpolator(CubicBezierInterpolator.DEFAULT); - anim.start(); - anim.addListener(new AnimatorListenerAdapter(){ + edit.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){ @Override - public void onAnimationEnd(Animator animation){ - currentAnim=null; + public boolean onPreDraw(){ + edit.getViewTreeObserver().removeOnPreDrawListener(this); + + float scale=edit.getLineHeight()/(float)label.getLineHeight(); + float transY=edit.getHeight()/2f-edit.getLineHeight()/2f+(edit.getTop()-label.getTop())-(label.getHeight()/2f-label.getLineHeight()/2f); + + AnimatorSet anim=new AnimatorSet(); if(hintVisible){ - label.setAlpha(0); - edit.setHintTextColor(label.getTextColors()); + anim.playTogether( + ObjectAnimator.ofFloat(edit, TRANSLATION_Y, 0), + ObjectAnimator.ofFloat(label, SCALE_X, scale), + ObjectAnimator.ofFloat(label, SCALE_Y, scale), + ObjectAnimator.ofFloat(label, TRANSLATION_Y, transY), + ObjectAnimator.ofFloat(FloatingHintEditTextLayout.this, "animProgress", 0f) + ); + edit.setHintTextColor(0); + }else{ + label.setScaleX(scale); + label.setScaleY(scale); + label.setTranslationY(transY); + anim.playTogether( + ObjectAnimator.ofFloat(edit, TRANSLATION_Y, offsetY), + ObjectAnimator.ofFloat(label, SCALE_X, 1f), + ObjectAnimator.ofFloat(label, SCALE_Y, 1f), + ObjectAnimator.ofFloat(label, TRANSLATION_Y, 0f), + ObjectAnimator.ofFloat(FloatingHintEditTextLayout.this, "animProgress", 1f) + ); } + anim.setDuration(150); + anim.setInterpolator(CubicBezierInterpolator.DEFAULT); + anim.start(); + anim.addListener(new AnimatorListenerAdapter(){ + @Override + public void onAnimationEnd(Animator animation){ + currentAnim=null; + if(hintVisible){ + label.setAlpha(0); + edit.setHintTextColor(origHintColors); + } + } + }); + currentAnim=anim; + return true; } }); - currentAnim=anim; + } + + @Keep + public void setAnimProgress(float progress){ + animProgress=progress; + invalidate(); + } + + @Keep + public float getAnimProgress(){ + return animProgress; + } + + @Override + public void onDrawForeground(Canvas canvas){ + if(getForeground()!=null && animProgress>0){ + canvas.save(); + float width=(label.getWidth()+V.dp(8))*animProgress; + float centerX=label.getLeft()+label.getWidth()/2f; + tmpRect.set(centerX-width/2f, label.getTop(), centerX+width/2f, label.getBottom()); + if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O) + canvas.clipOutRect(tmpRect); + else + canvas.clipRect(tmpRect, Region.Op.DIFFERENCE); + super.onDrawForeground(canvas); + canvas.restore(); + }else{ + super.onDrawForeground(canvas); + } + } + + @Override + public void setForeground(Drawable foreground){ + super.setForeground(new PaddedForegroundDrawable(foreground)); + } + + @Override + public Drawable getForeground(){ + if(super.getForeground() instanceof PaddedForegroundDrawable pfd){ + return pfd.wrapped; + } + return null; + } + + @Override + protected void drawableStateChanged(){ + super.drawableStateChanged(); + if(label==null || errorState) + return; + ColorStateList color=labelColors==null ? origHintColors : labelColors; + label.setTextColor(color.getColorForState(getDrawableState(), 0xff00ff00)); + } + + public void setErrorState(){ + if(errorState) + return; + errorState=true; + setForeground(getResources().getDrawable(R.drawable.bg_m3_outlined_text_field_error, getContext().getTheme())); + label.setTextColor(UiUtils.getThemeColor(getContext(), R.attr.colorM3Error)); + } + + private class PaddedForegroundDrawable extends Drawable{ + private final Drawable wrapped; + + private PaddedForegroundDrawable(Drawable wrapped){ + this.wrapped=wrapped; + wrapped.setCallback(new Callback(){ + @Override + public void invalidateDrawable(@NonNull Drawable who){ + invalidateSelf(); + } + + @Override + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when){ + scheduleSelf(what, when); + } + + @Override + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what){ + unscheduleSelf(what); + } + }); + } + + @Override + public void draw(@NonNull Canvas canvas){ + wrapped.draw(canvas); + } + + @Override + public void setAlpha(int alpha){ + wrapped.setAlpha(alpha); + } + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter){ + wrapped.setColorFilter(colorFilter); + } + + @Override + public int getOpacity(){ + return wrapped.getOpacity(); + } + + @Override + public boolean setState(@NonNull int[] stateSet){ + return wrapped.setState(stateSet); + } + + @Override + public int getLayoutDirection(){ + return wrapped.getLayoutDirection(); + } + + @Override + public int getAlpha(){ + return wrapped.getAlpha(); + } + + @Nullable + @Override + public ColorFilter getColorFilter(){ + return wrapped.getColorFilter(); + } + + @Override + public boolean isStateful(){ + return wrapped.isStateful(); + } + + @NonNull + @Override + public int[] getState(){ + return wrapped.getState(); + } + + @NonNull + @Override + public Drawable getCurrent(){ + return wrapped.getCurrent(); + } + + @Override + public void applyTheme(@NonNull Resources.Theme t){ + wrapped.applyTheme(t); + } + + @Override + public boolean canApplyTheme(){ + return wrapped.canApplyTheme(); + } + + @Override + protected void onBoundsChange(@NonNull Rect bounds){ + super.onBoundsChange(bounds); + LayoutParams lp=(LayoutParams) edit.getLayoutParams(); + wrapped.setBounds(bounds.left+lp.leftMargin-V.dp(12), bounds.top, bounds.right-lp.rightMargin+V.dp(12), bounds.bottom); + } } } diff --git a/mastodon/src/main/res/color/filter_chip_text.xml b/mastodon/src/main/res/color/filter_chip_text.xml new file mode 100644 index 00000000..4f7f4bd8 --- /dev/null +++ b/mastodon/src/main/res/color/filter_chip_text.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/color/m3_on_secondary_container_overlay.xml b/mastodon/src/main/res/color/m3_on_secondary_container_overlay.xml new file mode 100644 index 00000000..db014251 --- /dev/null +++ b/mastodon/src/main/res/color/m3_on_secondary_container_overlay.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/color/m3_on_surface_variant_overlay.xml b/mastodon/src/main/res/color/m3_on_surface_variant_overlay.xml new file mode 100644 index 00000000..2cc2542a --- /dev/null +++ b/mastodon/src/main/res/color/m3_on_surface_variant_overlay.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/color/m3_outlined_text_field_label.xml b/mastodon/src/main/res/color/m3_outlined_text_field_label.xml new file mode 100644 index 00000000..759eb2c8 --- /dev/null +++ b/mastodon/src/main/res/color/m3_outlined_text_field_label.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/bg_filter_chip.xml b/mastodon/src/main/res/drawable/bg_filter_chip.xml new file mode 100644 index 00000000..d1136625 --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_filter_chip.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/bg_m3_outlined_text_field.xml b/mastodon/src/main/res/drawable/bg_m3_outlined_text_field.xml new file mode 100644 index 00000000..fd51d93d --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_m3_outlined_text_field.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/bg_m3_outlined_text_field_error.xml b/mastodon/src/main/res/drawable/bg_m3_outlined_text_field_error.xml new file mode 100644 index 00000000..add53e4d --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_m3_outlined_text_field_error.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/bg_onboarding_panel.xml b/mastodon/src/main/res/drawable/bg_onboarding_panel.xml new file mode 100644 index 00000000..a6b2c10a --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_onboarding_panel.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/empty_8dp.xml b/mastodon/src/main/res/drawable/empty_8dp.xml new file mode 100644 index 00000000..85e20cfb --- /dev/null +++ b/mastodon/src/main/res/drawable/empty_8dp.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/ic_baseline_arrow_drop_down_18.xml b/mastodon/src/main/res/drawable/ic_baseline_arrow_drop_down_18.xml new file mode 100644 index 00000000..cbe6f511 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_baseline_arrow_drop_down_18.xml @@ -0,0 +1,5 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_baseline_check_18.xml b/mastodon/src/main/res/drawable/ic_baseline_check_18.xml new file mode 100644 index 00000000..f27ae867 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_baseline_check_18.xml @@ -0,0 +1,5 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_outline_email_24.xml b/mastodon/src/main/res/drawable/ic_outline_email_24.xml new file mode 100644 index 00000000..033a1e04 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_outline_email_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_outline_link_24.xml b/mastodon/src/main/res/drawable/ic_outline_link_24.xml new file mode 100644 index 00000000..346a9f7b --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_outline_link_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_outline_password_24.xml b/mastodon/src/main/res/drawable/ic_outline_password_24.xml new file mode 100644 index 00000000..dca893e4 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_outline_password_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_outline_person_24.xml b/mastodon/src/main/res/drawable/ic_outline_person_24.xml new file mode 100644 index 00000000..57423412 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_outline_person_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/mastodon/src/main/res/drawable/round_rect.xml b/mastodon/src/main/res/drawable/round_rect.xml new file mode 100644 index 00000000..1db80022 --- /dev/null +++ b/mastodon/src/main/res/drawable/round_rect.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/layout/fragment_onboarding_activation.xml b/mastodon/src/main/res/layout/fragment_onboarding_activation.xml index 8edcd0ff..b06b3115 100644 --- a/mastodon/src/main/res/layout/fragment_onboarding_activation.xml +++ b/mastodon/src/main/res/layout/fragment_onboarding_activation.xml @@ -1,15 +1,15 @@ - + android:layout_height="match_parent" + tools:context=".fragments.onboarding.AccountActivationFragment"> + android:layout_weight="1"> - - - + - \ No newline at end of file + + +