add custom login fragment

This commit is contained in:
sk 2022-12-12 19:18:01 -03:00 committed by LucasGGamerM
parent 9917712f66
commit ffa21b26af
10 changed files with 419 additions and 15 deletions

View File

@ -14,9 +14,9 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.ComposeFragment;
import org.joinmastodon.android.fragments.HomeFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.SplashFragment;
import org.joinmastodon.android.fragments.ThreadFragment;
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
import org.joinmastodon.android.fragments.onboarding.MegalodonLoginFragment;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
@ -33,7 +33,7 @@ public class MainActivity extends FragmentStackActivity{
if(savedInstanceState==null){
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
showFragmentClearingBackStack(new SplashFragment());
showFragmentClearingBackStack(new MegalodonLoginFragment());
}else{
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
AccountSession session;

View File

@ -84,7 +84,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
isSignup=getArguments().getBoolean("signup");
isSignup=getArguments() != null && getArguments().getBoolean("signup");
}
protected abstract void proceedWithAuthOrSignup(Instance instance);

View File

@ -0,0 +1,267 @@
package org.joinmastodon.android.fragments.onboarding;
import android.content.Context;
import android.content.res.ColorStateList;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.Space;
import android.widget.TextView;
import android.widget.Toolbar;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.catalog.CatalogInstance;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.PaginatedList;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class MegalodonLoginFragment extends InstanceCatalogFragment {
private View headerView;
public MegalodonLoginFragment() {
super(R.layout.fragment_megalodon_welcome, 1);
}
@Override
public void onAttach(Context context){
super.onAttach(context);
setRefreshEnabled(false);
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
dataLoaded();
}
@Override
protected void onUpdateToolbar(){
super.onUpdateToolbar();
if (!canGoBack()) {
ImageView toolbarLogo=new ImageView(getActivity());
toolbarLogo.setScaleType(ImageView.ScaleType.CENTER);
toolbarLogo.setImageResource(R.drawable.logo);
toolbarLogo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)));
FrameLayout logoWrap=new FrameLayout(getActivity());
FrameLayout.LayoutParams logoParams=new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER);
logoParams.setMargins(0, V.dp(2), 0, 0);
logoWrap.addView(toolbarLogo, logoParams);
getToolbar().addView(logoWrap, new Toolbar.LayoutParams(Gravity.CENTER));
} else {
setTitle(R.string.add_account);
}
}
@Override
protected void proceedWithAuthOrSignup(Instance instance) {
AccountSessionManager.getInstance().authenticate(getActivity(), instance);
}
@Override
protected void updateFilteredList(){
boolean addFakeInstance = currentSearchQuery.length()>0 && currentSearchQuery.matches("^\\S+\\.[^\\.]+$");
if(addFakeInstance){
fakeInstance.domain=fakeInstance.normalizedDomain=currentSearchQuery;
fakeInstance.description=getString(R.string.loading_instance);
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
if(list.findViewHolderForAdapterPosition(1) instanceof InstanceViewHolder ivh){
ivh.rebind();
}
}
if(filteredData.isEmpty()){
filteredData.add(fakeInstance);
adapter.notifyItemInserted(0);
}
}
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
filteredData.clear();
if(currentSearchQuery.length()>0){
boolean foundExactMatch=false;
for(CatalogInstance inst:data){
if(inst.normalizedDomain.contains(currentSearchQuery)){
filteredData.add(inst);
if(inst.normalizedDomain.equals(currentSearchQuery))
foundExactMatch=true;
}
}
if(!foundExactMatch && addFakeInstance) {
filteredData.add(0, fakeInstance);
adapter.notifyItemChanged(0);
}
}
UiUtils.updateList(prevData, filteredData, list, adapter, Objects::equals);
for(int i=0;i<list.getChildCount();i++){
list.getChildAt(i).invalidateOutline();
}
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorWindowBackground));
list.setItemAnimator(new BetterItemAnimator());
}
@Override
protected void doLoadData(int offset, int count) {}
@Override
protected RecyclerView.Adapter getAdapter(){
headerView=getActivity().getLayoutInflater().inflate(R.layout.header_megalodon_welcome, list, false);
searchEdit=headerView.findViewById(R.id.search_edit);
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
headerView.findViewById(R.id.more).setVisibility(View.GONE);
headerView.findViewById(R.id.visibility).setVisibility(View.GONE);
((TextView) headerView.findViewById(R.id.username)).setText("@megalodon");
((TextView) headerView.findViewById(R.id.name)).setText(R.string.sk_app_name);
((TextView) headerView.findViewById(R.id.timestamp)).setText(R.string.time_now);
((ImageView) headerView.findViewById(R.id.avatar)).setImageDrawable(getActivity().getDrawable(R.mipmap.ic_launcher));
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(this);
searchEdit.addTextChangedListener(new TextWatcher(){
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after){}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count){
nextButton.setEnabled(false);
chosenInstance = null;
searchEdit.removeCallbacks(searchDebouncer);
searchEdit.postDelayed(searchDebouncer, 300);
}
@Override
public void afterTextChanged(Editable s){}
});
mergeAdapter=new MergeRecyclerAdapter();
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
View spacer = new Space(getActivity());
spacer.setMinimumHeight(V.dp(8));
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(spacer));
return mergeAdapter;
}
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceViewHolder>{
public InstancesAdapter(){
super(imgLoader);
}
@NonNull
@Override
public InstanceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new InstanceViewHolder();
}
@Override
public void onBindViewHolder(InstanceViewHolder holder, int position){
holder.bind(filteredData.get(position));
chosenInstance = filteredData.get(position);
if (chosenInstance != fakeInstance) nextButton.setEnabled(true);
super.onBindViewHolder(holder, position);
}
@Override
public int getItemCount(){
return filteredData.size();
}
@Override
public int getItemViewType(int position){
return -1;
}
}
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.Clickable{
private final TextView title, description, userCount, lang;
private final RadioButton radioButton;
public InstanceViewHolder(){
super(getActivity(), R.layout.item_megalodon_instance, list);
// itemView.setPadding(V.dp(16), V.dp(16), V.dp(16), V.dp(16));
// TypedValue value = new TypedValue();
// getActivity().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, value, true);
// itemView.setBackground(getActivity().getTheme().getDrawable(R.drawable.bg_search_field));
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_INT<Build.VERSION_CODES.N){
UiUtils.fixCompoundDrawableTintOnAndroid6(userCount);
UiUtils.fixCompoundDrawableTintOnAndroid6(lang);
}
}
@Override
public void onBind(CatalogInstance item){
title.setText(item.normalizedDomain);
description.setText(item.description);
if (item == fakeInstance) {
userCount.setVisibility(View.GONE);
lang.setVisibility(View.GONE);
} else {
userCount.setVisibility(View.VISIBLE);
lang.setVisibility(View.VISIBLE);
userCount.setText(UiUtils.abbreviateNumber(item.totalUsers));
lang.setText(item.language.toUpperCase());
}
radioButton.setChecked(chosenInstance==item);
radioButton.setVisibility(View.GONE);
}
@Override
public void onClick(){
if(chosenInstance!=null){
int idx=filteredData.indexOf(chosenInstance);
if(idx!=-1){
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(mergeAdapter.getPositionForAdapter(adapter)+idx);
if(holder instanceof InstanceViewHolder ivh){
ivh.radioButton.setChecked(false);
}
}
}
radioButton.setChecked(true);
if(chosenInstance==null)
nextButton.setEnabled(true);
chosenInstance=item;
loadInstanceInfo(chosenInstance.domain, false);
onNextClick(null);
}
}
}

