Browse Source

restore marking feed stories as seen

renovate/org.jetbrains.kotlinx-kotlinx-coroutines-test-1.x
Austin Huang 4 years ago
parent
commit
c82080acf4
No known key found for this signature in database GPG Key ID: 84C23AA04587A91F
  1. 164
      app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt
  2. 6
      app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java
  3. 2
      app/src/main/java/awais/instagrabber/repositories/responses/stories/Story.kt
  4. 38
      app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt

164
app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt

@ -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, {
if (it?.items != null) {
storiesViewModel.getCurrentStory().observe(viewLifecycleOwner, {
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.getCurrentIndex().observe(fragmentActivity, {
storiesViewModel.getCurrentMedia().observe(viewLifecycleOwner, { refreshStory(it) })
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,30 +263,63 @@ 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) {
val context = context ?: return
swipeEvent = SwipeEvent { isRightSwipe: Boolean ->
storiesViewModel.paginate(isRightSwipe)
}
gestureDetector = GestureDetectorCompat(context, SwipeGestureListener(swipeEvent))
binding.playerView.setOnTouchListener { _, event -> gestureDetector!!.onTouchEvent(event) }
val simpleOnGestureListener: SimpleOnGestureListener = object : SimpleOnGestureListener() {
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
val diffX = e2.x - e1.x
try {
if (Math.abs(diffX) > Math.abs(e2.y - e1.y) && Math.abs(diffX) > SwipeGestureListener.SWIPE_THRESHOLD && Math.abs(
velocityX
) > SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD
) {
storiesViewModel.paginate(diffX > 0)
return true
}
} catch (e: Exception) {
if (BuildConfig.DEBUG) Log.e(TAG, "Error", e)
}
return false
}
}
binding.imageViewer.setTapListener(simpleOnGestureListener)
}
private fun setupMultipage(models: List<Story>?) {
if (models == null) return
storiesViewModel.getPagination().observe(viewLifecycleOwner, {
when (it) { when (it) {
StoryPaginationType.FORWARD -> { StoryPaginationType.FORWARD -> {
if (currentFeedStoryIndex == models.size - 1) if (currentFeedStoryIndex == models.size - 1)
@ -318,49 +347,16 @@ class StoryViewerFragment : Fragment() {
).show() ).show()
} }
} }
}
}) })
if (models != null && !models.isEmpty()) {
if (!models.isEmpty()) {
binding.btnBackward.isEnabled = currentFeedStoryIndex != 0 binding.btnBackward.isEnabled = currentFeedStoryIndex != 0
binding.btnForward.isEnabled = currentFeedStoryIndex != models.size - 1 binding.btnForward.isEnabled = currentFeedStoryIndex != models.size - 1
resetView() resetView()
} }
})
val context = context ?: return
swipeEvent = SwipeEvent { isRightSwipe: Boolean ->
storiesViewModel.paginate(isRightSwipe)
}
gestureDetector = GestureDetectorCompat(context, SwipeGestureListener(swipeEvent))
binding.playerView.setOnTouchListener { _, event -> gestureDetector!!.onTouchEvent(event) }
val simpleOnGestureListener: SimpleOnGestureListener = object : SimpleOnGestureListener() {
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
val diffX = e2.x - e1.x
try {
if (Math.abs(diffX) > Math.abs(e2.y - e1.y) && Math.abs(diffX) > SwipeGestureListener.SWIPE_THRESHOLD && Math.abs(
velocityX
) > SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD
) {
storiesViewModel.paginate(diffX > 0)
return true
}
} catch (e: Exception) {
if (BuildConfig.DEBUG) Log.e(TAG, "Error", e)
}
return false
}
}
binding.imageViewer.setTapListener(simpleOnGestureListener)
} }
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]
currentStoryUsername = user!!.username
fetchOptions = StoryViewerOptions.forUser(user.pk, currentStoryUsername)
live = broadcast
val userStory = models[currentFeedStoryIndex]
currentStoryUsername = userStory.user!!.username
fetchOptions = StoryViewerOptions.forUser(userStory.user.pk, currentStoryUsername)
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) {
refreshLive()
return
}
storiesViewModel.fetchStory(fetchOptions).observe(fragmentActivity, {
storiesViewModel.fetchStory(fetchOptions).observe(viewLifecycleOwner, {
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)
val actionBar = fragmentActivity.supportActionBar
actionBarSubtitle = epochSecondToString(live!!.publishedTime!!)
if (actionBar != null) {
try {
actionBar.setSubtitle(actionBarSubtitle)
} catch (e: Exception) {
Log.e(TAG, "refreshLive: ", e)
}
}
setupLive(live.dashPlaybackUrl ?: live.dashAbrPlaybackUrl ?: return)
} }
@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(
// csrfToken,
// userId,
// deviceId,
// currentStory!!.id!!,
// currentStory!!.takenAt,
// System.currentTimeMillis() / 1000
// )
if (options!!.type == StoryViewerOptions.Type.FEED_STORY_POSITION
&& Utils.settingsHelper.getBoolean(PreferenceKeys.MARK_AS_SEEN)) {
val feedStoriesViewModel = listViewModel as FeedStoriesViewModel?
storiesViewModel.markAsSeen(currentStory).observe(viewLifecycleOwner) { m ->
if (m.status == Resource.Status.SUCCESS && m.data != null) {
val liveModels: MutableLiveData<List<Story>> = feedStoriesViewModel!!.list
val models = liveModels.value
val modelsCopy: MutableList<Story> = models!!.toMutableList()
modelsCopy.set(currentFeedStoryIndex, m.data)
liveModels.postValue(modelsCopy)
}
}
}
} }
private fun downloadStory() { private fun downloadStory() {

6
app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java

@ -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())

2
app/src/main/java/awais/instagrabber/repositories/responses/stories/Story.kt

@ -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,

38
app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt

@ -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
}
} }
Loading…
Cancel
Save