diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index 386a8ed6e..3bc50a74f 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -117,6 +117,7 @@ import app.fedilab.android.activities.ReorderTimelinesActivity; import app.fedilab.android.activities.ScheduledActivity; import app.fedilab.android.activities.SearchResultTabActivity; import app.fedilab.android.activities.SettingsActivity; +import app.fedilab.android.activities.SuggestionActivity; import app.fedilab.android.activities.TrendsActivity; import app.fedilab.android.broadcastreceiver.NetworkStateReceiver; import app.fedilab.android.client.entities.api.Emoji; @@ -391,6 +392,9 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt } else if (id == R.id.nav_trends) { Intent intent = new Intent(this, TrendsActivity.class); startActivity(intent); + } else if (id == R.id.nav_suggestions) { + Intent intent = new Intent(this, SuggestionActivity.class); + startActivity(intent); } else if (id == R.id.nav_cache) { Intent intent = new Intent(BaseMainActivity.this, CacheActivity.class); startActivity(intent); diff --git a/app/src/main/java/app/fedilab/android/activities/SuggestionActivity.java b/app/src/main/java/app/fedilab/android/activities/SuggestionActivity.java index e0a31d57d..b9175901f 100644 --- a/app/src/main/java/app/fedilab/android/activities/SuggestionActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/SuggestionActivity.java @@ -23,11 +23,10 @@ import androidx.core.content.ContextCompat; import org.jetbrains.annotations.NotNull; import app.fedilab.android.R; -import app.fedilab.android.client.entities.app.Timeline; import app.fedilab.android.databinding.ActivitySuggestionsBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.ThemeHelper; -import app.fedilab.android.ui.fragment.timeline.FragmentMastodonAccount; +import app.fedilab.android.ui.fragment.timeline.FragmentMastodonSuggestion; public class SuggestionActivity extends BaseActivity { @@ -37,7 +36,7 @@ public class SuggestionActivity extends BaseActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ThemeHelper.applyThemeBar(this); - app.fedilab.android.databinding.ActivitySuggestionsBinding binding = ActivitySuggestionsBinding.inflate(getLayoutInflater()); + ActivitySuggestionsBinding binding = ActivitySuggestionsBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); @@ -46,9 +45,7 @@ public class SuggestionActivity extends BaseActivity { getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary))); } - Bundle bundle = new Bundle(); - bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.ACCOUNT_SUGGESTION); - Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_tags, new FragmentMastodonAccount(), bundle, null, null); + Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_suggestions, new FragmentMastodonSuggestion(), null, null, null); } diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/SuggestionAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/SuggestionAdapter.java new file mode 100644 index 000000000..c5b6918ba --- /dev/null +++ b/app/src/main/java/app/fedilab/android/ui/drawer/SuggestionAdapter.java @@ -0,0 +1,140 @@ +package app.fedilab.android.ui.drawer; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.core.app.ActivityOptionsCompat; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelStoreOwner; +import androidx.recyclerview.widget.RecyclerView; + +import java.lang.ref.WeakReference; +import java.util.List; + +import app.fedilab.android.BaseMainActivity; +import app.fedilab.android.R; +import app.fedilab.android.activities.ProfileActivity; +import app.fedilab.android.client.entities.api.Account; +import app.fedilab.android.client.entities.api.Suggestion; +import app.fedilab.android.databinding.DrawerSuggestionBinding; +import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.MastodonHelper; +import app.fedilab.android.viewmodel.mastodon.AccountsVM; + + +public class SuggestionAdapter extends RecyclerView.Adapter { + + private final List suggestionList; + private Context context; + + public SuggestionAdapter(List suggestionList) { + this.suggestionList = suggestionList; + } + + + public int getCount() { + return suggestionList.size(); + } + + public Suggestion getItem(int position) { + return suggestionList.get(position); + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + this.context = parent.getContext(); + DrawerSuggestionBinding itemBinding = DrawerSuggestionBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new SuggestionViewHolder(itemBinding); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { + Account account = suggestionList.get(position).account; + SuggestionViewHolder holder = (SuggestionViewHolder) viewHolder; + MastodonHelper.loadPPMastodon(holder.binding.avatar, account); + + + AccountsVM accountsVM = new ViewModelProvider((ViewModelStoreOwner) context).get(AccountsVM.class); + + holder.binding.avatar.setOnClickListener(v -> { + Intent intent = new Intent(context, ProfileActivity.class); + Bundle b = new Bundle(); + b.putSerializable(Helper.ARG_ACCOUNT, account); + intent.putExtras(b); + ActivityOptionsCompat options = ActivityOptionsCompat + .makeSceneTransitionAnimation((Activity) context, holder.binding.avatar, context.getString(R.string.activity_porfile_pp)); + // start the new activity + context.startActivity(intent, options.toBundle()); + }); + holder.binding.followAction.setIconResource(R.drawable.ic_baseline_person_add_24); + holder.binding.followAction.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(context, R.color.cyanea_accent_dark_reference))); + holder.binding.displayName.setText( + account.getSpanDisplayName(context, + new WeakReference<>(holder.binding.displayName)), + TextView.BufferType.SPANNABLE); + holder.binding.username.setText(String.format("@%s", account.acct)); + holder.binding.bio.setText( + account.getSpanNote(context, + new WeakReference<>(holder.binding.bio)), + TextView.BufferType.SPANNABLE); + + holder.binding.followAction.setEnabled(false); + holder.binding.followAction.setOnClickListener(v -> { + suggestionList.remove(position); + notifyItemRemoved(position); + accountsVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, true, false); + }); + holder.binding.notInterested.setOnClickListener(view -> { + suggestionList.remove(position); + notifyItemRemoved(position); + accountsVM.removeSuggestion(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id); + }); + //TODO, remove when supported + holder.binding.notInterested.setVisibility(View.GONE); + } + + public long getItemId(int position) { + return position; + } + + @Override + public int getItemCount() { + return suggestionList.size(); + } + + + public static class SuggestionViewHolder extends RecyclerView.ViewHolder { + DrawerSuggestionBinding binding; + + SuggestionViewHolder(DrawerSuggestionBinding itemView) { + super(itemView.getRoot()); + binding = itemView; + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonAccount.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonAccount.java index f2e5fd4e3..0d7f96942 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonAccount.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonAccount.java @@ -143,14 +143,6 @@ public class FragmentMastodonAccount extends Fragment { accountsVM.getBlocks(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, String.valueOf(MastodonHelper.accountsPerCall(requireActivity())), max_id, null) .observe(getViewLifecycleOwner(), this::initializeAccountCommonView); } - } else if (timelineType == Timeline.TimeLineEnum.ACCOUNT_SUGGESTION) { - if (firstLoad) { - accountsVM.getSuggestions(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null) - .observe(getViewLifecycleOwner(), this::initializeAccountCommonView); - } else { - accountsVM.getSuggestions(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, max_id) - .observe(getViewLifecycleOwner(), this::initializeAccountCommonView); - } } } diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonSuggestion.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonSuggestion.java new file mode 100644 index 000000000..417876cd3 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonSuggestion.java @@ -0,0 +1,161 @@ +package app.fedilab.android.ui.fragment.timeline; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + +import app.fedilab.android.BaseMainActivity; +import app.fedilab.android.R; +import app.fedilab.android.client.entities.api.Suggestion; +import app.fedilab.android.client.entities.api.Suggestions; +import app.fedilab.android.databinding.FragmentPaginationBinding; +import app.fedilab.android.helper.ThemeHelper; +import app.fedilab.android.ui.drawer.SuggestionAdapter; +import app.fedilab.android.viewmodel.mastodon.AccountsVM; + +public class FragmentMastodonSuggestion extends Fragment { + + + private FragmentPaginationBinding binding; + private AccountsVM accountsVM; + private boolean flagLoading; + private List suggestions; + private String max_id; + private SuggestionAdapter suggestionAdapter; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + flagLoading = false; + binding = FragmentPaginationBinding.inflate(inflater, container, false); + binding.getRoot().setBackgroundColor(ThemeHelper.getBackgroundColor(requireActivity())); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + int c1 = getResources().getColor(R.color.cyanea_accent_reference); + binding.swipeContainer.setProgressBackgroundColorSchemeColor(getResources().getColor(R.color.cyanea_primary_reference)); + binding.swipeContainer.setColorSchemeColors( + c1, c1, c1 + ); + binding.loader.setVisibility(View.VISIBLE); + binding.recyclerView.setVisibility(View.GONE); + accountsVM = new ViewModelProvider(FragmentMastodonSuggestion.this).get(AccountsVM.class); + max_id = null; + accountsVM.getSuggestions(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null) + .observe(getViewLifecycleOwner(), this::initializeAccountCommonView); + } + + + public void scrollToTop() { + binding.recyclerView.setAdapter(suggestionAdapter); + } + + /** + * Intialize the view for Suggestions + * + * @param suggestions {@link Suggestions} + */ + private void initializeAccountCommonView(final Suggestions suggestions) { + flagLoading = false; + if (binding == null || !isAdded() || getActivity() == null) { + return; + } + binding.loader.setVisibility(View.GONE); + binding.noAction.setVisibility(View.GONE); + binding.swipeContainer.setRefreshing(false); + binding.swipeContainer.setOnRefreshListener(() -> { + binding.swipeContainer.setRefreshing(true); + flagLoading = false; + max_id = null; + accountsVM.getSuggestions(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null) + .observe(getViewLifecycleOwner(), this::initializeAccountCommonView); + }); + if (suggestions == null || suggestions.suggestions == null || suggestions.suggestions.size() == 0) { + binding.noAction.setVisibility(View.VISIBLE); + binding.noActionText.setText(R.string.no_accounts); + return; + } + binding.recyclerView.setVisibility(View.VISIBLE); + + this.suggestions = suggestions.suggestions; + suggestionAdapter = new SuggestionAdapter(this.suggestions); + flagLoading = suggestions.pagination.max_id == null; + LinearLayoutManager mLayoutManager = new LinearLayoutManager(requireActivity()); + binding.recyclerView.setLayoutManager(mLayoutManager); + binding.recyclerView.setAdapter(suggestionAdapter); + max_id = suggestions.pagination.max_id; + binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition(); + if (dy > 0) { + int visibleItemCount = mLayoutManager.getChildCount(); + int totalItemCount = mLayoutManager.getItemCount(); + if (firstVisibleItem + visibleItemCount == totalItemCount) { + if (!flagLoading) { + flagLoading = true; + binding.loadingNextElements.setVisibility(View.VISIBLE); + accountsVM.getSuggestions(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, max_id) + .observe(getViewLifecycleOwner(), suggestionsPaginated -> { + dealWithPagination(suggestionsPaginated); + }); + } + } else { + binding.loadingNextElements.setVisibility(View.GONE); + } + } + + } + }); + } + + + /** + * Update view and pagination when scrolling down + * + * @param suggestions_fetched Suggestions + */ + private void dealWithPagination(Suggestions suggestions_fetched) { + flagLoading = false; + if (binding == null || !isAdded() || getActivity() == null) { + return; + } + binding.loadingNextElements.setVisibility(View.GONE); + if (this.suggestions != null && suggestions_fetched != null && suggestions_fetched.suggestions != null) { + flagLoading = suggestions_fetched.pagination.max_id == null; + int startId = this.suggestions.size(); + this.suggestions.addAll(suggestions_fetched.suggestions); + max_id = suggestions_fetched.pagination.max_id; + suggestionAdapter.notifyItemRangeInserted(startId, suggestions_fetched.suggestions.size()); + } else { + flagLoading = true; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/SuggestionVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/SuggestionVM.java deleted file mode 100644 index 04a0ac13f..000000000 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/SuggestionVM.java +++ /dev/null @@ -1,98 +0,0 @@ -package app.fedilab.android.viewmodel.mastodon; -/* Copyright 2022 Thomas Schneider - * - * This file is a part of Fedilab - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Fedilab; if not, - * see . */ - - -import android.app.Application; -import android.os.Handler; -import android.os.Looper; - -import androidx.annotation.NonNull; -import androidx.lifecycle.AndroidViewModel; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -import app.fedilab.android.client.endpoints.MastodonFiltersService; -import app.fedilab.android.client.endpoints.MastodonTimelinesService; -import app.fedilab.android.client.entities.api.Filter; -import app.fedilab.android.client.entities.api.Status; -import app.fedilab.android.client.entities.api.Statuses; -import app.fedilab.android.client.entities.app.Timeline; -import app.fedilab.android.helper.Helper; -import app.fedilab.android.helper.MastodonHelper; -import app.fedilab.android.helper.TimelineHelper; -import okhttp3.OkHttpClient; -import retrofit2.Call; -import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - -public class SuggestionVM extends AndroidViewModel { - - - final OkHttpClient okHttpClient = new OkHttpClient.Builder() - .readTimeout(60, TimeUnit.SECONDS) - .connectTimeout(60, TimeUnit.SECONDS) - .callTimeout(60, TimeUnit.SECONDS) - .proxy(Helper.getProxy(getApplication().getApplicationContext())) - .build(); - - - private MutableLiveData filterMutableLiveData; - private MutableLiveData> filterListMutableLiveData; - - public SuggestionVM(@NonNull Application application) { - super(application); - } - - private MastodonFiltersService initV2(String instance) { - Retrofit retrofit = new Retrofit.Builder() - .baseUrl("https://" + instance + "/api/v2/") - // .addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder())) - .addConverterFactory(GsonConverterFactory.create()) - .client(okHttpClient) - .build(); - return retrofit.create(MastodonFiltersService.class); - } - - - public LiveData getSuggestions(String token, @NonNull String instance, String max_id, Integer limit) { - MastodonTimelinesService mastodonTimelinesService = initV2(instance); - statusesMutableLiveData = new MutableLiveData<>(); - new Thread(() -> { - Call> publicTlCall = mastodonTimelinesService.getStatusTrends(token, max_id, limit); - Statuses statuses = new Statuses(); - if (publicTlCall != null) { - try { - Response> publicTlResponse = publicTlCall.execute(); - if (publicTlResponse.isSuccessful()) { - List statusList = publicTlResponse.body(); - statuses.statuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), statusList, Timeline.TimeLineEnum.TREND_MESSAGE); - statuses.pagination = MastodonHelper.getOffSetPagination(publicTlResponse.headers()); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - Handler mainHandler = new Handler(Looper.getMainLooper()); - Runnable myRunnable = () -> statusesMutableLiveData.setValue(statuses); - mainHandler.post(myRunnable); - }).start(); - return statusesMutableLiveData; - } -} diff --git a/app/src/main/res/drawable/ic_baseline_account_circle_24.xml b/app/src/main/res/drawable/ic_baseline_account_circle_24.xml index f9110996c..0b3fa82b1 100644 --- a/app/src/main/res/drawable/ic_baseline_account_circle_24.xml +++ b/app/src/main/res/drawable/ic_baseline_account_circle_24.xml @@ -1,7 +1,7 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml index c82203cc2..63e6efefa 100644 --- a/app/src/main/res/menu/activity_main_drawer.xml +++ b/app/src/main/res/menu/activity_main_drawer.xml @@ -58,7 +58,11 @@ android:icon="@drawable/ic_baseline_trending_up_24" android:title="@string/trending" android:visible="true" /> - + Submitted a report Signed up Suggestions + Not interested \ No newline at end of file