restore marking feed stories as seen

This commit is contained in:
Austin Huang 2021-07-08 16:51:46 -04:00
parent 585db4056a
commit c82080acf4
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
4 changed files with 116 additions and 98 deletions

View File

@ -17,10 +17,9 @@ import androidx.appcompat.view.ContextThemeWrapper
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.view.GestureDetectorCompat import androidx.core.view.GestureDetectorCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavController import androidx.navigation.NavController
@ -31,7 +30,6 @@ import awais.instagrabber.R
import awais.instagrabber.adapters.StoriesAdapter import awais.instagrabber.adapters.StoriesAdapter
import awais.instagrabber.customviews.helpers.SwipeGestureListener import awais.instagrabber.customviews.helpers.SwipeGestureListener
import awais.instagrabber.databinding.FragmentStoryViewerBinding import awais.instagrabber.databinding.FragmentStoryViewerBinding
import awais.instagrabber.fragments.main.ProfileFragment
import awais.instagrabber.fragments.settings.PreferenceKeys import awais.instagrabber.fragments.settings.PreferenceKeys
import awais.instagrabber.interfaces.SwipeEvent import awais.instagrabber.interfaces.SwipeEvent
import awais.instagrabber.models.Resource import awais.instagrabber.models.Resource
@ -41,9 +39,7 @@ import awais.instagrabber.models.enums.StoryPaginationType
import awais.instagrabber.repositories.requests.StoryViewerOptions import awais.instagrabber.repositories.requests.StoryViewerOptions
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
import awais.instagrabber.repositories.responses.stories.* import awais.instagrabber.repositories.responses.stories.*
import awais.instagrabber.utils.Constants
import awais.instagrabber.utils.DownloadUtils.download import awais.instagrabber.utils.DownloadUtils.download
import awais.instagrabber.utils.TextUtils.epochSecondToString
import awais.instagrabber.utils.ResponseBodyUtils import awais.instagrabber.utils.ResponseBodyUtils
import awais.instagrabber.utils.Utils import awais.instagrabber.utils.Utils
import awais.instagrabber.utils.extensions.TAG import awais.instagrabber.utils.extensions.TAG
@ -79,7 +75,6 @@ class StoryViewerFragment : Fragment() {
private var gestureDetector: GestureDetectorCompat? = null private var gestureDetector: GestureDetectorCompat? = null
private val storiesRepository: StoriesRepository? = null private val storiesRepository: StoriesRepository? = null
private val mediaRepository: MediaRepository? = null private val mediaRepository: MediaRepository? = null
private var live: Broadcast? = null
private var menuProfile: MenuItem? = null private var menuProfile: MenuItem? = null
private var profileVisible: Boolean = false private var profileVisible: Boolean = false
private var player: SimpleExoPlayer? = null private var player: SimpleExoPlayer? = null
@ -218,8 +213,8 @@ class StoryViewerFragment : Fragment() {
storiesViewModel.setMedia(position) storiesViewModel.setMedia(position)
} }
binding.storiesList.adapter = storiesAdapter binding.storiesList.adapter = storiesAdapter
storiesViewModel.getCurrentStory().observe(fragmentActivity, { storiesViewModel.getCurrentStory().observe(viewLifecycleOwner, {
if (it?.items != null) { if (it?.items != null && it.items.size > 1) {
val storyMedias = it.items.toMutableList() val storyMedias = it.items.toMutableList()
val newItem = storyMedias.get(0) val newItem = storyMedias.get(0)
newItem.isCurrentSlide = true newItem.isCurrentSlide = true
@ -231,23 +226,24 @@ class StoryViewerFragment : Fragment() {
else View.GONE else View.GONE
} }
else { else {
if (it?.items != null) storiesViewModel.setMedia(0)
binding.listToggle.isEnabled = false binding.listToggle.isEnabled = false
binding.storiesList.visibility = View.GONE binding.storiesList.visibility = View.GONE
} }
}) })
storiesViewModel.getDate().observe(fragmentActivity, { storiesViewModel.getDate().observe(viewLifecycleOwner, {
val actionBar = fragmentActivity.supportActionBar val actionBar = fragmentActivity.supportActionBar
if (actionBar != null && it != null) actionBar.subtitle = it if (actionBar != null && it != null) actionBar.subtitle = it
}) })
storiesViewModel.getTitle().observe(fragmentActivity, { storiesViewModel.getTitle().observe(viewLifecycleOwner, {
val actionBar = fragmentActivity.supportActionBar val actionBar = fragmentActivity.supportActionBar
if (actionBar != null && it != null) actionBar.title = it if (actionBar != null && it != null) actionBar.title = it
}) })
storiesViewModel.getCurrentMedia().observe(fragmentActivity, { refreshStory(it) }) storiesViewModel.getCurrentMedia().observe(viewLifecycleOwner, { refreshStory(it) })
storiesViewModel.getCurrentIndex().observe(fragmentActivity, { storiesViewModel.getCurrentIndex().observe(viewLifecycleOwner, {
storiesAdapter!!.paginate(it) storiesAdapter!!.paginate(it)
}) })
storiesViewModel.getOptions().observe(fragmentActivity, { storiesViewModel.getOptions().observe(viewLifecycleOwner, {
binding.stickers.isEnabled = it.first.size > 0 binding.stickers.isEnabled = it.first.size > 0
}) })
} }
@ -267,65 +263,28 @@ class StoryViewerFragment : Fragment() {
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private fun setupListeners() { private fun setupListeners() {
var liveModels: LiveData<List<Story>?>? = null
if (currentFeedStoryIndex >= 0) { if (currentFeedStoryIndex >= 0) {
val type = options!!.type val type = options!!.type
when (type) { when (type) {
StoryViewerOptions.Type.HIGHLIGHT -> { StoryViewerOptions.Type.HIGHLIGHT -> {
storiesViewModel.fetchHighlights(options!!.id) storiesViewModel.fetchHighlights(options!!.id)
liveModels = storiesViewModel.getHighlights() storiesViewModel.highlights.observe(viewLifecycleOwner) {
setupMultipage(it)
}
} }
StoryViewerOptions.Type.FEED_STORY_POSITION -> { StoryViewerOptions.Type.FEED_STORY_POSITION -> {
val feedStoriesViewModel = listViewModel as FeedStoriesViewModel? val feedStoriesViewModel = listViewModel as FeedStoriesViewModel?
liveModels = feedStoriesViewModel!!.list setupMultipage(feedStoriesViewModel!!.list.value)
} }
StoryViewerOptions.Type.STORY_ARCHIVE -> { StoryViewerOptions.Type.STORY_ARCHIVE -> {
val archivesViewModel = listViewModel as ArchivesViewModel? val archivesViewModel = listViewModel as ArchivesViewModel?
liveModels = archivesViewModel!!.list setupMultipage(archivesViewModel!!.list.value)
} }
StoryViewerOptions.Type.USER -> { StoryViewerOptions.Type.USER -> {
resetView() resetView()
} }
} }
} }
if (liveModels != null) liveModels.observe(viewLifecycleOwner, { models ->
storiesViewModel.getPagination().observe(fragmentActivity, {
if (models != null) {
when (it) {
StoryPaginationType.FORWARD -> {
if (currentFeedStoryIndex == models.size - 1)
Toast.makeText(
context,
R.string.no_more_stories,
Toast.LENGTH_SHORT
).show()
else paginateStories(false, currentFeedStoryIndex == models.size - 2)
}
StoryPaginationType.BACKWARD -> {
if (currentFeedStoryIndex == 0)
Toast.makeText(
context,
R.string.no_more_stories,
Toast.LENGTH_SHORT
).show()
else paginateStories(true, false)
}
StoryPaginationType.ERROR -> {
Toast.makeText(
context,
R.string.downloader_unknown_error,
Toast.LENGTH_SHORT
).show()
}
}
}
})
if (models != null && !models.isEmpty()) {
binding.btnBackward.isEnabled = currentFeedStoryIndex != 0
binding.btnForward.isEnabled = currentFeedStoryIndex != models.size - 1
resetView()
}
})
val context = context ?: return val context = context ?: return
swipeEvent = SwipeEvent { isRightSwipe: Boolean -> swipeEvent = SwipeEvent { isRightSwipe: Boolean ->
@ -358,9 +317,46 @@ class StoryViewerFragment : Fragment() {
binding.imageViewer.setTapListener(simpleOnGestureListener) binding.imageViewer.setTapListener(simpleOnGestureListener)
} }
private fun setupMultipage(models: List<Story>?) {
if (models == null) return
storiesViewModel.getPagination().observe(viewLifecycleOwner, {
when (it) {
StoryPaginationType.FORWARD -> {
if (currentFeedStoryIndex == models.size - 1)
Toast.makeText(
context,
R.string.no_more_stories,
Toast.LENGTH_SHORT
).show()
else paginateStories(false, currentFeedStoryIndex == models.size - 2)
}
StoryPaginationType.BACKWARD -> {
if (currentFeedStoryIndex == 0)
Toast.makeText(
context,
R.string.no_more_stories,
Toast.LENGTH_SHORT
).show()
else paginateStories(true, false)
}
StoryPaginationType.ERROR -> {
Toast.makeText(
context,
R.string.downloader_unknown_error,
Toast.LENGTH_SHORT
).show()
}
}
})
if (!models.isEmpty()) {
binding.btnBackward.isEnabled = currentFeedStoryIndex != 0
binding.btnForward.isEnabled = currentFeedStoryIndex != models.size - 1
resetView()
}
}
private fun resetView() { private fun resetView() {
val context = context ?: return val context = context ?: return
live = null
if (menuProfile != null) menuProfile!!.isVisible = false if (menuProfile != null) menuProfile!!.isVisible = false
binding.imageViewer.controller = null binding.imageViewer.controller = null
releasePlayer() releasePlayer()
@ -368,7 +364,7 @@ class StoryViewerFragment : Fragment() {
var fetchOptions: StoryViewerOptions? = null var fetchOptions: StoryViewerOptions? = null
when (type) { when (type) {
StoryViewerOptions.Type.HIGHLIGHT -> { StoryViewerOptions.Type.HIGHLIGHT -> {
val models = storiesViewModel.getHighlights().value val models = storiesViewModel.highlights.value
if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size || currentFeedStoryIndex < 0) { if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size || currentFeedStoryIndex < 0) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show()
return return
@ -379,10 +375,15 @@ class StoryViewerFragment : Fragment() {
val feedStoriesViewModel = listViewModel as FeedStoriesViewModel? val feedStoriesViewModel = listViewModel as FeedStoriesViewModel?
val models = feedStoriesViewModel!!.list.value val models = feedStoriesViewModel!!.list.value
if (models == null || currentFeedStoryIndex >= models.size || currentFeedStoryIndex < 0) return if (models == null || currentFeedStoryIndex >= models.size || currentFeedStoryIndex < 0) return
val (_, _, _, _, user, _, _, _, _, _, _, broadcast) = models[currentFeedStoryIndex] val userStory = models[currentFeedStoryIndex]
currentStoryUsername = user!!.username currentStoryUsername = userStory.user!!.username
fetchOptions = StoryViewerOptions.forUser(user.pk, currentStoryUsername) fetchOptions = StoryViewerOptions.forUser(userStory.user.pk, currentStoryUsername)
live = broadcast val live = userStory.broadcast
if (live != null) {
storiesViewModel.setStory(userStory)
refreshLive(live)
return
}
} }
StoryViewerOptions.Type.STORY_ARCHIVE -> { StoryViewerOptions.Type.STORY_ARCHIVE -> {
val archivesViewModel = listViewModel as ArchivesViewModel? val archivesViewModel = listViewModel as ArchivesViewModel?
@ -405,11 +406,7 @@ class StoryViewerFragment : Fragment() {
storiesViewModel.fetchSingleMedia(options!!.id) storiesViewModel.fetchSingleMedia(options!!.id)
return return
} }
if (live != null) { storiesViewModel.fetchStory(fetchOptions).observe(viewLifecycleOwner, {
refreshLive()
return
}
storiesViewModel.fetchStory(fetchOptions).observe(fragmentActivity, {
if (it.status == Resource.Status.ERROR) { if (it.status == Resource.Status.ERROR) {
Toast.makeText(context, "Error: " + it.message, Toast.LENGTH_SHORT).show() Toast.makeText(context, "Error: " + it.message, Toast.LENGTH_SHORT).show()
} }
@ -417,18 +414,14 @@ class StoryViewerFragment : Fragment() {
} }
@Synchronized @Synchronized
private fun refreshLive() { private fun refreshLive(live: Broadcast) {
binding.btnDownload.isEnabled = false
binding.stickers.isEnabled = false
binding.listToggle.isEnabled = false
binding.btnShare.isEnabled = false
binding.btnReply.isEnabled = false
releasePlayer() releasePlayer()
setupLive(live!!.dashPlaybackUrl ?: live!!.dashAbrPlaybackUrl ?: return) setupLive(live.dashPlaybackUrl ?: live.dashAbrPlaybackUrl ?: return)
val actionBar = fragmentActivity.supportActionBar
actionBarSubtitle = epochSecondToString(live!!.publishedTime!!)
if (actionBar != null) {
try {
actionBar.setSubtitle(actionBarSubtitle)
} catch (e: Exception) {
Log.e(TAG, "refreshLive: ", e)
}
}
} }
@Synchronized @Synchronized
@ -447,14 +440,19 @@ class StoryViewerFragment : Fragment() {
binding.btnReply.isEnabled = currentStory.canReply binding.btnReply.isEnabled = currentStory.canReply
if (itemType === MediaItemType.MEDIA_TYPE_VIDEO) setupVideo(url) else setupImage(url) if (itemType === MediaItemType.MEDIA_TYPE_VIDEO) setupVideo(url) else setupImage(url)
// if (Utils.settingsHelper.getBoolean(MARK_AS_SEEN)) storiesRepository!!.seen( if (options!!.type == StoryViewerOptions.Type.FEED_STORY_POSITION
// csrfToken, && Utils.settingsHelper.getBoolean(PreferenceKeys.MARK_AS_SEEN)) {
// userId, val feedStoriesViewModel = listViewModel as FeedStoriesViewModel?
// deviceId, storiesViewModel.markAsSeen(currentStory).observe(viewLifecycleOwner) { m ->
// currentStory!!.id!!, if (m.status == Resource.Status.SUCCESS && m.data != null) {
// currentStory!!.takenAt, val liveModels: MutableLiveData<List<Story>> = feedStoriesViewModel!!.list
// System.currentTimeMillis() / 1000 val models = liveModels.value
// ) val modelsCopy: MutableList<Story> = models!!.toMutableList()
modelsCopy.set(currentFeedStoryIndex, m.data)
liveModels.postValue(modelsCopy)
}
}
}
} }
private fun downloadStory() { private fun downloadStory() {

View File

@ -322,7 +322,9 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
binding.getRoot().postDelayed(feedStoriesAdapter::notifyDataSetChanged, 1000); // temporary fix
feedStoriesViewModel.getList().removeObservers(getViewLifecycleOwner());
feedStoriesViewModel.getList().observe(getViewLifecycleOwner(), feedStoriesAdapter::submitList);
} }
@Override @Override
@ -398,8 +400,6 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
storiesFetching = false; storiesFetching = false;
//noinspection unchecked //noinspection unchecked
feedStoriesViewModel.getList().postValue((List<Story>) feedStoryModels); feedStoriesViewModel.getList().postValue((List<Story>) feedStoryModels);
//noinspection unchecked
feedStoriesAdapter.submitList((List<Story>) feedStoryModels);
if (storyListMenu != null) storyListMenu.setVisible(true); if (storyListMenu != null) storyListMenu.setVisible(true);
updateSwipeRefreshState(); updateSwipeRefreshState();
}), Dispatchers.getIO()) }), Dispatchers.getIO())

View File

@ -11,7 +11,7 @@ data class Story(
val latestReelMedia: Long? = null, // = timestamp val latestReelMedia: Long? = null, // = timestamp
val mediaCount: Int? = null, val mediaCount: Int? = null,
// for stories and highlights // for stories and highlights
var seen: Long? = null, val seen: Long? = null,
val user: User? = null, val user: User? = null,
// for stories // for stories
val muted: Boolean? = null, val muted: Boolean? = null,

View File

@ -60,21 +60,22 @@ class StoryFragmentViewModel : ViewModel() {
private val mediaRepository: MediaRepository by lazy { MediaRepository.getInstance() } private val mediaRepository: MediaRepository by lazy { MediaRepository.getInstance() }
// for highlights ONLY // for highlights ONLY
private val highlights = MutableLiveData<List<Story>?>() val highlights = MutableLiveData<List<Story>?>()
/* set functions */ /* set functions */
fun setStory(story: Story) { fun setStory(story: Story) {
if (story.items == null || story.items.size == 0) {
pagination.postValue(StoryPaginationType.ERROR)
return
}
currentStory.postValue(story) currentStory.postValue(story)
storyTitle.postValue(story.title ?: story.user?.username) storyTitle.postValue(story.title ?: story.user?.username)
if (story.broadcast != null) { if (story.broadcast != null) {
date.postValue(story.dateTime) date.postValue(story.dateTime)
type.postValue(MediaItemType.MEDIA_TYPE_LIVE) type.postValue(MediaItemType.MEDIA_TYPE_LIVE)
pagination.postValue(StoryPaginationType.DO_NOTHING) pagination.postValue(StoryPaginationType.DO_NOTHING)
return
}
if (story.items == null || story.items.size == 0) {
pagination.postValue(StoryPaginationType.ERROR)
return
} }
} }
@ -184,10 +185,6 @@ class StoryFragmentViewModel : ViewModel() {
/* get functions */ /* get functions */
fun getHighlights(): LiveData<List<Story>?> {
return highlights
}
fun getCurrentStory(): LiveData<Story?> { fun getCurrentStory(): LiveData<Story?> {
return currentStory return currentStory
} }
@ -472,4 +469,27 @@ class StoryFragmentViewModel : ViewModel() {
} }
return data return data
} }
fun markAsSeen(storyMedia: StoryMedia): LiveData<Resource<Story?>> {
val data = MutableLiveData<Resource<Story?>>()
data.postValue(loading(null))
viewModelScope.launch(Dispatchers.IO) {
try {
storiesRepository.seen(
csrfToken!!,
userId,
deviceId,
storyMedia.id,
storyMedia.takenAt,
System.currentTimeMillis() / 1000
)
val oldStory = currentStory.value!!
val newStory = oldStory.copy(seen = storyMedia.takenAt)
data.postValue(success(newStory))
} catch (e: Exception) {
data.postValue(error(e.message, null))
}
}
return data
}
} }