From 127643549422038f61cb8f4f8fdb0fbd75370d16 Mon Sep 17 00:00:00 2001 From: Balazs Toldi Date: Sun, 30 Jun 2024 10:53:45 +0200 Subject: [PATCH] View blocked instances This commit adds the instances page to the blocks activity to display the instances blocked by the user. --- .../toldi/infinityforlemmy/AppComponent.java | 3 + .../RedditDataRoomDatabase.java | 29 +- .../account/FetchBlockedThings.java | 30 +- .../BlockedThingListingActivity.java | 18 +- .../activities/ViewUserDetailActivity.java | 3 +- .../BlockedInstancesRecyclerViewAdapter.java | 269 ++++++++++++++++++ .../asynctasks/InsertBlockedThings.java | 45 +++ .../blockedinstances/BlockedInstanceDao.java | 30 ++ .../blockedinstances/BlockedInstanceData.java | 57 ++++ .../BlockedInstanceRepository.java | 47 +++ .../BlockedInstanceViewModel.java | 61 ++++ .../BlockedInstancesListingFragment.java | 173 +++++++++++ app/src/main/res/values/strings.xml | 2 + 13 files changed, 758 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/eu/toldi/infinityforlemmy/adapters/BlockedInstancesRecyclerViewAdapter.java create mode 100644 app/src/main/java/eu/toldi/infinityforlemmy/blockedinstances/BlockedInstanceDao.java create mode 100644 app/src/main/java/eu/toldi/infinityforlemmy/blockedinstances/BlockedInstanceData.java create mode 100644 app/src/main/java/eu/toldi/infinityforlemmy/blockedinstances/BlockedInstanceRepository.java create mode 100644 app/src/main/java/eu/toldi/infinityforlemmy/blockedinstances/BlockedInstanceViewModel.java create mode 100644 app/src/main/java/eu/toldi/infinityforlemmy/fragments/BlockedInstancesListingFragment.java diff --git a/app/src/main/java/eu/toldi/infinityforlemmy/AppComponent.java b/app/src/main/java/eu/toldi/infinityforlemmy/AppComponent.java index 82101cd7..c289a3fa 100644 --- a/app/src/main/java/eu/toldi/infinityforlemmy/AppComponent.java +++ b/app/src/main/java/eu/toldi/infinityforlemmy/AppComponent.java @@ -74,6 +74,7 @@ import eu.toldi.infinityforlemmy.bottomsheetfragments.AccountChooserBottomSheetF import eu.toldi.infinityforlemmy.bottomsheetfragments.CommentMoreBottomSheetFragment; import eu.toldi.infinityforlemmy.bottomsheetfragments.FlairBottomSheetFragment; import eu.toldi.infinityforlemmy.fragments.BlockedCommunitiesListingFragment; +import eu.toldi.infinityforlemmy.fragments.BlockedInstancesListingFragment; import eu.toldi.infinityforlemmy.fragments.BlockedUsersListingFragment; import eu.toldi.infinityforlemmy.fragments.CommentsListingFragment; import eu.toldi.infinityforlemmy.fragments.FollowedUsersListingFragment; @@ -318,6 +319,8 @@ public interface AppComponent { void inject(BlockedUsersListingFragment blockedUsersListingFragment); + void inject(BlockedInstancesListingFragment blockedInstancesListingFragment); + void inject(CommentMoreBottomSheetFragment commentMoreBottomSheetFragment); void inject(PrivateMessageFragment privateMessageFragment); diff --git a/app/src/main/java/eu/toldi/infinityforlemmy/RedditDataRoomDatabase.java b/app/src/main/java/eu/toldi/infinityforlemmy/RedditDataRoomDatabase.java index 21291f5e..6a2dae82 100644 --- a/app/src/main/java/eu/toldi/infinityforlemmy/RedditDataRoomDatabase.java +++ b/app/src/main/java/eu/toldi/infinityforlemmy/RedditDataRoomDatabase.java @@ -15,6 +15,8 @@ import eu.toldi.infinityforlemmy.account.Account; import eu.toldi.infinityforlemmy.account.AccountDao; import eu.toldi.infinityforlemmy.blockedcommunity.BlockedCommunityDao; import eu.toldi.infinityforlemmy.blockedcommunity.BlockedCommunityData; +import eu.toldi.infinityforlemmy.blockedinstances.BlockedInstanceDao; +import eu.toldi.infinityforlemmy.blockedinstances.BlockedInstanceData; import eu.toldi.infinityforlemmy.blockeduser.BlockedUserDao; import eu.toldi.infinityforlemmy.blockeduser.BlockedUserData; import eu.toldi.infinityforlemmy.commentfilter.CommentFilter; @@ -47,7 +49,7 @@ import eu.toldi.infinityforlemmy.user.UserData; @Database(entities = {Account.class, SubredditData.class, SubscribedSubredditData.class, UserData.class, SubscribedUserData.class, MultiReddit.class, CustomTheme.class, RecentSearchQuery.class, ReadPost.class, PostFilter.class, PostFilterUsage.class, AnonymousMultiredditSubreddit.class, - BlockedUserData.class, BlockedCommunityData.class, CommentFilter.class, CommentFilterUsage.class}, version = 28) + BlockedUserData.class, BlockedCommunityData.class, BlockedInstanceData.class, CommentFilter.class, CommentFilterUsage.class}, version = 30) public abstract class RedditDataRoomDatabase extends RoomDatabase { public static RedditDataRoomDatabase create(final Context context) { @@ -58,7 +60,9 @@ public abstract class RedditDataRoomDatabase extends RoomDatabase { MIGRATION_9_10, MIGRATION_10_11, MIGRATION_11_12, MIGRATION_12_13, MIGRATION_13_14, MIGRATION_14_15, MIGRATION_15_16, MIGRATION_16_17, MIGRATION_17_18, MIGRATION_18_19, MIGRATION_19_20, MIGRATION_20_21, - MIGRATION_21_22, MIGRATION_22_23, MIGRATION_23_24, MIGRATION_24_25, MIGRATION_25_26, MIGRATION_26_27, MIGRATION_27_28) + MIGRATION_21_22, MIGRATION_22_23, MIGRATION_23_24, MIGRATION_24_25, + MIGRATION_25_26, MIGRATION_26_27, MIGRATION_27_28, MIGRATION_28_29, + MIGRATION_29_30) .build(); } @@ -72,6 +76,8 @@ public abstract class RedditDataRoomDatabase extends RoomDatabase { public abstract BlockedCommunityDao blockedCommunityDao(); + public abstract BlockedInstanceDao blockedInstanceDao(); + public abstract UserDao userDao(); public abstract SubscribedUserDao subscribedUserDao(); @@ -445,4 +451,23 @@ public abstract class RedditDataRoomDatabase extends RoomDatabase { "name_of_usage TEXT NOT NULL, PRIMARY KEY(name, usage, name_of_usage), FOREIGN KEY(name) REFERENCES comment_filter(name) ON DELETE CASCADE)"); } }; + + private static final Migration MIGRATION_28_29 = new Migration(28, 29) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + database.execSQL("CREATE TABLE blocked_instances" + + "(domain TEXT, id INTEGER NOT NULL, account_name TEXT NOT NULL, PRIMARY KEY( id, account_name))"); + + } + }; + + private static final Migration MIGRATION_29_30 = new Migration(29, 30) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + // Alter table "blocked_instances" to add new columns "instance_name" and "icon" + database.execSQL("ALTER TABLE blocked_instances ADD COLUMN instance_name TEXT"); + database.execSQL("ALTER TABLE blocked_instances ADD COLUMN icon TEXT"); + + } + }; } diff --git a/app/src/main/java/eu/toldi/infinityforlemmy/account/FetchBlockedThings.java b/app/src/main/java/eu/toldi/infinityforlemmy/account/FetchBlockedThings.java index 67e1a30d..def4846f 100644 --- a/app/src/main/java/eu/toldi/infinityforlemmy/account/FetchBlockedThings.java +++ b/app/src/main/java/eu/toldi/infinityforlemmy/account/FetchBlockedThings.java @@ -9,6 +9,7 @@ import java.util.List; import eu.toldi.infinityforlemmy.apis.LemmyAPI; import eu.toldi.infinityforlemmy.blockedcommunity.BlockedCommunityData; +import eu.toldi.infinityforlemmy.blockedinstances.BlockedInstanceData; import eu.toldi.infinityforlemmy.blockeduser.BlockedUserData; import eu.toldi.infinityforlemmy.subreddit.ParseSubredditData; import eu.toldi.infinityforlemmy.subreddit.SubredditData; @@ -31,10 +32,13 @@ public class FetchBlockedThings { if (siteInfo != null) { List blockedUsers = new ArrayList<>(); List blockedCommunities = new ArrayList<>(); + List blockedInstances = new ArrayList<>(); + try { JSONObject siteInfoJson = new JSONObject(siteInfo).getJSONObject("my_user"); JSONArray blockedUsersJson = (siteInfoJson.has("person_blocks")) ? siteInfoJson.getJSONArray("person_blocks") : null; JSONArray blockedCommunitiesJson = (siteInfoJson.has("community_blocks")) ? siteInfoJson.getJSONArray("community_blocks") : null; + JSONArray blockedInstancesJson = (siteInfoJson.has("instance_blocks")) ? siteInfoJson.getJSONArray("instance_blocks") : null; if (blockedUsersJson != null) { for (int i = 0; i < blockedUsersJson.length(); i++) { JSONObject blockedUserJson = blockedUsersJson.getJSONObject(i).getJSONObject("target"); @@ -57,7 +61,29 @@ public class FetchBlockedThings { blockedCommunities.add(new BlockedCommunityData(blockedCommunityData, accountName)); } } - fetchBlockedThingsListener.onFetchBlockedThingsSuccess(blockedUsers, blockedCommunities); + if (blockedInstancesJson != null) { + for (int i = 0; i < blockedInstancesJson.length(); i++) { + JSONObject blockedInstanceJson = blockedInstancesJson.getJSONObject(i); + // Get the "instance" object if it exists + JSONObject instanceJson = blockedInstanceJson.has("instance") ? blockedInstanceJson.getJSONObject("instance") : null; + if (instanceJson == null) { + continue; + } + int id = instanceJson.getInt("id"); + String domain = instanceJson.getString("domain"); + JSONObject siteJson = blockedInstanceJson.has("site") ? blockedInstanceJson.getJSONObject("site") : null; + if (siteJson == null) { + blockedInstances.add(new BlockedInstanceData(id, domain, null, null, accountName)); + continue; + } + + String name = siteJson.getString("name"); + String icon = siteJson.optString("icon"); + blockedInstances.add(new BlockedInstanceData(id, domain, name, icon, accountName)); + } + } + + fetchBlockedThingsListener.onFetchBlockedThingsSuccess(blockedUsers, blockedCommunities, blockedInstances); } catch (JSONException e) { e.printStackTrace(); fetchBlockedThingsListener.onFetchBlockedThingsFailure(); @@ -78,7 +104,7 @@ public class FetchBlockedThings { } public interface FetchBlockedThingsListener { - void onFetchBlockedThingsSuccess(List blockedUsers, List blockedCommunities); + void onFetchBlockedThingsSuccess(List blockedUsers, List blockedCommunities, List blockedInstances); void onFetchBlockedThingsFailure(); } diff --git a/app/src/main/java/eu/toldi/infinityforlemmy/activities/BlockedThingListingActivity.java b/app/src/main/java/eu/toldi/infinityforlemmy/activities/BlockedThingListingActivity.java index e2fadba4..ccf21035 100644 --- a/app/src/main/java/eu/toldi/infinityforlemmy/activities/BlockedThingListingActivity.java +++ b/app/src/main/java/eu/toldi/infinityforlemmy/activities/BlockedThingListingActivity.java @@ -51,6 +51,7 @@ import eu.toldi.infinityforlemmy.RetrofitHolder; import eu.toldi.infinityforlemmy.account.FetchBlockedThings; import eu.toldi.infinityforlemmy.asynctasks.InsertBlockedThings; import eu.toldi.infinityforlemmy.blockedcommunity.BlockedCommunityData; +import eu.toldi.infinityforlemmy.blockedinstances.BlockedInstanceData; import eu.toldi.infinityforlemmy.blockeduser.BlockedUserData; import eu.toldi.infinityforlemmy.customtheme.CustomThemeWrapper; import eu.toldi.infinityforlemmy.customviews.ViewPagerBugFixed; @@ -58,6 +59,7 @@ import eu.toldi.infinityforlemmy.customviews.slidr.Slidr; import eu.toldi.infinityforlemmy.events.GoBackToMainPageEvent; import eu.toldi.infinityforlemmy.events.SwitchAccountEvent; import eu.toldi.infinityforlemmy.fragments.BlockedCommunitiesListingFragment; +import eu.toldi.infinityforlemmy.fragments.BlockedInstancesListingFragment; import eu.toldi.infinityforlemmy.fragments.BlockedUsersListingFragment; import eu.toldi.infinityforlemmy.utils.SharedPreferencesUtils; import eu.toldi.infinityforlemmy.utils.Utils; @@ -316,9 +318,9 @@ public class BlockedThingListingActivity extends BaseActivity implements Activit if (mAccessToken != null && !(!forceLoad && mInsertSuccess)) { FetchBlockedThings.fetchBlockedThings(mRetrofit.getRetrofit(), mAccessToken, mAccountQualifiedName, new FetchBlockedThings.FetchBlockedThingsListener() { @Override - public void onFetchBlockedThingsSuccess(List blockedUsers, List blockedCommunities) { + public void onFetchBlockedThingsSuccess(List blockedUsers, List blockedCommunities, List blockedInstances) { InsertBlockedThings.insertBlockedThings(mExecutor, new Handler(), mRedditDataRoomDatabase, mAccountQualifiedName, - blockedCommunities, blockedUsers, () -> { + blockedCommunities, blockedUsers, blockedInstances, () -> { mInsertSuccess = true; sectionsPagerAdapter.stopRefreshProgressbar(); }); @@ -396,12 +398,20 @@ public class BlockedThingListingActivity extends BaseActivity implements Activit fragment.setArguments(bundle); return fragment; } + case 2: { + BlockedInstancesListingFragment fragment = new BlockedInstancesListingFragment(); + Bundle bundle = new Bundle(); + bundle.putString(BlockedInstancesListingFragment.EXTRA_ACCOUNT_NAME, mAccountQualifiedName); + bundle.putString(BlockedInstancesListingFragment.EXTRA_ACCESS_TOKEN, mAccessToken); + fragment.setArguments(bundle); + return fragment; + } } } @Override public int getCount() { - return 2; + return 3; } @Override @@ -412,7 +422,7 @@ public class BlockedThingListingActivity extends BaseActivity implements Activit case 1: return Utils.getTabTextWithCustomFont(typeface, getString(R.string.users)); case 2: - return Utils.getTabTextWithCustomFont(typeface, getString(R.string.multi_reddits)); + return Utils.getTabTextWithCustomFont(typeface, getString(R.string.instances)); } return null; diff --git a/app/src/main/java/eu/toldi/infinityforlemmy/activities/ViewUserDetailActivity.java b/app/src/main/java/eu/toldi/infinityforlemmy/activities/ViewUserDetailActivity.java index 3fb1409d..7a01d90c 100644 --- a/app/src/main/java/eu/toldi/infinityforlemmy/activities/ViewUserDetailActivity.java +++ b/app/src/main/java/eu/toldi/infinityforlemmy/activities/ViewUserDetailActivity.java @@ -83,6 +83,7 @@ import eu.toldi.infinityforlemmy.asynctasks.AddSubredditOrUserToMultiReddit; import eu.toldi.infinityforlemmy.asynctasks.CheckIsFollowingUser; import eu.toldi.infinityforlemmy.asynctasks.SwitchAccount; import eu.toldi.infinityforlemmy.blockedcommunity.BlockedCommunityData; +import eu.toldi.infinityforlemmy.blockedinstances.BlockedInstanceData; import eu.toldi.infinityforlemmy.blockeduser.BlockedUserData; import eu.toldi.infinityforlemmy.bottomsheetfragments.CopyTextBottomSheetFragment; import eu.toldi.infinityforlemmy.bottomsheetfragments.FABMoreOptionsBottomSheetFragment; @@ -1179,7 +1180,7 @@ public class ViewUserDetailActivity extends BaseActivity implements SortTypeSele FetchBlockedThings.fetchBlockedThings(mRetrofit.getRetrofit(), mAccessToken, mAccountQualifiedName, new FetchBlockedThings.FetchBlockedThingsListener() { @Override - public void onFetchBlockedThingsSuccess(List blockedUsers, List blockedCommunities) { + public void onFetchBlockedThingsSuccess(List blockedUsers, List blockedCommunities, List blockedInstances) { for (BlockedUserData blockedUserData : blockedUsers) { if (blockedUserData.getQualifiedName().equals(qualifiedName)) { isBlocked = true; diff --git a/app/src/main/java/eu/toldi/infinityforlemmy/adapters/BlockedInstancesRecyclerViewAdapter.java b/app/src/main/java/eu/toldi/infinityforlemmy/adapters/BlockedInstancesRecyclerViewAdapter.java new file mode 100644 index 00000000..6ed34d56 --- /dev/null +++ b/app/src/main/java/eu/toldi/infinityforlemmy/adapters/BlockedInstancesRecyclerViewAdapter.java @@ -0,0 +1,269 @@ +package eu.toldi.infinityforlemmy.adapters; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; +import com.bumptech.glide.request.RequestOptions; + +import java.util.List; +import java.util.concurrent.Executor; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.toldi.infinityforlemmy.R; +import eu.toldi.infinityforlemmy.RedditDataRoomDatabase; +import eu.toldi.infinityforlemmy.activities.BaseActivity; +import eu.toldi.infinityforlemmy.blockedinstances.BlockedInstanceData; +import eu.toldi.infinityforlemmy.customtheme.CustomThemeWrapper; +import jp.wasabeef.glide.transformations.RoundedCornersTransformation; +import me.zhanghai.android.fastscroll.PopupTextProvider; +import pl.droidsonroids.gif.GifImageView; +import retrofit2.Retrofit; + +public class BlockedInstancesRecyclerViewAdapter extends RecyclerView.Adapter implements PopupTextProvider { + private static final int VIEW_TYPE_FAVORITE_SUBREDDIT_DIVIDER = 0; + private static final int VIEW_TYPE_FAVORITE_SUBREDDIT = 1; + private static final int VIEW_TYPE_SUBREDDIT_DIVIDER = 2; + private static final int VIEW_TYPE_SUBREDDIT = 3; + + private BaseActivity mActivity; + private Executor mExecutor; + private Retrofit mOauthRetrofit; + private RedditDataRoomDatabase mRedditDataRoomDatabase; + private List mBlockedInstanceData; + private List mFavoriteBlockedInstanceData; + private RequestManager glide; + private ItemClickListener itemClickListener; + + private String accessToken; + private String instancename; + private String instanceIconUrl; + private boolean hasClearSelectionRow; + + private int primaryTextColor; + private int secondaryTextColor; + + public BlockedInstancesRecyclerViewAdapter(BaseActivity activity, Executor executor, Retrofit oauthRetrofit, + RedditDataRoomDatabase redditDataRoomDatabase, + CustomThemeWrapper customThemeWrapper, + String accessToken) { + mActivity = activity; + mExecutor = executor; + glide = Glide.with(activity); + mOauthRetrofit = oauthRetrofit; + mRedditDataRoomDatabase = redditDataRoomDatabase; + this.accessToken = accessToken; + primaryTextColor = customThemeWrapper.getPrimaryTextColor(); + secondaryTextColor = customThemeWrapper.getSecondaryTextColor(); + } + + public BlockedInstancesRecyclerViewAdapter(BaseActivity activity, Executor executor, Retrofit oauthRetrofit, + RedditDataRoomDatabase redditDataRoomDatabase, + CustomThemeWrapper customThemeWrapper, + String accessToken, boolean hasClearSelectionRow, + ItemClickListener itemClickListener) { + this(activity, executor, oauthRetrofit, redditDataRoomDatabase, customThemeWrapper, accessToken); + this.hasClearSelectionRow = hasClearSelectionRow; + this.itemClickListener = itemClickListener; + } + + @Override + public int getItemViewType(int position) { + if (mFavoriteBlockedInstanceData != null && mFavoriteBlockedInstanceData.size() > 0) { + if (itemClickListener != null && !hasClearSelectionRow) { + if (position == 0) { + return VIEW_TYPE_SUBREDDIT; + } else if (position == 1) { + return VIEW_TYPE_FAVORITE_SUBREDDIT_DIVIDER; + } else if (position == mFavoriteBlockedInstanceData.size() + 2) { + return VIEW_TYPE_SUBREDDIT_DIVIDER; + } else if (position <= mFavoriteBlockedInstanceData.size() + 1) { + return VIEW_TYPE_FAVORITE_SUBREDDIT; + } else { + return VIEW_TYPE_SUBREDDIT; + } + } else if (hasClearSelectionRow) { + if (position == 0) { + return VIEW_TYPE_SUBREDDIT; + } else if (position == 1) { + return VIEW_TYPE_SUBREDDIT; + } else if (position == 2) { + return VIEW_TYPE_FAVORITE_SUBREDDIT_DIVIDER; + } else if (position == mFavoriteBlockedInstanceData.size() + 3) { + return VIEW_TYPE_SUBREDDIT_DIVIDER; + } else if (position <= mFavoriteBlockedInstanceData.size() + 2) { + return VIEW_TYPE_FAVORITE_SUBREDDIT; + } else { + return VIEW_TYPE_SUBREDDIT; + } + } else { + if (position == 0) { + return VIEW_TYPE_FAVORITE_SUBREDDIT_DIVIDER; + } else if (position == mFavoriteBlockedInstanceData.size() + 1) { + return VIEW_TYPE_SUBREDDIT_DIVIDER; + } else if (position <= mFavoriteBlockedInstanceData.size()) { + return VIEW_TYPE_FAVORITE_SUBREDDIT; + } else { + return VIEW_TYPE_SUBREDDIT; + } + } + } else { + return VIEW_TYPE_SUBREDDIT; + } + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { + return new InstanceViewHolder(LayoutInflater.from(viewGroup.getContext()) + .inflate(R.layout.item_subscribed_thing, viewGroup, false)); + } + + @Override + public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder viewHolder, int i) { + + String name; + String iconUrl; + + if (hasClearSelectionRow && viewHolder.getBindingAdapterPosition() == 0) { + ((InstanceViewHolder) viewHolder).subredditNameTextView.setText(R.string.all_communities); + viewHolder.itemView.setOnClickListener(view -> itemClickListener.onClick(null)); + return; + } else { + int offset = hasClearSelectionRow ? 1 : 0; + BlockedInstanceData instanceData = mBlockedInstanceData.get(viewHolder.getBindingAdapterPosition() - offset); + String domain = mBlockedInstanceData.get(viewHolder.getBindingAdapterPosition() - offset).getDomain(); + String instanceName = mBlockedInstanceData.get(viewHolder.getBindingAdapterPosition() - offset).getName(); + name = instanceName != null ? instanceName + " (" + domain + ")" : domain; + iconUrl = mBlockedInstanceData.get(viewHolder.getBindingAdapterPosition() - offset).getIcon(); + } + + if (itemClickListener == null) { + // TODO: 2020-07-29 Add instance view page + } + + if (iconUrl == null || iconUrl.equals("")) { + ((InstanceViewHolder) viewHolder).iconGifImageView.setVisibility(View.GONE); + } else { + ((InstanceViewHolder) viewHolder).iconGifImageView.setVisibility(View.VISIBLE); + glide.load(iconUrl) + .apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0))) + .error(glide.load(R.drawable.subreddit_default_icon) + .apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0)))) + .into(((InstanceViewHolder) viewHolder).iconGifImageView); + } + + + ((InstanceViewHolder) viewHolder).subredditNameTextView.setText(name); + + + } + + @Override + public int getItemCount() { + if (mBlockedInstanceData != null) { + + if (itemClickListener != null) { + return mBlockedInstanceData.size() > 0 ? mBlockedInstanceData.size() + ((hasClearSelectionRow) ? 1 : 0) : 0; + } + + return mBlockedInstanceData.size(); + } + return 0; + } + + @Override + public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { + glide.clear(((InstanceViewHolder) holder).iconGifImageView); + } + + public void blockedInstances(List subscribedSubreddits) { + mBlockedInstanceData = subscribedSubreddits; + notifyDataSetChanged(); + } + + public void setFavoriteSubscribedSubreddits(List favoriteBlockedInstanceData) { + mFavoriteBlockedInstanceData = favoriteBlockedInstanceData; + notifyDataSetChanged(); + } + + public void addInstance(String instancename, String instanceIconUrl) { + this.instancename = instancename; + this.instanceIconUrl = instanceIconUrl; + } + + @NonNull + @Override + public String getPopupText(int position) { + switch (getItemViewType(position)) { + case VIEW_TYPE_SUBREDDIT: + if (hasClearSelectionRow && position == 0) { + return ""; + } else if (itemClickListener != null && !hasClearSelectionRow && position == 0) { + return ""; + } else if (hasClearSelectionRow && position == 1) { + return ""; + } else { + int offset; + if (itemClickListener != null) { + if (hasClearSelectionRow) { + offset = (mFavoriteBlockedInstanceData != null && mFavoriteBlockedInstanceData.size() > 0) ? + mFavoriteBlockedInstanceData.size() + 4 : 0; + } else { + offset = (mFavoriteBlockedInstanceData != null && mFavoriteBlockedInstanceData.size() > 0) ? + mFavoriteBlockedInstanceData.size() + 3 : 0; + } + } else { + offset = (mFavoriteBlockedInstanceData != null && mFavoriteBlockedInstanceData.size() > 0) ? + mFavoriteBlockedInstanceData.size() + 2 : 0; + } + + return mBlockedInstanceData.get(position - offset).getDomain().substring(0, 1).toUpperCase(); + } + case VIEW_TYPE_FAVORITE_SUBREDDIT: + int offset; + if (itemClickListener != null) { + if (hasClearSelectionRow) { + offset = 3; + } else { + offset = 2; + } + } else { + offset = 1; + } + return mFavoriteBlockedInstanceData.get(position - offset).getDomain().substring(0, 1).toUpperCase(); + default: + return ""; + } + } + + public interface ItemClickListener { + void onClick(BlockedInstanceData subredditData); + } + + class InstanceViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.thing_icon_gif_image_view_item_subscribed_thing) + GifImageView iconGifImageView; + @BindView(R.id.thing_name_text_view_item_subscribed_thing) + TextView subredditNameTextView; + + @BindView(R.id.thing_instance_text_view_item_subscribed_thing) + TextView instanceInstanceTextView; + + InstanceViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + if (mActivity.typeface != null) { + subredditNameTextView.setTypeface(mActivity.typeface); + } + subredditNameTextView.setTextColor(primaryTextColor); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/toldi/infinityforlemmy/asynctasks/InsertBlockedThings.java b/app/src/main/java/eu/toldi/infinityforlemmy/asynctasks/InsertBlockedThings.java index ebce9c2e..958943e6 100644 --- a/app/src/main/java/eu/toldi/infinityforlemmy/asynctasks/InsertBlockedThings.java +++ b/app/src/main/java/eu/toldi/infinityforlemmy/asynctasks/InsertBlockedThings.java @@ -12,6 +12,7 @@ import java.util.concurrent.Executor; import eu.toldi.infinityforlemmy.RedditDataRoomDatabase; import eu.toldi.infinityforlemmy.blockedcommunity.BlockedCommunityDao; import eu.toldi.infinityforlemmy.blockedcommunity.BlockedCommunityData; +import eu.toldi.infinityforlemmy.blockedinstances.BlockedInstanceData; import eu.toldi.infinityforlemmy.blockeduser.BlockedUserDao; import eu.toldi.infinityforlemmy.blockeduser.BlockedUserData; @@ -22,6 +23,7 @@ public class InsertBlockedThings { RedditDataRoomDatabase redditDataRoomDatabase, @Nullable String accountName, List blockedCommunityDataList, List blockedUserDataDataList, + List blockedInstanceDataList, InsertBlockedThingListener insertSubscribedThingListener) { executor.execute(() -> { @@ -68,6 +70,23 @@ public class InsertBlockedThings { } } + if (blockedInstanceDataList != null) { + List existingBlockedInstanceDataList = + redditDataRoomDatabase.blockedInstanceDao().getAllInstanceInstancesList(accountName); + Collections.sort(blockedInstanceDataList, (subscribedInstanceData, t1) -> subscribedInstanceData.getDomain().compareToIgnoreCase(t1.getDomain())); + List unblockedInstances = new ArrayList<>(); + compareTwoBlockedInstanceList(blockedInstanceDataList, existingBlockedInstanceDataList, + unblockedInstances); + + for (String unblocked : unblockedInstances) { + redditDataRoomDatabase.blockedInstanceDao().deleteInstanceUser(unblocked, accountName); + } + + for (BlockedInstanceData s : blockedInstanceDataList) { + redditDataRoomDatabase.blockedInstanceDao().insert(s); + } + } + handler.post(insertSubscribedThingListener::insertSuccess); }); } @@ -124,6 +143,32 @@ public class InsertBlockedThings { } } + private static void compareTwoBlockedInstanceList(List newBlockedInstances, + List oldBlockedInstances, + List unblockedInstances) { + int newIndex = 0; + for (int oldIndex = 0; oldIndex < oldBlockedInstances.size(); oldIndex++) { + if (newIndex >= newBlockedInstances.size()) { + for (; oldIndex < oldBlockedInstances.size(); oldIndex++) { + unblockedInstances.add(oldBlockedInstances.get(oldIndex).getDomain()); + } + return; + } + + BlockedInstanceData old = oldBlockedInstances.get(oldIndex); + for (; newIndex < newBlockedInstances.size(); newIndex++) { + if (newBlockedInstances.get(newIndex).getDomain().compareToIgnoreCase(old.getDomain()) == 0) { + newIndex++; + break; + } + if (newBlockedInstances.get(newIndex).getDomain().compareToIgnoreCase(old.getDomain()) > 0) { + unblockedInstances.add(old.getDomain()); + break; + } + } + } + } + public interface InsertBlockedThingListener { void insertSuccess(); } diff --git a/app/src/main/java/eu/toldi/infinityforlemmy/blockedinstances/BlockedInstanceDao.java b/app/src/main/java/eu/toldi/infinityforlemmy/blockedinstances/BlockedInstanceDao.java new file mode 100644 index 00000000..8862766e --- /dev/null +++ b/app/src/main/java/eu/toldi/infinityforlemmy/blockedinstances/BlockedInstanceDao.java @@ -0,0 +1,30 @@ +package eu.toldi.infinityforlemmy.blockedinstances; + +import androidx.lifecycle.LiveData; +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; + +import java.util.List; + +@Dao +public interface BlockedInstanceDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insert(BlockedInstanceData instanceData); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insertAll(List blockedUserDataDataList); + + @Query("SELECT * FROM blocked_instances WHERE account_name = :accountName AND domain LIKE '%' || :searchQuery || '%' COLLATE NOCASE ORDER BY domain COLLATE NOCASE ASC") + LiveData> getAllBlockedInstancesWithSearchQuery(String accountName, String searchQuery); + + @Query("SELECT * FROM blocked_instances WHERE account_name = :accountName COLLATE NOCASE ORDER BY domain COLLATE NOCASE ASC") + List getAllInstanceInstancesList(String accountName); + + @Query("SELECT * FROM blocked_instances WHERE domain = :domain COLLATE NOCASE AND account_name = :accountName COLLATE NOCASE LIMIT 1") + BlockedInstanceData getInstanceUser(String domain, String accountName); + + @Query("DELETE FROM blocked_instances WHERE domain = :domain COLLATE NOCASE AND account_name = :accountName COLLATE NOCASE") + void deleteInstanceUser(String domain, String accountName); +} diff --git a/app/src/main/java/eu/toldi/infinityforlemmy/blockedinstances/BlockedInstanceData.java b/app/src/main/java/eu/toldi/infinityforlemmy/blockedinstances/BlockedInstanceData.java new file mode 100644 index 00000000..a8b8a9fc --- /dev/null +++ b/app/src/main/java/eu/toldi/infinityforlemmy/blockedinstances/BlockedInstanceData.java @@ -0,0 +1,57 @@ +package eu.toldi.infinityforlemmy.blockedinstances; + +import androidx.annotation.NonNull; +import androidx.room.ColumnInfo; +import androidx.room.Entity; + +@Entity(tableName = "blocked_instances", primaryKeys = {"id", "account_name"}) +public class BlockedInstanceData { + @ColumnInfo(name = "id") + private final int id; + + @ColumnInfo(name = "domain") + private String domain; + + @NonNull + @ColumnInfo(name = "account_name") + private String accountName; + + @ColumnInfo(name = "instance_name") + private String name; + + @ColumnInfo(name = "icon") + private String icon; + + + public BlockedInstanceData(int id, String domain, String name, String icon, String accountName) { + this.id = id; + this.domain = domain; + this.icon = icon; + this.name = name; + this.accountName = accountName; + } + + public int getId() { + return id; + } + + public String getDomain() { + return domain; + } + + public String getAccountName() { + return accountName; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public String getName() { + return name; + } + + public String getIcon() { + return icon; + } +} diff --git a/app/src/main/java/eu/toldi/infinityforlemmy/blockedinstances/BlockedInstanceRepository.java b/app/src/main/java/eu/toldi/infinityforlemmy/blockedinstances/BlockedInstanceRepository.java new file mode 100644 index 00000000..9ed69b44 --- /dev/null +++ b/app/src/main/java/eu/toldi/infinityforlemmy/blockedinstances/BlockedInstanceRepository.java @@ -0,0 +1,47 @@ +package eu.toldi.infinityforlemmy.blockedinstances; + +import android.os.AsyncTask; + +import androidx.lifecycle.LiveData; + +import java.util.List; + +import eu.toldi.infinityforlemmy.RedditDataRoomDatabase; + +public class BlockedInstanceRepository { + + private BlockedInstanceDao blockedInstanceDao; + private String mAccountName; + + BlockedInstanceRepository(RedditDataRoomDatabase redditDataRoomDatabase, String accountName) { + blockedInstanceDao = redditDataRoomDatabase.blockedInstanceDao(); + mAccountName = accountName; + } + + LiveData> getAllBlockedInstancesWithSearchQuery(String searchQuery) { + return blockedInstanceDao.getAllBlockedInstancesWithSearchQuery(mAccountName, searchQuery); + } + + LiveData> getAllFavoriteSubscribedInstancesWithSearchQuery(String searchQuery) { + return blockedInstanceDao.getAllBlockedInstancesWithSearchQuery(mAccountName, searchQuery); + } + + public void insert(BlockedInstanceData BlockedInstanceData) { + new BlockedInstanceRepository.insertAsyncTask(blockedInstanceDao).execute(BlockedInstanceData); + } + + private static class insertAsyncTask extends AsyncTask { + + private BlockedInstanceDao mAsyncTaskDao; + + insertAsyncTask(BlockedInstanceDao dao) { + mAsyncTaskDao = dao; + } + + @Override + protected Void doInBackground(final BlockedInstanceData... params) { + mAsyncTaskDao.insert(params[0]); + return null; + } + } +} diff --git a/app/src/main/java/eu/toldi/infinityforlemmy/blockedinstances/BlockedInstanceViewModel.java b/app/src/main/java/eu/toldi/infinityforlemmy/blockedinstances/BlockedInstanceViewModel.java new file mode 100644 index 00000000..91dbd94a --- /dev/null +++ b/app/src/main/java/eu/toldi/infinityforlemmy/blockedinstances/BlockedInstanceViewModel.java @@ -0,0 +1,61 @@ +package eu.toldi.infinityforlemmy.blockedinstances; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Transformations; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; + +import java.util.List; + +import eu.toldi.infinityforlemmy.RedditDataRoomDatabase; + +public class BlockedInstanceViewModel extends AndroidViewModel { + private BlockedInstanceRepository blockedInstanceRepository; + private LiveData> mAllSubscribedInstances; + private MutableLiveData searchQueryLiveData; + + public BlockedInstanceViewModel(Application application, RedditDataRoomDatabase redditDataRoomDatabase, String accountName) { + super(application); + blockedInstanceRepository = new BlockedInstanceRepository(redditDataRoomDatabase, accountName); + searchQueryLiveData = new MutableLiveData<>(); + searchQueryLiveData.postValue(""); + + mAllSubscribedInstances = Transformations.switchMap(searchQueryLiveData, searchQuery -> blockedInstanceRepository.getAllFavoriteSubscribedInstancesWithSearchQuery(searchQuery)); + } + + public LiveData> getAllSubscribedInstances() { + return mAllSubscribedInstances; + } + + + public void insert(BlockedInstanceData BlockedInstanceData) { + blockedInstanceRepository.insert(BlockedInstanceData); + } + + public void setSearchQuery(String searchQuery) { + searchQueryLiveData.postValue(searchQuery); + } + + public static class Factory extends ViewModelProvider.NewInstanceFactory { + private Application mApplication; + private RedditDataRoomDatabase mRedditDataRoomDatabase; + private String mAccountName; + + public Factory(Application application, RedditDataRoomDatabase redditDataRoomDatabase, String accountName) { + mApplication = application; + mRedditDataRoomDatabase = redditDataRoomDatabase; + mAccountName = accountName; + } + + @NonNull + @Override + public T create(@NonNull Class modelClass) { + return (T) new BlockedInstanceViewModel(mApplication, mRedditDataRoomDatabase, mAccountName); + } + } +} diff --git a/app/src/main/java/eu/toldi/infinityforlemmy/fragments/BlockedInstancesListingFragment.java b/app/src/main/java/eu/toldi/infinityforlemmy/fragments/BlockedInstancesListingFragment.java new file mode 100644 index 00000000..84e86a99 --- /dev/null +++ b/app/src/main/java/eu/toldi/infinityforlemmy/fragments/BlockedInstancesListingFragment.java @@ -0,0 +1,173 @@ +package eu.toldi.infinityforlemmy.fragments; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.os.Build; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; + +import java.util.concurrent.Executor; + +import javax.inject.Inject; +import javax.inject.Named; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.toldi.infinityforlemmy.FragmentCommunicator; +import eu.toldi.infinityforlemmy.Infinity; +import eu.toldi.infinityforlemmy.R; +import eu.toldi.infinityforlemmy.RedditDataRoomDatabase; +import eu.toldi.infinityforlemmy.activities.BaseActivity; +import eu.toldi.infinityforlemmy.activities.BlockedThingListingActivity; +import eu.toldi.infinityforlemmy.adapters.BlockedInstancesRecyclerViewAdapter; +import eu.toldi.infinityforlemmy.blockedinstances.BlockedInstanceViewModel; +import eu.toldi.infinityforlemmy.customtheme.CustomThemeWrapper; +import eu.toldi.infinityforlemmy.customviews.LinearLayoutManagerBugFixed; +import eu.toldi.infinityforlemmy.utils.SharedPreferencesUtils; +import me.zhanghai.android.fastscroll.FastScrollerBuilder; +import retrofit2.Retrofit; + +public class BlockedInstancesListingFragment extends Fragment implements FragmentCommunicator { + public static final String EXTRA_ACCOUNT_NAME = "EAN"; + public static final String EXTRA_ACCESS_TOKEN = "EAT"; + + @BindView(R.id.swipe_refresh_layout_followed_users_listing_fragment) + SwipeRefreshLayout mSwipeRefreshLayout; + @BindView(R.id.recycler_view_followed_users_listing_fragment) + RecyclerView mRecyclerView; + @BindView(R.id.no_subscriptions_linear_layout_followed_users_listing_fragment) + LinearLayout mLinearLayout; + @BindView(R.id.no_subscriptions_image_view_followed_users_listing_fragment) + ImageView mImageView; + @BindView(R.id.error_text_view_followed_users_listing_fragment) + TextView mErrorTextView; + @Inject + @Named("oauth") + Retrofit mOauthRetrofit; + @Inject + @Named("default") + SharedPreferences mSharedPreferences; + @Inject + RedditDataRoomDatabase mRedditDataRoomDatabase; + @Inject + CustomThemeWrapper mCustomThemeWrapper; + @Inject + Executor mExecutor; + BlockedInstanceViewModel blockedInstanceViewModel; + private BaseActivity mActivity; + private RequestManager mGlide; + private LinearLayoutManagerBugFixed mLinearLayoutManager; + + public BlockedInstancesListingFragment() { + // Required empty public constructor + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_followed_users_listing, container, false); + + ButterKnife.bind(this, rootView); + + ((Infinity) mActivity.getApplication()).getAppComponent().inject(this); + + applyTheme(); + + Resources resources = getResources(); + + if ((mActivity instanceof BaseActivity && ((BaseActivity) mActivity).isImmersiveInterface())) { + mRecyclerView.setPadding(0, 0, 0, ((BaseActivity) mActivity).getNavBarHeight()); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + && mSharedPreferences.getBoolean(SharedPreferencesUtils.IMMERSIVE_INTERFACE_KEY, true)) { + int navBarResourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); + if (navBarResourceId > 0) { + mRecyclerView.setPadding(0, 0, 0, resources.getDimensionPixelSize(navBarResourceId)); + } + } + + mGlide = Glide.with(this); + + String accessToken = getArguments().getString(EXTRA_ACCESS_TOKEN); + if (accessToken == null) { + mSwipeRefreshLayout.setEnabled(false); + } + mLinearLayoutManager = new LinearLayoutManagerBugFixed(mActivity); + mRecyclerView.setLayoutManager(mLinearLayoutManager); + BlockedInstancesRecyclerViewAdapter adapter = new BlockedInstancesRecyclerViewAdapter(mActivity, + mExecutor, mOauthRetrofit, mRedditDataRoomDatabase, mCustomThemeWrapper, accessToken); + mRecyclerView.setAdapter(adapter); + new FastScrollerBuilder(mRecyclerView).useMd2Style().build(); + + blockedInstanceViewModel = new ViewModelProvider(this, + new BlockedInstanceViewModel.Factory(mActivity.getApplication(), mRedditDataRoomDatabase, getArguments().getString(EXTRA_ACCOUNT_NAME))) + .get(BlockedInstanceViewModel.class); + + blockedInstanceViewModel.getAllSubscribedInstances().observe(getViewLifecycleOwner(), blockedInstanceData -> { + mSwipeRefreshLayout.setRefreshing(false); + if (blockedInstanceData == null || blockedInstanceData.size() == 0) { + mRecyclerView.setVisibility(View.GONE); + mLinearLayout.setVisibility(View.VISIBLE); + mGlide.load(R.drawable.error_image).into(mImageView); + mErrorTextView.setText(R.string.no_blocked_instances); + } else { + mLinearLayout.setVisibility(View.GONE); + mRecyclerView.setVisibility(View.VISIBLE); + mGlide.clear(mImageView); + } + adapter.blockedInstances(blockedInstanceData); + }); + + return rootView; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mActivity = (BaseActivity) context; + } + + @Override + public void stopRefreshProgressbar() { + mSwipeRefreshLayout.setRefreshing(false); + } + + @Override + public void applyTheme() { + if (mActivity instanceof BlockedThingListingActivity) { + mSwipeRefreshLayout.setOnRefreshListener(() -> ((BlockedThingListingActivity) mActivity).loadBlocks(true)); + mSwipeRefreshLayout.setProgressBackgroundColorSchemeColor(mCustomThemeWrapper.getCircularProgressBarBackground()); + mSwipeRefreshLayout.setColorSchemeColors(mCustomThemeWrapper.getColorAccent()); + } else { + mSwipeRefreshLayout.setEnabled(false); + } + mErrorTextView.setTextColor(mCustomThemeWrapper.getSecondaryTextColor()); + if (mActivity.typeface != null) { + mErrorTextView.setTypeface(mActivity.typeface); + } + } + + public void goBackToTop() { + if (mLinearLayoutManager != null) { + mLinearLayoutManager.scrollToPositionWithOffset(0, 0); + } + } + + public void changeSearchQuery(String searchQuery) { + blockedInstanceViewModel.setSearchQuery(searchQuery); + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 98526244..48f48266 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1323,4 +1323,6 @@ "Your token has been expired. As Eternity does not store your password, you need to manually log back in! " Most Comments New Comments + Instances + No blocked instances \ No newline at end of file