View File

@ -2,14 +2,12 @@ package org.joinmastodon.android.ui;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@ -25,8 +23,7 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.SplashFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.fragments.onboarding.MegalodonLoginFragment;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.List;
@ -80,7 +77,7 @@ public class AccountSwitcherSheet extends BottomSheet{
holder.avatar.setImageResource(R.drawable.ic_fluent_add_circle_24_filled);
holder.avatar.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, android.R.attr.textColorPrimary)));
adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(holder.itemView, ()->{
Nav.go(activity, SplashFragment.class, null);
Nav.go(activity, MegalodonLoginFragment.class, null);
dismiss();
}));

View File

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
<path android:pathData="M10 18c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8 3.582 8 8 8zm0-15c0.657 0 1.407 0.59 2.022 1.908 0.217 0.466 0.406 1.002 0.559 1.592H7.419c0.153-0.59 0.342-1.126 0.56-1.592C8.592 3.59 9.342 3 10 3zM7.072 4.485C6.796 5.077 6.565 5.757 6.389 6.5H3.936c0.837-1.446 2.176-2.565 3.778-3.118-0.241 0.33-0.456 0.704-0.642 1.103zM6.192 7.5C6.068 8.288 6 9.13 6 10c0 0.87 0.067 1.712 0.193 2.5H3.46C3.163 11.724 3 10.88 3 10c0-0.88 0.163-1.724 0.46-2.5h2.733zm0.197 6c0.176 0.743 0.407 1.422 0.683 2.015 0.186 0.399 0.401 0.773 0.642 1.103-1.602-0.553-2.941-1.672-3.778-3.118H6.39zm1.03 0h5.162c-0.153 0.59-0.342 1.126-0.56 1.592C11.408 16.41 10.658 17 10 17c-0.657 0-1.407-0.59-2.022-1.908C7.761 14.626 7.572 14.09 7.42 13.5zm5.375-1H7.206C7.073 11.725 7 10.883 7 10s0.074-1.725 0.206-2.5h5.588C12.927 8.275 13 9.117 13 10s-0.073 1.725-0.206 2.5zm0.817 1h2.453c-0.837 1.446-2.176 2.565-3.778 3.118 0.241-0.33 0.456-0.704 0.642-1.103 0.276-0.593 0.507-1.272 0.683-2.015zm2.93-1h-2.734C13.933 11.712 14 10.87 14 10c0-0.87-0.067-1.712-0.193-2.5h2.733C16.837 8.276 17 9.12 17 10c0 0.88-0.163 1.724-0.46 2.5zm-4.255-9.118c1.602 0.553 2.941 1.672 3.778 3.118H13.61c-0.176-0.743-0.407-1.423-0.683-2.015-0.186-0.399-0.401-0.773-0.642-1.103z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@ -17,7 +17,8 @@
android:layout_marginTop="6dp"
android:layout_marginBottom="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:visibility="gone">
<View
android:id="@+id/border_top"
android:layout_width="match_parent"

