Skip to content

Commit

Permalink
Merge pull request #20172 from wordpress-mobile/issue/20040-notificat…
Browse files Browse the repository at this point in the history
…ions-share-an-own-published-post--preliminary-refactor

Notification inline actions - preliminary refactor
  • Loading branch information
Antonis Lilis authored Feb 12, 2024
2 parents ed967fd + 1146fe9 commit 4786b5e
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ private static boolean putNote(Note note, boolean checkBeforeInsert) {
String rawNote = prepareNote(note.getId(), note.getJSON().toString());

ContentValues values = new ContentValues();
values.put("type", note.getType());
values.put("type", note.getRawType());
values.put("timestamp", note.getTimestamp());
values.put("raw_note_data", rawNote);

Expand Down
40 changes: 28 additions & 12 deletions WordPress/src/main/java/org/wordpress/android/models/Note.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,62 +95,76 @@ public String getId() {
return mKey;
}

public String getType() {
@NonNull
public String getRawType() {
return queryJSON("type", NOTE_UNKNOWN_TYPE);
}

private Boolean isType(String type) {
return getType().equals(type);
@NonNull
private Boolean isTypeRaw(@NonNull String rawType) {
return getRawType().equals(rawType);
}

@NonNull
public Boolean isCommentType() {
synchronized (mSyncLock) {
return (isAutomattcherType() && JSONUtils.queryJSON(mNoteJSON, "meta.ids.comment", -1) != -1)
|| isType(NOTE_COMMENT_TYPE);
|| isTypeRaw(NOTE_COMMENT_TYPE);
}
}

@NonNull
public Boolean isAutomattcherType() {
return isType(NOTE_MATCHER_TYPE);
return isTypeRaw(NOTE_MATCHER_TYPE);
}

@NonNull
public Boolean isNewPostType() {
return isType(NOTE_NEW_POST_TYPE);
return isTypeRaw(NOTE_NEW_POST_TYPE);
}

@NonNull
public Boolean isFollowType() {
return isType(NOTE_FOLLOW_TYPE);
return isTypeRaw(NOTE_FOLLOW_TYPE);
}

@NonNull
public Boolean isLikeType() {
return isPostLikeType() || isCommentLikeType();
}

@NonNull
public Boolean isPostLikeType() {
return isType(NOTE_LIKE_TYPE);
return isTypeRaw(NOTE_LIKE_TYPE);
}

@NonNull
public Boolean isCommentLikeType() {
return isType(NOTE_COMMENT_LIKE_TYPE);
return isTypeRaw(NOTE_COMMENT_LIKE_TYPE);
}

@NonNull
public Boolean isReblogType() {
return isType(NOTE_REBLOG_TYPE);
return isTypeRaw(NOTE_REBLOG_TYPE);
}

@NonNull
public Boolean isViewMilestoneType() {
return isType(NOTE_VIEW_MILESTONE);
return isTypeRaw(NOTE_VIEW_MILESTONE);
}

@NonNull
public Boolean isCommentReplyType() {
return isCommentType() && getParentCommentId() > 0;
}

// Returns true if the user has replied to this comment note
@NonNull
public Boolean isCommentWithUserReply() {
return isCommentType() && !TextUtils.isEmpty(getCommentSubjectNoticon());
}

@NonNull
public Boolean isUserList() {
return isLikeType() || isFollowType() || isReblogType();
}
Expand Down Expand Up @@ -278,6 +292,7 @@ public long getCommentReplyId() {
/**
* Compare note timestamp to now and return a time grouping
*/
@NonNull
public static NoteTimeGroup getTimeGroupForTimestamp(long timestamp) {
Date today = new Date();
Date then = new Date(timestamp * 1000);
Expand Down Expand Up @@ -422,7 +437,8 @@ public long getParentCommentId() {
/**
* Rudimentary system for pulling an item out of a JSON object hierarchy
*/
private <U> U queryJSON(String query, U defaultObject) {
@NonNull
private <U> U queryJSON(@Nullable String query, @NonNull U defaultObject) {
synchronized (mSyncLock) {
if (mNoteJSON == null) {
return defaultObject;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.wordpress.android.models

val Note.type
get() = NoteType.from(rawType)

sealed class Notification {
data class Like(val url: String, val title: String): Notification()
data object Unknown: Notification()

companion object {
fun from(rawNote: Note) = when(rawNote.type) {
NoteType.Like -> Like(url= rawNote.url, title = rawNote.title)
else -> Unknown
}
}
}
enum class NoteType(val rawType: String) {
Follow(Note.NOTE_FOLLOW_TYPE),
Like(Note.NOTE_LIKE_TYPE),
Comment(Note.NOTE_COMMENT_TYPE),
Matcher(Note.NOTE_MATCHER_TYPE),
CommentLike(Note.NOTE_COMMENT_LIKE_TYPE),
Reblog(Note.NOTE_REBLOG_TYPE),
NewPost(Note.NOTE_NEW_POST_TYPE),
ViewMilestone(Note.NOTE_VIEW_MILESTONE),
Unknown(Note.NOTE_UNKNOWN_TYPE);

companion object {
private val map = entries.associateBy(NoteType::rawType)
fun from(rawType: String) = map[rawType] ?: Unknown
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class ListScenarioUtils @Inject constructor(
val notificationsUtilsWrapper: NotificationsUtilsWrapper
) {
fun mapLikeNoteToListScenario(note: Note, context: Context): ListScenario {
require(note.isLikeType) { "mapLikeNoteToListScenario > unexpected note type ${note.type}" }
require(note.isLikeType) { "mapLikeNoteToListScenario > unexpected note type ${note.rawType}" }

val imageType = AVATAR_WITH_BACKGROUND
val headerNoteBlock = HeaderNoteBlock(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ private void updateUIAndNote(boolean doRefresh) {

// analytics tracking
Map<String, String> properties = new HashMap<>();
properties.put("notification_type", note.getType());
properties.put("notification_type", note.getRawType());
AnalyticsTracker.track(AnalyticsTracker.Stat.NOTIFICATIONS_OPENED_NOTIFICATION_DETAILS, properties);

setProgressVisible(false);
Expand Down Expand Up @@ -343,7 +343,7 @@ private void setActionBarTitleForNote(Note note) {
String title = note.getTitle();
if (TextUtils.isEmpty(title)) {
// set a default title if title is not set within the note
switch (note.getType()) {
switch (note.getRawType()) {
case NOTE_FOLLOW_TYPE:
title = getString(R.string.follows);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ class NotificationsDetailListFragment : ListFragment(), NotificationFragment {
).also {
if (noteObject.ranges != null && noteObject.ranges!!.isNotEmpty()) {
val range = noteObject.ranges!![noteObject.ranges!!.size - 1]
it.setClickableSpan(range, note.type)
it.setClickableSpan(range, note.rawType)
}
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import android.view.ViewGroup.MarginLayoutParams
import android.view.ViewTreeObserver
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.StringRes
import androidx.core.text.BidiFormatter
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
Expand All @@ -24,6 +25,8 @@ import org.wordpress.android.datasets.NotificationsTable
import org.wordpress.android.models.Note
import org.wordpress.android.models.Note.NoteTimeGroup
import org.wordpress.android.models.Note.TimeStampComparator
import org.wordpress.android.models.NoteType
import org.wordpress.android.models.type
import org.wordpress.android.ui.comments.CommentUtils
import org.wordpress.android.ui.notifications.NotificationsListFragmentPage.OnNoteClickListener
import org.wordpress.android.ui.notifications.adapters.NotesAdapter.NoteViewHolder
Expand All @@ -46,13 +49,11 @@ class NotesAdapter(
private val notes = ArrayList<Note>()
val filteredNotes = ArrayList<Note>()

@JvmField
@Inject
var imageManager: ImageManager? = null
lateinit var imageManager: ImageManager

@JvmField
@Inject
var notificationsUtilsWrapper: NotificationsUtilsWrapper? = null
lateinit var notificationsUtilsWrapper: NotificationsUtilsWrapper

enum class FILTERS {
FILTER_ALL,
Expand Down Expand Up @@ -128,35 +129,42 @@ class NotesAdapter(
return filteredNotes.size
}

private val Note.timeGroup
get() = Note.getTimeGroupForTimestamp(timestamp)

@StringRes
private fun timeGroupHeaderText(note: Note, previousNote: Note?) =
previousNote?.timeGroup.let { previousTimeGroup ->
val timeGroup = note.timeGroup
if (previousTimeGroup?.let { it == timeGroup } == true) {
// If the previous time group exists and is the same, we don't need a new one
null
} else {
// Otherwise, we create a new one
when (timeGroup) {
NoteTimeGroup.GROUP_TODAY -> R.string.stats_timeframe_today
NoteTimeGroup.GROUP_YESTERDAY -> R.string.stats_timeframe_yesterday
NoteTimeGroup.GROUP_OLDER_TWO_DAYS -> R.string.older_two_days
NoteTimeGroup.GROUP_OLDER_WEEK -> R.string.older_last_week
NoteTimeGroup.GROUP_OLDER_MONTH -> R.string.older_month
}
}
}

@Suppress("CyclomaticComplexMethod", "LongMethod")
override fun onBindViewHolder(noteViewHolder: NoteViewHolder, position: Int) {
val note = getNoteAtPosition(position) ?: return
val previousNote = getNoteAtPosition(position - 1)
noteViewHolder.contentView.tag = note.id

// Display group header
val timeGroup = Note.getTimeGroupForTimestamp(note.timestamp)
var previousTimeGroup: NoteTimeGroup? = null
if (position > 0) {
val previousNote = getNoteAtPosition(position - 1)
previousTimeGroup = Note.getTimeGroupForTimestamp(
previousNote!!.timestamp
)
}
if (previousTimeGroup?.let { it == timeGroup } == true) {
noteViewHolder.headerText.visibility = View.GONE
} else {
noteViewHolder.headerText.visibility = View.VISIBLE
timeGroup?.let {
noteViewHolder.headerText.setText(
when (it) {
NoteTimeGroup.GROUP_TODAY -> R.string.stats_timeframe_today
NoteTimeGroup.GROUP_YESTERDAY -> R.string.stats_timeframe_yesterday
NoteTimeGroup.GROUP_OLDER_TWO_DAYS -> R.string.older_two_days
NoteTimeGroup.GROUP_OLDER_WEEK -> R.string.older_last_week
NoteTimeGroup.GROUP_OLDER_MONTH -> R.string.older_month
}
)
// Display time group header
timeGroupHeaderText(note, previousNote)?.let { timeGroupText ->
with(noteViewHolder.headerText) {
visibility = View.VISIBLE
setText(timeGroupText)
}
} ?: run {
noteViewHolder.headerText.visibility = View.GONE
}

// Subject is stored in db as html to preserve text formatting
Expand Down Expand Up @@ -204,6 +212,7 @@ class NotesAdapter(
noteViewHolder.textDetail.visibility = View.GONE
}
noteViewHolder.loadAvatars(note)
noteViewHolder.bindInlineActionIconsForNote(note)
noteViewHolder.unreadNotificationView.isVisible = note.isUnread

// request to load more comments when we near the end
Expand Down Expand Up @@ -251,11 +260,37 @@ class NotesAdapter(

private fun loadAvatar(imageView: ImageView, avatarUrl: String) {
val url = GravatarUtils.fixGravatarUrl(avatarUrl, avatarSize)
imageManager?.loadIntoCircle(imageView, ImageType.AVATAR_WITH_BACKGROUND, url)
imageManager.loadIntoCircle(imageView, ImageType.AVATAR_WITH_BACKGROUND, url)
}

private fun Note.shouldShowMultipleAvatars() = isFollowType || isLikeType || isCommentLikeType

@Suppress("ForbiddenComment")
private fun NoteViewHolder.bindInlineActionIconsForNote(note: Note) {
when (note.type) {
NoteType.Comment -> {
actionIcon.setImageResource(R.drawable.star_empty)
actionIcon.isVisible = true
actionIcon.setOnClickListener {
// TODO: handle tap on comment's inline action icon (the star)
}
}
NoteType.NewPost,
NoteType.Reblog,
NoteType.Like -> {
// TODO: Use the icon from the Figma design
actionIcon.setImageResource(R.drawable.gb_ic_share)
actionIcon.isVisible = true
actionIcon.setOnClickListener {
// TODO: handle tap on comment's inline action icon (the share icon)
}
}
else -> {
actionIcon.isVisible = false
}
}
}

private fun handleMaxLines(subject: TextView, detail: TextView) {
subject.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
Expand Down Expand Up @@ -315,6 +350,7 @@ class NotesAdapter(
val threeAvatars2: ImageView
val threeAvatars3: ImageView
val unreadNotificationView: View
val actionIcon: ImageView

init {
contentView = checkNotNull(view.findViewById(R.id.note_content_container))
Expand All @@ -331,6 +367,7 @@ class NotesAdapter(
twoAvatarsView = checkNotNull(view.findViewById(R.id.two_avatars_view))
threeAvatarsView = checkNotNull(view.findViewById(R.id.three_avatars_view))
unreadNotificationView = checkNotNull(view.findViewById(R.id.notification_unread))
actionIcon = checkNotNull(view.findViewById(R.id.action))
contentView.setOnClickListener(onClickListener)
}
}
Expand Down

0 comments on commit 4786b5e

Please sign in to comment.