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) diff --git a/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt b/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt index d27a74fb8..782659a21 100644 --- a/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt +++ b/src/main/java/org/quantumbadger/redreader/fragments/UserProfileDialog.kt @@ -19,14 +19,15 @@ 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 import android.widget.ImageView 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,7 +56,11 @@ import org.quantumbadger.redreader.reddit.APIResponseHandler.ActionResponseHandl 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 +99,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) @@ -132,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 @@ -152,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) { @@ -258,6 +287,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 +317,66 @@ object UserProfileDialog { ) } + private fun subscribeToUser(activity: AppCompatActivity, username: String) { + try { + val usernameToSubreddit = "u_$username" + 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 { + val userSubredditCanonicalIdA = SubredditCanonicalId("/user/$username") + val userSubredditCanonicalIdB = SubredditCanonicalId("u_$username") + + val subMan = getSubMan(activity) + + 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) + } + } + + 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 @@ -416,8 +511,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) + } } } } 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! +