View File

@ -0,0 +1,60 @@
<?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="?colorBackgroundLight">
<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>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="?attr/colorPollVoted"/>
<LinearLayout
android:id="@+id/button_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorBackgroundLight"
android:outlineProvider="bounds"
android:orientation="horizontal"
android:elevation="0dp">
<Button
style="?primaryLargeButtonStyle"
android:id="@+id/btn_next"
android:minWidth="145dp"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_weight="1"
android:text="@string/next" />
</LinearLayout>
</me.grishka.appkit.views.FragmentRootLinearLayout>

View File

@ -0,0 +1,59 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/display_item_header" />
<TextView
style="@style/m3_headline_small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="12dp"
android:text="@string/sk_welcome_title"
/>
<TextView
style="@style/m3_body_large"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:text="@string/sk_welcome_text" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginTop="16dp"
android:background="?attr/colorPollVoted"/>
<EditText
android:id="@+id/search_edit"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:layout_marginHorizontal="16dp"
android:inputType="textFilter|textNoSuggestions"
android:singleLine="true"
android:imeOptions="actionGo"
android:drawableStart="@drawable/ic_fluent_globe_20_regular"
android:drawablePadding="12dp"
android:drawableTint="?android:textColorSecondary"
android:background="@drawable/bg_search_field"
android:paddingHorizontal="16dp"
android:elevation="0dp"
android:hint="@string/sk_example_domain"/>
<ViewStub
android:layout="?errorViewLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/error"
android:visibility="gone" />
</LinearLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_search_field">
<include layout="@layout/item_instance_catalog" />
</FrameLayout>
</FrameLayout>

View File

@ -51,11 +51,14 @@
<string name="sk_not_supported">Not supported on your device</string>
<string name="sk_poll_allow_multiple">Allow multiple choices</string>
<string name="sk_translate_post">Translate</string>
<string name="sk_translate_show_original">Show original</string>
<string name="sk_translated_using">Translated using %s</string>
<string name="sk_translate_show_original">Show original</string>
<string name="sk_translated_using">Translated using %s</string>
<string name="sk_post_language">Language: %s</string>
<string name="sk_available_languages">Available languages</string>
<string name="sk_available_languages">Available languages</string>
<string name="sk_language_name">%s (%s)</string>
<string name="sk_clear_recent_languages">Clear recent languages</string>
<string name="sk_confirm_clear_recent_languages">Are you sure you want to clear your recently used languages?</string>
</resources>
<string name="sk_clear_recent_languages">Clear recent languages</string>
<string name="sk_confirm_clear_recent_languages">Are you sure you want to clear your recently used languages?</string>
<string name="sk_welcome_title">Welcome!</string>
<string name="sk_welcome_text">The shark salutes you! To get started, please enter your home instances domain name below.</string>
<string name="sk_example_domain">example.social</string>
</resources>