From ccb1a79a97fba0cee9937ede7f3de89f1c9dfbf2 Mon Sep 17 00:00:00 2001 From: folkemat <96705437+folkemat@users.noreply.github.com> Date: Tue, 9 Jan 2024 15:45:25 +0100 Subject: [PATCH 1/5] Ability to follow and unfollow a user --- .../redreader/fragments/UserProfileDialog.kt | 92 ++++++++++++++++++- .../fragments/UserPropertiesDialog.java | 16 ++++ .../redreader/reddit/things/RedditUser.java | 3 + src/main/res/layout/user_profile_dialog.xml | 30 ++++++ src/main/res/values/strings.xml | 8 ++ 5 files changed, 148 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt b/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt index d27a74fb8..4b1cba942 100644 --- a/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt +++ b/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt @@ -25,6 +25,7 @@ import android.view.View import android.widget.FrameLayout import android.widget.ImageView import android.widget.ScrollView +import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.AppCompatImageView import com.google.android.material.card.MaterialCardView @@ -54,8 +55,11 @@ import org.quantumbadger.redreader.common.time.TimestampUTC.Companion.now import org.quantumbadger.redreader.reddit.APIResponseHandler.ActionResponseHandler import org.quantumbadger.redreader.reddit.APIResponseHandler.UserResponseHandler import org.quantumbadger.redreader.reddit.RedditAPI -import org.quantumbadger.redreader.reddit.api.RedditOAuth.completeLogin +import org.quantumbadger.redreader.reddit.api.RedditSubredditSubscriptionManager +import org.quantumbadger.redreader.reddit.api.SubredditSubscriptionState +import org.quantumbadger.redreader.reddit.things.InvalidSubredditNameException import org.quantumbadger.redreader.reddit.things.RedditUser +import org.quantumbadger.redreader.reddit.things.SubredditCanonicalId import org.quantumbadger.redreader.reddit.url.UserPostListingURL import org.quantumbadger.redreader.views.LoadingSpinnerView import org.quantumbadger.redreader.views.liststatus.ErrorView @@ -94,6 +98,9 @@ object UserProfileDialog { val chipMoreInfo = dialog.findViewById(R.id.user_profile_chip_more_info)!! val chipBlock = dialog.findViewById(R.id.user_profile_chip_block)!! val chipUnblock = dialog.findViewById(R.id.user_profile_chip_unblock)!! + val chipFollow = dialog.findViewById(R.id.user_profile_chip_follow)!! + val chipFollowed = dialog.findViewById(R.id.user_profile_chip_followed)!! + val chipUnfollow = dialog.findViewById(R.id.user_profile_chip_unfollow)!! val cm = CacheManager.getInstance(activity) val accountManager = RedditAccountManager.getInstance(activity) @@ -170,6 +177,19 @@ object UserProfileDialog { chipGold.visibility = View.GONE } + val usernameToSubreddit = "u_"+username + val userSubredditCanonicalId = SubredditCanonicalId(usernameToSubreddit) + if ((getSubMan(activity).getSubscriptionState(userSubredditCanonicalId) + == SubredditSubscriptionState.NOT_SUBSCRIBED) + ) { + chipFollowed.visibility = View.GONE + chipFollow.visibility = View.VISIBLE + chipUnfollow.visibility = View.GONE + }else{ + chipFollow.visibility = View.GONE + chipUnfollow.visibility = View.VISIBLE + } + if (PrefsUtility.appearance_user_show_avatars()) { val iconUrl = user.iconUrl if (iconUrl?.value?.isNotEmpty() == true) { @@ -258,6 +278,12 @@ object UserProfileDialog { chipUnblock.isEnabled = false // grey out unblockUser(activity, username, chipBlock, chipBlocked, chipUnblock) } + chipFollow.setOnClickListener { + subscribeToUser(activity, username) + } + chipUnfollow.setOnClickListener { + unsubscribeToUser(activity, username) + } } } @@ -282,6 +308,70 @@ object UserProfileDialog { ) } + private fun subscribeToUser(activity: AppCompatActivity, username: String) { + try { + //Every user has a user-subreddit that you can follow + val usernameToSubreddit = "u_"+username //subreddit of spez is u_spez + val userSubredditCanonicalId = SubredditCanonicalId(usernameToSubreddit) + + val subMan = getSubMan(activity) + if ((subMan.getSubscriptionState(userSubredditCanonicalId) + == SubredditSubscriptionState.NOT_SUBSCRIBED) + ) { + subMan.subscribe(userSubredditCanonicalId, activity) + Toast.makeText( + activity, + R.string.userprofile_toast_follow_loading, + Toast.LENGTH_SHORT + ).show() + } else { + Toast.makeText( + activity, + R.string.userprofile_toast_followed, + Toast.LENGTH_SHORT + ).show() + } + } catch (e: InvalidSubredditNameException) { + throw RuntimeException(e) + } + } + + private fun unsubscribeToUser(activity: AppCompatActivity, username: String) { + try { + //Every user has a user-subreddit that you can follow + val usernameToSubreddit = "u_"+username //subreddit of spez is u_spez + val userSubredditCanonicalId = SubredditCanonicalId(usernameToSubreddit) + + val subMan = getSubMan(activity) + if ((subMan.getSubscriptionState(userSubredditCanonicalId) + == SubredditSubscriptionState.SUBSCRIBED) + ) { + subMan.unsubscribe(userSubredditCanonicalId, activity) + Toast.makeText( + activity, + R.string.userprofile_toast_unfollow_loading, + Toast.LENGTH_SHORT + ).show() + } else { + Toast.makeText( + activity, + R.string.userprofile_toast_not_following, + Toast.LENGTH_SHORT + ).show() + } + } catch (e: InvalidSubredditNameException) { + throw RuntimeException(e) + } + } + + private fun getSubMan(activity: AppCompatActivity): RedditSubredditSubscriptionManager { + val subMan = RedditSubredditSubscriptionManager.getSingleton( + activity, + RedditAccountManager.getInstance(activity).defaultAccount + ) + return subMan + } + private fun unblockUser(activity: AppCompatActivity, username: String, chipBlock: Chip, chipBlocked: Chip, chipUnblock: Chip) { val cm = CacheManager.getInstance(activity) val currentUser = RedditAccountManager.getInstance(activity).defaultAccount diff --git a/src/main/java/org/quantumbadger/redreader/fragments/UserPropertiesDialog.java b/src/main/java/org/quantumbadger/redreader/fragments/UserPropertiesDialog.java index 5bb6daa87..9065f7be5 100644 --- a/src/main/java/org/quantumbadger/redreader/fragments/UserPropertiesDialog.java +++ b/src/main/java/org/quantumbadger/redreader/fragments/UserPropertiesDialog.java @@ -107,6 +107,14 @@ protected void prepare( false)); } + if (user.is_followed != null) { + items.addView(propView( + context, + R.string.userprofile_tag_followed, + user.is_followed ? R.string.general_true : R.string.general_false, + false)); + } + if (user.is_employee != null) { items.addView(propView( context, @@ -131,6 +139,14 @@ protected void prepare( false)); } + if (user.is_followed != null) { + items.addView(propView( + context, + R.string.userprofile_tag_followed, + user.is_followed ? R.string.general_true : R.string.general_false, + false)); + } + if (user.icon_img != null) { items.addView(propView( context, diff --git a/src/main/java/org/quantumbadger/redreader/reddit/things/RedditUser.java b/src/main/java/org/quantumbadger/redreader/reddit/things/RedditUser.java index c1c202211..885ab7b24 100644 --- a/src/main/java/org/quantumbadger/redreader/reddit/things/RedditUser.java +++ b/src/main/java/org/quantumbadger/redreader/reddit/things/RedditUser.java @@ -44,6 +44,7 @@ public class RedditUser implements Parcelable, JsonObject.JsonDeserializable { @Nullable public Boolean is_suspended; @Nullable public Boolean over_18; @Nullable public Boolean is_blocked; + @Nullable public Boolean is_followed; @Nullable public String id; @NonNull public String name; @@ -87,6 +88,7 @@ private RedditUser(final Parcel in) { is_mod = in.readInt() == 1; over_18 = in.readInt() == 1; is_blocked = in.readInt() == 1; + is_followed = in.readInt() == 1; id = in.readString(); name = in.readString(); @@ -121,6 +123,7 @@ public void writeToParcel(final Parcel parcel, final int flags) { parcel.writeInt(is_mod ? 1 : 0); parcel.writeInt(over_18 ? 1 : 0); parcel.writeInt(is_blocked ? 1 : 0); + parcel.writeInt(is_followed ? 1 : 0); parcel.writeString(id); parcel.writeString(name); diff --git a/src/main/res/layout/user_profile_dialog.xml b/src/main/res/layout/user_profile_dialog.xml index 5c4bb028c..3ee81cc4f 100644 --- a/src/main/res/layout/user_profile_dialog.xml +++ b/src/main/res/layout/user_profile_dialog.xml @@ -158,6 +158,16 @@ android:checkable="false" app:chipMinTouchTargetSize="0dp"/> + + + + + + diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index ad25bbbf4..2cbec8366 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -1914,4 +1914,12 @@ pref_behaviour_mark_posts_as_read Mark posts as read + Follow + Following… + Followed + You are already following this user! + Unfollow + Unfollowing… + You are not following this user! + From 198a84d17a320a93955918ff77380a61ae7f8a85 Mon Sep 17 00:00:00 2001 From: folkemat <96705437+folkemat@users.noreply.github.com> Date: Tue, 9 Jan 2024 19:28:53 +0100 Subject: [PATCH 2/5] Possibility for /user/username and u_username when checking and unfollowing --- .../redreader/fragments/UserProfileDialog.kt | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt b/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt index 4b1cba942..7993dd51a 100644 --- a/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt +++ b/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt @@ -177,15 +177,15 @@ object UserProfileDialog { chipGold.visibility = View.GONE } - val usernameToSubreddit = "u_"+username - val userSubredditCanonicalId = SubredditCanonicalId(usernameToSubreddit) - if ((getSubMan(activity).getSubscriptionState(userSubredditCanonicalId) - == SubredditSubscriptionState.NOT_SUBSCRIBED) - ) { + val userSubredditCanonicalIdA = SubredditCanonicalId("/user/$username") + val userSubredditCanonicalIdB = SubredditCanonicalId("u_$username") + val subMan = getSubMan(activity) + if (subMan.getSubscriptionState(userSubredditCanonicalIdA) == SubredditSubscriptionState.NOT_SUBSCRIBED && + subMan.getSubscriptionState(userSubredditCanonicalIdB) == SubredditSubscriptionState.NOT_SUBSCRIBED) { chipFollowed.visibility = View.GONE chipFollow.visibility = View.VISIBLE chipUnfollow.visibility = View.GONE - }else{ + } else { chipFollow.visibility = View.GONE chipUnfollow.visibility = View.VISIBLE } @@ -310,8 +310,7 @@ object UserProfileDialog { private fun subscribeToUser(activity: AppCompatActivity, username: String) { try { - //Every user has a user-subreddit that you can follow - val usernameToSubreddit = "u_"+username //subreddit of spez is u_spez + val usernameToSubreddit = "u_$username" val userSubredditCanonicalId = SubredditCanonicalId(usernameToSubreddit) val subMan = getSubMan(activity) @@ -338,26 +337,23 @@ object UserProfileDialog { private fun unsubscribeToUser(activity: AppCompatActivity, username: String) { try { - //Every user has a user-subreddit that you can follow - val usernameToSubreddit = "u_"+username //subreddit of spez is u_spez - val userSubredditCanonicalId = SubredditCanonicalId(usernameToSubreddit) + val userSubredditCanonicalIdA = SubredditCanonicalId("/user/$username") + val userSubredditCanonicalIdB = SubredditCanonicalId("u_$username") val subMan = getSubMan(activity) - if ((subMan.getSubscriptionState(userSubredditCanonicalId) - == SubredditSubscriptionState.SUBSCRIBED) - ) { - subMan.unsubscribe(userSubredditCanonicalId, activity) - Toast.makeText( - activity, - R.string.userprofile_toast_unfollow_loading, - Toast.LENGTH_SHORT - ).show() - } else { - Toast.makeText( - activity, - R.string.userprofile_toast_not_following, - Toast.LENGTH_SHORT - ).show() + + fun unsubscribeIfSubscribed(canonicalId: SubredditCanonicalId): Boolean { + return if (subMan.getSubscriptionState(canonicalId) == SubredditSubscriptionState.SUBSCRIBED) { + subMan.unsubscribe(canonicalId, activity) + Toast.makeText(activity, R.string.userprofile_toast_unfollow_loading, Toast.LENGTH_SHORT).show() + true + } else { + false + } + } + + if (!unsubscribeIfSubscribed(userSubredditCanonicalIdA) && !unsubscribeIfSubscribed(userSubredditCanonicalIdB)) { + Toast.makeText(activity, R.string.userprofile_toast_not_following, Toast.LENGTH_SHORT).show() } } catch (e: InvalidSubredditNameException) { throw RuntimeException(e) From 6df99d9218a5ba75b69e384f56a4dc1bc8ab37a1 Mon Sep 17 00:00:00 2001 From: QuantumBadger Date: Sun, 20 Apr 2025 15:45:31 +0100 Subject: [PATCH 3/5] Build fixes (#1166) --- .../redreader/fragments/UserProfileDialog.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt b/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt index 7993dd51a..c07467006 100644 --- a/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt +++ b/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt @@ -19,7 +19,6 @@ package org.quantumbadger.redreader.fragments import android.content.Intent import android.graphics.BitmapFactory -import android.net.Uri import android.util.Log import android.view.View import android.widget.FrameLayout @@ -28,6 +27,7 @@ import android.widget.ScrollView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.AppCompatImageView +import androidx.core.net.toUri import com.google.android.material.card.MaterialCardView import com.google.android.material.chip.Chip import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -55,6 +55,7 @@ import org.quantumbadger.redreader.common.time.TimestampUTC.Companion.now import org.quantumbadger.redreader.reddit.APIResponseHandler.ActionResponseHandler import org.quantumbadger.redreader.reddit.APIResponseHandler.UserResponseHandler import org.quantumbadger.redreader.reddit.RedditAPI +import org.quantumbadger.redreader.reddit.api.RedditOAuth.completeLogin import org.quantumbadger.redreader.reddit.api.RedditSubredditSubscriptionManager import org.quantumbadger.redreader.reddit.api.SubredditSubscriptionState import org.quantumbadger.redreader.reddit.things.InvalidSubredditNameException @@ -502,8 +503,10 @@ object UserProfileDialog { ) { resultCode: Int, data: Intent? -> if (data != null) { if (resultCode == 123 && data.hasExtra("url")) { - val uri = Uri.parse(data.getStringExtra("url")) - completeLogin(activity, uri, RunnableOnce.DO_NOTHING) + val uri = data.getStringExtra("url")?.toUri() + if (uri != null) { + completeLogin(activity, uri, RunnableOnce.DO_NOTHING) + } } } } From 800c93a9d871bf4898b63bcaecb16bb21d9970ea Mon Sep 17 00:00:00 2001 From: QuantumBadger Date: Sun, 20 Apr 2025 15:46:07 +0100 Subject: [PATCH 4/5] Updating changelog (#1166) --- src/main/assets/changelog-alpha.txt | 1 + src/main/assets/changelog.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/assets/changelog-alpha.txt b/src/main/assets/changelog-alpha.txt index 60a9acd34..6587da4b2 100644 --- a/src/main/assets/changelog-alpha.txt +++ b/src/main/assets/changelog-alpha.txt @@ -1,5 +1,6 @@ /Alpha 359 (2025-04-20) Added support for emotes in comment flairs (thanks to bharatknv) +Ability to follow/unfollow users (thanks to folkemat) Added "Mark as Read/Unread" fling action, and optional context menu item (thanks to JoshAusHessen and codeofdusk) /Alpha 358 (2025-03-12) diff --git a/src/main/assets/changelog.txt b/src/main/assets/changelog.txt index 5860cb11e..b3547412e 100644 --- a/src/main/assets/changelog.txt +++ b/src/main/assets/changelog.txt @@ -1,5 +1,6 @@ 114/1.25 Added video playback speed control (thanks to folkemat) +Ability to follow/unfollow users (thanks to folkemat) Added support for emotes in comment flairs (thanks to bharatknv) Added "Mark as Read/Unread" fling action, and optional context menu item (thanks to JoshAusHessen and codeofdusk) Added preference to prevent posts being marked as read when clicked (thanks to Daniel Ho) From 5be9ab7376fa8cb4427dbdcbcf3eb86060a58d1f Mon Sep 17 00:00:00 2001 From: QuantumBadger Date: Sun, 20 Apr 2025 16:05:15 +0100 Subject: [PATCH 5/5] Don't allow following if not logged in (#1166) --- .../redreader/fragments/UserProfileDialog.kt | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt b/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt index c07467006..782659a21 100644 --- a/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt +++ b/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt @@ -140,18 +140,26 @@ object UserProfileDialog { accountManager.getDefaultAccount().canonicalUsername )) { chipYou.visibility = View.GONE - }else{ - chipBlock.visibility = View.GONE //you should not block yourself + } else{ + // You should not block yourself + chipBlock.visibility = View.GONE + chipFollow.visibility = View.GONE } if (user.is_suspended != true) { chipSuspended.visibility = View.GONE } - if (accountManager.getDefaultAccount().isAnonymous()) { - chipBlock.visibility = View.GONE - chipUnblock.visibility = View.GONE - chipBlocked.visibility = View.GONE + if (accountManager.getDefaultAccount().isAnonymous) { + listOf( + chipBlock, + chipUnblock, + chipBlocked, + chipFollow, + chipUnfollow, + chipFollowed + ).forEach { it.visibility = View.GONE } + } else { //show block actions only if user is logged in if (user.is_blocked != true) { chipBlocked.visibility = View.GONE @@ -160,6 +168,19 @@ object UserProfileDialog { chipBlock.visibility = View.GONE chipUnblock.visibility = View.VISIBLE } + + val userSubredditCanonicalIdA = SubredditCanonicalId("/user/$username") + val userSubredditCanonicalIdB = SubredditCanonicalId("u_$username") + val subMan = getSubMan(activity) + if (subMan.getSubscriptionState(userSubredditCanonicalIdA) == SubredditSubscriptionState.NOT_SUBSCRIBED && + subMan.getSubscriptionState(userSubredditCanonicalIdB) == SubredditSubscriptionState.NOT_SUBSCRIBED) { + chipFollowed.visibility = View.GONE + chipFollow.visibility = View.VISIBLE + chipUnfollow.visibility = View.GONE + } else { + chipFollow.visibility = View.GONE + chipUnfollow.visibility = View.VISIBLE + } } if (user.is_friend != true) { @@ -178,19 +199,6 @@ object UserProfileDialog { chipGold.visibility = View.GONE } - val userSubredditCanonicalIdA = SubredditCanonicalId("/user/$username") - val userSubredditCanonicalIdB = SubredditCanonicalId("u_$username") - val subMan = getSubMan(activity) - if (subMan.getSubscriptionState(userSubredditCanonicalIdA) == SubredditSubscriptionState.NOT_SUBSCRIBED && - subMan.getSubscriptionState(userSubredditCanonicalIdB) == SubredditSubscriptionState.NOT_SUBSCRIBED) { - chipFollowed.visibility = View.GONE - chipFollow.visibility = View.VISIBLE - chipUnfollow.visibility = View.GONE - } else { - chipFollow.visibility = View.GONE - chipUnfollow.visibility = View.VISIBLE - } - if (PrefsUtility.appearance_user_show_avatars()) { val iconUrl = user.iconUrl if (iconUrl?.value?.isNotEmpty() == true) {