mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-11-22 06:37:30 +00:00
story viewmodel (wip)
hiding the storylist doesn't work yet but everything else should be good
This commit is contained in:
parent
31ea42d105
commit
bb5244665b
@ -1,5 +1,7 @@
|
|||||||
package awais.instagrabber.adapters;
|
package awais.instagrabber.adapters;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -72,6 +74,16 @@ public final class StoriesAdapter extends ListAdapter<StoryMedia, StoriesAdapter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void paginate(final int newIndex) {
|
||||||
|
final List<StoryMedia> list = getCurrentList();
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
final StoryMedia item = list.get(i);
|
||||||
|
if (!item.isCurrentSlide() && i != newIndex) continue;
|
||||||
|
item.setCurrentSlide(i == newIndex);
|
||||||
|
notifyItemChanged(i, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public interface OnItemClickListener {
|
public interface OnItemClickListener {
|
||||||
void onItemClick(StoryMedia storyModel, int position);
|
void onItemClick(StoryMedia storyModel, int position);
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,902 @@
|
|||||||
|
package awais.instagrabber.fragments
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.DialogInterface.OnClickListener
|
||||||
|
import android.graphics.drawable.Animatable
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.*
|
||||||
|
import android.view.GestureDetector.SimpleOnGestureListener
|
||||||
|
import android.widget.*
|
||||||
|
import android.widget.SeekBar.OnSeekBarChangeListener
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.view.ContextThemeWrapper
|
||||||
|
import androidx.appcompat.widget.PopupMenu
|
||||||
|
import androidx.core.view.GestureDetectorCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import awais.instagrabber.BuildConfig
|
||||||
|
import awais.instagrabber.R
|
||||||
|
import awais.instagrabber.adapters.StoriesAdapter
|
||||||
|
import awais.instagrabber.customviews.helpers.SwipeGestureListener
|
||||||
|
import awais.instagrabber.databinding.FragmentStoryViewerBinding
|
||||||
|
import awais.instagrabber.fragments.settings.PreferenceKeys
|
||||||
|
import awais.instagrabber.interfaces.SwipeEvent
|
||||||
|
import awais.instagrabber.models.Resource
|
||||||
|
import awais.instagrabber.models.enums.FavoriteType
|
||||||
|
import awais.instagrabber.models.enums.MediaItemType
|
||||||
|
import awais.instagrabber.models.enums.StoryPaginationType
|
||||||
|
import awais.instagrabber.repositories.requests.StoryViewerOptions
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
|
||||||
|
import awais.instagrabber.repositories.responses.stories.*
|
||||||
|
import awais.instagrabber.utils.*
|
||||||
|
import awais.instagrabber.utils.DownloadUtils.download
|
||||||
|
import awais.instagrabber.utils.TextUtils.epochSecondToString
|
||||||
|
import awais.instagrabber.utils.extensions.TAG
|
||||||
|
import awais.instagrabber.viewmodels.ArchivesViewModel
|
||||||
|
import awais.instagrabber.viewmodels.FeedStoriesViewModel
|
||||||
|
import awais.instagrabber.viewmodels.HighlightsViewModel
|
||||||
|
import awais.instagrabber.viewmodels.StoryFragmentViewModel
|
||||||
|
import awais.instagrabber.webservices.MediaRepository
|
||||||
|
import awais.instagrabber.webservices.StoriesRepository
|
||||||
|
import com.facebook.drawee.backends.pipeline.Fresco
|
||||||
|
import com.facebook.drawee.controller.BaseControllerListener
|
||||||
|
import com.facebook.drawee.interfaces.DraweeController
|
||||||
|
import com.facebook.imagepipeline.image.ImageInfo
|
||||||
|
import com.facebook.imagepipeline.request.ImageRequestBuilder
|
||||||
|
import com.google.android.exoplayer2.MediaItem
|
||||||
|
import com.google.android.exoplayer2.Player
|
||||||
|
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||||
|
import com.google.android.exoplayer2.source.*
|
||||||
|
import com.google.android.exoplayer2.source.dash.DashMediaSource
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
|
||||||
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
|
import java.io.IOException
|
||||||
|
import java.text.NumberFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
class StoryViewerFragment : Fragment() {
|
||||||
|
private val TAG = "StoryViewerFragment"
|
||||||
|
|
||||||
|
private val cookie = Utils.settingsHelper.getString(Constants.COOKIE)
|
||||||
|
private var root: View? = null
|
||||||
|
private var currentStoryUsername: String? = null
|
||||||
|
private var highlightTitle: String? = null
|
||||||
|
private var storiesAdapter: StoriesAdapter? = null
|
||||||
|
private var swipeEvent: SwipeEvent? = null
|
||||||
|
private var gestureDetector: GestureDetectorCompat? = null
|
||||||
|
private val storiesRepository: StoriesRepository? = null
|
||||||
|
private val mediaRepository: MediaRepository? = null
|
||||||
|
private var live: Broadcast? = null
|
||||||
|
private var menuProfile: MenuItem? = null
|
||||||
|
private var profileVisible: Boolean = false
|
||||||
|
private var player: SimpleExoPlayer? = null
|
||||||
|
|
||||||
|
private var actionBarTitle: String? = null
|
||||||
|
private var actionBarSubtitle: String? = null
|
||||||
|
private var fetching = false
|
||||||
|
private val sticking = false
|
||||||
|
private var shouldRefresh = true
|
||||||
|
private var dmVisible = false
|
||||||
|
private var currentFeedStoryIndex = 0
|
||||||
|
private var sliderValue = 0.0
|
||||||
|
private var options: StoryViewerOptions? = null
|
||||||
|
private var listViewModel: ViewModel? = null
|
||||||
|
private var backStackSavedStateResultLiveData: MutableLiveData<Any?>? = null
|
||||||
|
private lateinit var fragmentActivity: AppCompatActivity
|
||||||
|
private lateinit var storiesViewModel: StoryFragmentViewModel
|
||||||
|
private lateinit var binding: FragmentStoryViewerBinding
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private val backStackSavedStateObserver = Observer<Any?> { result ->
|
||||||
|
if (result == null) return@Observer
|
||||||
|
if ((result is RankedRecipient)) {
|
||||||
|
if (context != null) {
|
||||||
|
Toast.makeText(context, R.string.sending, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
storiesViewModel.shareDm(result)
|
||||||
|
} else if ((result is Set<*>)) {
|
||||||
|
try {
|
||||||
|
if (context != null) {
|
||||||
|
Toast.makeText(context, R.string.sending, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
storiesViewModel.shareDm(result as Set<RankedRecipient>)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "share: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// clear result
|
||||||
|
backStackSavedStateResultLiveData?.postValue(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
fragmentActivity = requireActivity() as AppCompatActivity
|
||||||
|
storiesViewModel = ViewModelProvider(this).get(StoryFragmentViewModel::class.java)
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
if (root != null) {
|
||||||
|
shouldRefresh = false
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
binding = FragmentStoryViewerBinding.inflate(inflater, container, false)
|
||||||
|
root = binding.root
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
if (!shouldRefresh) return
|
||||||
|
init()
|
||||||
|
shouldRefresh = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||||
|
menuInflater.inflate(R.menu.story_menu, menu)
|
||||||
|
menuProfile = menu.findItem(R.id.action_profile)
|
||||||
|
menuProfile!!.isVisible = profileVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||||
|
// hide menu items from activity
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
val context = context ?: return false
|
||||||
|
val itemId = item.itemId
|
||||||
|
if (itemId == R.id.action_profile) {
|
||||||
|
val username = storiesViewModel.getCurrentStory().value?.user?.username
|
||||||
|
openProfile(Pair(username, FavoriteType.USER))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
player?.pause() ?: return
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
try {
|
||||||
|
val backStackEntry = NavHostFragment.findNavController(this).currentBackStackEntry
|
||||||
|
if (backStackEntry != null) {
|
||||||
|
backStackSavedStateResultLiveData = backStackEntry.savedStateHandle.getLiveData("result")
|
||||||
|
backStackSavedStateResultLiveData?.observe(viewLifecycleOwner, backStackSavedStateObserver)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "onResume: ", e)
|
||||||
|
}
|
||||||
|
val actionBar = fragmentActivity.supportActionBar ?: return
|
||||||
|
actionBar.title = storiesViewModel.getTitle().value
|
||||||
|
actionBar.subtitle = storiesViewModel.getDate().value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
releasePlayer()
|
||||||
|
val actionBar = fragmentActivity.supportActionBar
|
||||||
|
actionBar?.subtitle = null
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun init() {
|
||||||
|
val args = arguments
|
||||||
|
if (args == null) return
|
||||||
|
val fragmentArgs = StoryViewerFragmentArgs.fromBundle(args)
|
||||||
|
options = fragmentArgs.options
|
||||||
|
currentFeedStoryIndex = options!!.currentFeedStoryIndex
|
||||||
|
val type = options!!.type
|
||||||
|
if (currentFeedStoryIndex >= 0) {
|
||||||
|
listViewModel = when (type) {
|
||||||
|
StoryViewerOptions.Type.HIGHLIGHT -> ViewModelProvider(fragmentActivity).get(
|
||||||
|
HighlightsViewModel::class.java
|
||||||
|
)
|
||||||
|
StoryViewerOptions.Type.STORY_ARCHIVE -> ViewModelProvider(fragmentActivity).get(
|
||||||
|
ArchivesViewModel::class.java
|
||||||
|
)
|
||||||
|
StoryViewerOptions.Type.FEED_STORY_POSITION -> ViewModelProvider(fragmentActivity).get(
|
||||||
|
FeedStoriesViewModel::class.java
|
||||||
|
)
|
||||||
|
else -> ViewModelProvider(fragmentActivity).get(
|
||||||
|
FeedStoriesViewModel::class.java
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setupButtons()
|
||||||
|
setupStories()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupStories() {
|
||||||
|
setupListeners()
|
||||||
|
val context = context ?: return
|
||||||
|
binding.storiesList.layoutManager =
|
||||||
|
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
||||||
|
storiesAdapter = StoriesAdapter { model: StoryMedia?, position: Int ->
|
||||||
|
storiesViewModel.setMedia(position)
|
||||||
|
}
|
||||||
|
binding.storiesList.adapter = storiesAdapter
|
||||||
|
storiesViewModel.getCurrentStory().observe(fragmentActivity, {
|
||||||
|
if (it?.items != null) {
|
||||||
|
val storyMedias = it.items.toMutableList()
|
||||||
|
val newItem = storyMedias.get(0)
|
||||||
|
newItem.isCurrentSlide = true
|
||||||
|
storyMedias.set(0, newItem)
|
||||||
|
storiesAdapter!!.submitList(storyMedias)
|
||||||
|
storiesViewModel.setMedia(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
storiesViewModel.getDate().observe(fragmentActivity, {
|
||||||
|
val actionBar = fragmentActivity.supportActionBar
|
||||||
|
if (actionBar != null && it != null) actionBar.subtitle = it
|
||||||
|
})
|
||||||
|
storiesViewModel.getTitle().observe(fragmentActivity, {
|
||||||
|
val actionBar = fragmentActivity.supportActionBar
|
||||||
|
if (actionBar != null && it != null) actionBar.title = it
|
||||||
|
})
|
||||||
|
storiesViewModel.getCurrentMedia().observe(fragmentActivity, { refreshStory(it) })
|
||||||
|
storiesViewModel.getCurrentIndex().observe(fragmentActivity, {
|
||||||
|
storiesAdapter!!.paginate(it)
|
||||||
|
})
|
||||||
|
storiesViewModel.getOptions().observe(fragmentActivity, {
|
||||||
|
binding.stickers.isEnabled = it.first.size > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
resetView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupButtons() {
|
||||||
|
binding.btnDownload.setOnClickListener({ _ -> downloadStory() })
|
||||||
|
binding.btnForward.setOnClickListener({ _ -> storiesViewModel.skip(false) })
|
||||||
|
binding.btnBackward.setOnClickListener({ _ -> storiesViewModel.skip(true) })
|
||||||
|
binding.btnShare.setOnClickListener({ _ -> shareStoryViaDm() })
|
||||||
|
binding.btnReply.setOnClickListener({ _ -> createReplyDialog(null) })
|
||||||
|
binding.stickers.setOnClickListener({ _ -> showStickerMenu() })
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
private fun setupListeners() {
|
||||||
|
val hasFeedStories: Boolean
|
||||||
|
var models: List<Story>? = null
|
||||||
|
if (currentFeedStoryIndex >= 0) {
|
||||||
|
val type = options!!.type
|
||||||
|
when (type) {
|
||||||
|
StoryViewerOptions.Type.HIGHLIGHT -> {
|
||||||
|
val highlightsViewModel = listViewModel as HighlightsViewModel?
|
||||||
|
models = highlightsViewModel!!.list.value
|
||||||
|
}
|
||||||
|
StoryViewerOptions.Type.FEED_STORY_POSITION -> {
|
||||||
|
val feedStoriesViewModel = listViewModel as FeedStoriesViewModel?
|
||||||
|
models = feedStoriesViewModel!!.list.value
|
||||||
|
}
|
||||||
|
StoryViewerOptions.Type.STORY_ARCHIVE -> {
|
||||||
|
val archivesViewModel = listViewModel as ArchivesViewModel?
|
||||||
|
models = archivesViewModel!!.list.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasFeedStories = models != null && !models.isEmpty()
|
||||||
|
|
||||||
|
storiesViewModel.getPagination().observe(fragmentActivity, {
|
||||||
|
if (models != null) {
|
||||||
|
when (it) {
|
||||||
|
StoryPaginationType.FORWARD -> {
|
||||||
|
paginateStories(false, currentFeedStoryIndex == models.size - 2)
|
||||||
|
}
|
||||||
|
StoryPaginationType.BACKWARD -> {
|
||||||
|
paginateStories(true, false)
|
||||||
|
}
|
||||||
|
StoryPaginationType.ERROR -> {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
R.string.downloader_unknown_error,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
StoryPaginationType.DO_NOTHING -> {
|
||||||
|
} // do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
val context = context ?: return
|
||||||
|
swipeEvent = label@ 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasFeedStories) {
|
||||||
|
binding.btnBackward.isEnabled = currentFeedStoryIndex != 0
|
||||||
|
binding.btnForward.isEnabled = currentFeedStoryIndex != models!!.size - 1
|
||||||
|
}
|
||||||
|
binding.imageViewer.setTapListener(simpleOnGestureListener)
|
||||||
|
|
||||||
|
// process stickers
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetView() {
|
||||||
|
val context = context ?: return
|
||||||
|
live = null
|
||||||
|
if (menuProfile != null) menuProfile!!.isVisible = false
|
||||||
|
profileVisible = false
|
||||||
|
binding.imageViewer.controller = null
|
||||||
|
releasePlayer()
|
||||||
|
val type = options!!.type
|
||||||
|
var fetchOptions: StoryViewerOptions? = null
|
||||||
|
when (type) {
|
||||||
|
StoryViewerOptions.Type.HIGHLIGHT -> {
|
||||||
|
val highlightsViewModel = listViewModel as HighlightsViewModel?
|
||||||
|
val models = highlightsViewModel!!.list.value
|
||||||
|
if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size || currentFeedStoryIndex < 0) {
|
||||||
|
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val (id, _, _, _, _, _, _, _, _, title) = models[currentFeedStoryIndex]
|
||||||
|
fetchOptions = StoryViewerOptions.forHighlight(id)
|
||||||
|
}
|
||||||
|
StoryViewerOptions.Type.FEED_STORY_POSITION -> {
|
||||||
|
val feedStoriesViewModel = listViewModel as FeedStoriesViewModel?
|
||||||
|
val models = feedStoriesViewModel!!.list.value
|
||||||
|
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
|
||||||
|
}
|
||||||
|
StoryViewerOptions.Type.STORY_ARCHIVE -> {
|
||||||
|
val archivesViewModel = listViewModel as ArchivesViewModel?
|
||||||
|
val models = archivesViewModel!!.list.value
|
||||||
|
if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size || currentFeedStoryIndex < 0) {
|
||||||
|
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val (id, _, _, _, _, _, _, _, _, title) = models[currentFeedStoryIndex]
|
||||||
|
currentStoryUsername = title
|
||||||
|
fetchOptions = StoryViewerOptions.forStoryArchive(id)
|
||||||
|
}
|
||||||
|
StoryViewerOptions.Type.USER -> {
|
||||||
|
currentStoryUsername = options!!.name
|
||||||
|
fetchOptions = StoryViewerOptions.forUser(options!!.id, currentStoryUsername)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type == StoryViewerOptions.Type.STORY) {
|
||||||
|
storiesViewModel.fetchSingleMedia(options!!.id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (live != null) {
|
||||||
|
refreshLive()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
storiesViewModel.fetchStory(fetchOptions).observe(fragmentActivity, {
|
||||||
|
// toast error if necessary?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun refreshLive() {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun refreshStory(currentStory: StoryMedia) {
|
||||||
|
val itemType = currentStory.type
|
||||||
|
val url = if (itemType === MediaItemType.MEDIA_TYPE_IMAGE) ResponseBodyUtils.getImageUrl(currentStory)
|
||||||
|
else ResponseBodyUtils.getVideoUrl(currentStory)
|
||||||
|
|
||||||
|
releasePlayer()
|
||||||
|
|
||||||
|
binding.btnDownload.isEnabled = false
|
||||||
|
binding.btnShare.isEnabled = currentStory.canReshare
|
||||||
|
binding.btnReply.isEnabled = currentStory.canReply
|
||||||
|
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
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun downloadStory() {
|
||||||
|
val context = context ?: return
|
||||||
|
val currentStory = storiesViewModel.getMedia().value
|
||||||
|
if (currentStory == null) {
|
||||||
|
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
download(context, currentStory)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupImage(url: String) {
|
||||||
|
binding.progressView.visibility = View.VISIBLE
|
||||||
|
binding.playerView.visibility = View.GONE
|
||||||
|
binding.imageViewer.visibility = View.VISIBLE
|
||||||
|
val requestBuilder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url))
|
||||||
|
.setLocalThumbnailPreviewsEnabled(true)
|
||||||
|
.setProgressiveRenderingEnabled(true)
|
||||||
|
.build()
|
||||||
|
val controller: DraweeController = Fresco.newDraweeControllerBuilder()
|
||||||
|
.setImageRequest(requestBuilder)
|
||||||
|
.setOldController(binding.imageViewer.controller)
|
||||||
|
.setControllerListener(object : BaseControllerListener<ImageInfo?>() {
|
||||||
|
override fun onFailure(id: String, throwable: Throwable) {
|
||||||
|
binding.btnDownload.isEnabled = false
|
||||||
|
binding.progressView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFinalImageSet(
|
||||||
|
id: String,
|
||||||
|
imageInfo: ImageInfo?,
|
||||||
|
animatable: Animatable?
|
||||||
|
) {
|
||||||
|
binding.btnDownload.isEnabled = true
|
||||||
|
binding.progressView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
binding.imageViewer.controller = controller
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupVideo(url: String) {
|
||||||
|
binding.playerView.visibility = View.VISIBLE
|
||||||
|
binding.progressView.visibility = View.GONE
|
||||||
|
binding.imageViewer.visibility = View.GONE
|
||||||
|
binding.imageViewer.controller = null
|
||||||
|
val context = context ?: return
|
||||||
|
player = SimpleExoPlayer.Builder(context).build()
|
||||||
|
binding.playerView.player = player
|
||||||
|
player!!.playWhenReady =
|
||||||
|
Utils.settingsHelper.getBoolean(PreferenceKeys.AUTOPLAY_VIDEOS_STORIES)
|
||||||
|
val uri = Uri.parse(url)
|
||||||
|
val mediaItem = MediaItem.fromUri(uri)
|
||||||
|
val mediaSource =
|
||||||
|
ProgressiveMediaSource.Factory(DefaultDataSourceFactory(context, "instagram"))
|
||||||
|
.createMediaSource(mediaItem)
|
||||||
|
mediaSource.addEventListener(Handler(), object : MediaSourceEventListener {
|
||||||
|
override fun onLoadCompleted(
|
||||||
|
windowIndex: Int,
|
||||||
|
mediaPeriodId: MediaSource.MediaPeriodId?,
|
||||||
|
loadEventInfo: LoadEventInfo,
|
||||||
|
mediaLoadData: MediaLoadData
|
||||||
|
) {
|
||||||
|
binding.btnDownload.isEnabled = true
|
||||||
|
binding.progressView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadStarted(
|
||||||
|
windowIndex: Int,
|
||||||
|
mediaPeriodId: MediaSource.MediaPeriodId?,
|
||||||
|
loadEventInfo: LoadEventInfo,
|
||||||
|
mediaLoadData: MediaLoadData
|
||||||
|
) {
|
||||||
|
binding.btnDownload.isEnabled = true
|
||||||
|
binding.progressView.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadCanceled(
|
||||||
|
windowIndex: Int,
|
||||||
|
mediaPeriodId: MediaSource.MediaPeriodId?,
|
||||||
|
loadEventInfo: LoadEventInfo,
|
||||||
|
mediaLoadData: MediaLoadData
|
||||||
|
) {
|
||||||
|
binding.progressView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadError(
|
||||||
|
windowIndex: Int,
|
||||||
|
mediaPeriodId: MediaSource.MediaPeriodId?,
|
||||||
|
loadEventInfo: LoadEventInfo,
|
||||||
|
mediaLoadData: MediaLoadData,
|
||||||
|
error: IOException,
|
||||||
|
wasCanceled: Boolean
|
||||||
|
) {
|
||||||
|
binding.btnDownload.isEnabled = false
|
||||||
|
if (menuProfile != null) {
|
||||||
|
profileVisible = false
|
||||||
|
menuProfile!!.isVisible = false
|
||||||
|
}
|
||||||
|
binding.progressView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
player!!.setMediaSource(mediaSource)
|
||||||
|
player!!.prepare()
|
||||||
|
binding.playerView.setOnClickListener { v: View? ->
|
||||||
|
if (player != null) {
|
||||||
|
if (player!!.playbackState == Player.STATE_ENDED) player!!.seekTo(0)
|
||||||
|
player!!.playWhenReady =
|
||||||
|
player!!.playbackState == Player.STATE_ENDED || !player!!.isPlaying
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupLive(url: String) {
|
||||||
|
binding.playerView.visibility = View.VISIBLE
|
||||||
|
binding.progressView.visibility = View.GONE
|
||||||
|
binding.imageViewer.visibility = View.GONE
|
||||||
|
binding.imageViewer.controller = null
|
||||||
|
val context = context ?: return
|
||||||
|
player = SimpleExoPlayer.Builder(context).build()
|
||||||
|
binding.playerView.player = player
|
||||||
|
player!!.playWhenReady =
|
||||||
|
Utils.settingsHelper.getBoolean(PreferenceKeys.AUTOPLAY_VIDEOS_STORIES)
|
||||||
|
val uri = Uri.parse(url)
|
||||||
|
val mediaItem = MediaItem.fromUri(uri)
|
||||||
|
val mediaSource = DashMediaSource.Factory(DefaultDataSourceFactory(context, "instagram"))
|
||||||
|
.createMediaSource(mediaItem)
|
||||||
|
mediaSource.addEventListener(Handler(), object : MediaSourceEventListener {
|
||||||
|
override fun onLoadCompleted(
|
||||||
|
windowIndex: Int,
|
||||||
|
mediaPeriodId: MediaSource.MediaPeriodId?,
|
||||||
|
loadEventInfo: LoadEventInfo,
|
||||||
|
mediaLoadData: MediaLoadData
|
||||||
|
) {
|
||||||
|
binding.progressView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadStarted(
|
||||||
|
windowIndex: Int,
|
||||||
|
mediaPeriodId: MediaSource.MediaPeriodId?,
|
||||||
|
loadEventInfo: LoadEventInfo,
|
||||||
|
mediaLoadData: MediaLoadData
|
||||||
|
) {
|
||||||
|
binding.progressView.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadCanceled(
|
||||||
|
windowIndex: Int,
|
||||||
|
mediaPeriodId: MediaSource.MediaPeriodId?,
|
||||||
|
loadEventInfo: LoadEventInfo,
|
||||||
|
mediaLoadData: MediaLoadData
|
||||||
|
) {
|
||||||
|
binding.progressView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadError(
|
||||||
|
windowIndex: Int,
|
||||||
|
mediaPeriodId: MediaSource.MediaPeriodId?,
|
||||||
|
loadEventInfo: LoadEventInfo,
|
||||||
|
mediaLoadData: MediaLoadData,
|
||||||
|
error: IOException,
|
||||||
|
wasCanceled: Boolean
|
||||||
|
) {
|
||||||
|
binding.progressView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
player!!.setMediaSource(mediaSource)
|
||||||
|
player!!.prepare()
|
||||||
|
binding.playerView.setOnClickListener { _ ->
|
||||||
|
if (player != null) {
|
||||||
|
if (player!!.playbackState == Player.STATE_ENDED) player!!.seekTo(0)
|
||||||
|
player!!.playWhenReady =
|
||||||
|
player!!.playbackState == Player.STATE_ENDED || !player!!.isPlaying
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openProfile(data: Pair<String?, FavoriteType>) {
|
||||||
|
val navController: NavController = NavHostFragment.findNavController(this)
|
||||||
|
val bundle = Bundle()
|
||||||
|
if (data.first == null) {
|
||||||
|
// toast
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val actionBar = fragmentActivity.supportActionBar
|
||||||
|
if (actionBar != null) {
|
||||||
|
actionBar.title = null
|
||||||
|
actionBar.subtitle = null
|
||||||
|
}
|
||||||
|
when (data.second) {
|
||||||
|
FavoriteType.USER -> {
|
||||||
|
bundle.putString("username", data.first)
|
||||||
|
navController.navigate(R.id.action_global_profileFragment, bundle)
|
||||||
|
}
|
||||||
|
FavoriteType.HASHTAG -> {
|
||||||
|
bundle.putString("hashtag", data.first)
|
||||||
|
navController.navigate(R.id.action_global_hashTagFragment, bundle)
|
||||||
|
}
|
||||||
|
FavoriteType.LOCATION -> {
|
||||||
|
bundle.putLong("locationId", data.first!!.toLong())
|
||||||
|
navController.navigate(R.id.action_global_locationFragment, bundle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun releasePlayer() {
|
||||||
|
if (player == null) return
|
||||||
|
try {
|
||||||
|
player!!.stop(true)
|
||||||
|
} catch (ignored: Exception) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
player!!.release()
|
||||||
|
} catch (ignored: Exception) {
|
||||||
|
}
|
||||||
|
player = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun paginateStories(
|
||||||
|
backward: Boolean,
|
||||||
|
last: Boolean
|
||||||
|
) {
|
||||||
|
binding.btnBackward.isEnabled = currentFeedStoryIndex != 1 || !backward
|
||||||
|
binding.btnForward.isEnabled = !last
|
||||||
|
currentFeedStoryIndex = if (backward) currentFeedStoryIndex - 1 else currentFeedStoryIndex + 1
|
||||||
|
resetView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createChoiceDialog(
|
||||||
|
title: String?,
|
||||||
|
tallies: List<Tally>,
|
||||||
|
onClickListener: OnClickListener,
|
||||||
|
viewerVote: Int?,
|
||||||
|
correctAnswer: Int?
|
||||||
|
) {
|
||||||
|
val context = context ?: return
|
||||||
|
val choices = tallies.map {
|
||||||
|
(if (viewerVote == tallies.indexOf(it)) "√ " else "") +
|
||||||
|
(if (correctAnswer == tallies.indexOf(it)) "*** " else "") +
|
||||||
|
it.text + " (" + it.count + ")" }
|
||||||
|
val builder = AlertDialog.Builder(context)
|
||||||
|
if (title != null) builder.setTitle(title)
|
||||||
|
if (viewerVote != null) builder.setMessage(R.string.story_quizzed)
|
||||||
|
builder.setPositiveButton(if (viewerVote == null) R.string.cancel else R.string.ok, null)
|
||||||
|
val adapter = ArrayAdapter(context, android.R.layout.simple_list_item_1, choices.toTypedArray())
|
||||||
|
builder.setAdapter(adapter, onClickListener)
|
||||||
|
builder.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createMentionDialog() {
|
||||||
|
val context = context ?: return
|
||||||
|
val adapter = ArrayAdapter(context, android.R.layout.simple_list_item_1, storiesViewModel.getMentionTexts())
|
||||||
|
val builder = AlertDialog.Builder(context)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.setAdapter(adapter, { _, w ->
|
||||||
|
val data = storiesViewModel.getMention(w)
|
||||||
|
if (data != null) openProfile(Pair(data.second, data.third))
|
||||||
|
})
|
||||||
|
builder.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSliderDialog() {
|
||||||
|
val slider = storiesViewModel.getSlider().value ?: return
|
||||||
|
val context = context ?: return
|
||||||
|
val percentage: NumberFormat = NumberFormat.getPercentInstance()
|
||||||
|
percentage.maximumFractionDigits = 2
|
||||||
|
val sliderView = LinearLayout(context)
|
||||||
|
sliderView.layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
sliderView.orientation = LinearLayout.VERTICAL
|
||||||
|
val tv = TextView(context)
|
||||||
|
tv.gravity = Gravity.CENTER_HORIZONTAL
|
||||||
|
val input = SeekBar(context)
|
||||||
|
val avg: Double = slider.sliderVoteAverage ?: 0.5
|
||||||
|
input.progress = (avg * 100).toInt()
|
||||||
|
var onClickListener: OnClickListener? = null
|
||||||
|
|
||||||
|
if (slider.viewerVote == null && slider.viewerCanVote == true) {
|
||||||
|
input.isEnabled = true
|
||||||
|
input.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
|
||||||
|
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||||
|
sliderValue = progress / 100.0
|
||||||
|
tv.text = percentage.format(sliderValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||||
|
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||||
|
})
|
||||||
|
onClickListener = OnClickListener { _, _ -> storiesViewModel.answerSlider(sliderValue) }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
input.isEnabled = false
|
||||||
|
tv.text = getString(R.string.slider_answer, percentage.format(slider.viewerVote))
|
||||||
|
}
|
||||||
|
sliderView.addView(input)
|
||||||
|
sliderView.addView(tv)
|
||||||
|
val builder = AlertDialog.Builder(context)
|
||||||
|
.setTitle(if (slider.question.isNullOrEmpty()) slider.emoji else slider.question)
|
||||||
|
.setMessage(
|
||||||
|
resources.getQuantityString(R.plurals.slider_info,
|
||||||
|
slider.sliderVoteCount ?: 0,
|
||||||
|
slider.sliderVoteCount ?: 0,
|
||||||
|
percentage.format(avg)))
|
||||||
|
.setView(sliderView)
|
||||||
|
.setPositiveButton(R.string.ok, onClickListener)
|
||||||
|
|
||||||
|
builder.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createReplyDialog(question: String?) {
|
||||||
|
val context = context ?: return
|
||||||
|
val input = TextInputEditText(context)
|
||||||
|
input.setHint(R.string.reply_hint)
|
||||||
|
val builder = AlertDialog.Builder(context)
|
||||||
|
.setTitle(question ?: context.getString(R.string.reply_story))
|
||||||
|
.setView(input)
|
||||||
|
val onClickListener = OnClickListener{ _, _ ->
|
||||||
|
val result =
|
||||||
|
if (question != null) storiesViewModel.answerQuestion(input.text.toString())
|
||||||
|
else storiesViewModel.reply(input.text.toString())
|
||||||
|
if (result == null) {
|
||||||
|
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
else result.observe(viewLifecycleOwner, {
|
||||||
|
when (it.status) {
|
||||||
|
Resource.Status.SUCCESS -> {
|
||||||
|
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
Resource.Status.ERROR -> {
|
||||||
|
Toast.makeText(context, "Error: " + it.message, Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
Resource.Status.LOADING -> {
|
||||||
|
Toast.makeText(context, R.string.sending, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
builder.setPositiveButton(R.string.confirm, onClickListener)
|
||||||
|
builder.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shareStoryViaDm() {
|
||||||
|
val actionGlobalUserSearch = UserSearchFragmentDirections.actionGlobalUserSearch().apply {
|
||||||
|
title = getString(R.string.share)
|
||||||
|
setActionLabel(getString(R.string.send))
|
||||||
|
showGroups = true
|
||||||
|
multiple = true
|
||||||
|
setSearchMode(UserSearchFragment.SearchMode.RAVEN)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val navController = NavHostFragment.findNavController(this@StoryViewerFragment)
|
||||||
|
navController.navigate(actionGlobalUserSearch)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "shareStoryViaDm: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showStickerMenu() {
|
||||||
|
val data = storiesViewModel.getOptions().value
|
||||||
|
if (data == null) return
|
||||||
|
val themeWrapper = ContextThemeWrapper(context, R.style.popupMenuStyle)
|
||||||
|
val popupMenu = PopupMenu(themeWrapper, binding.stickers)
|
||||||
|
val menu = popupMenu.menu
|
||||||
|
data.first.map {
|
||||||
|
if (it.second != 0) menu.add(0, it.first, 0, it.second)
|
||||||
|
if (it.first == R.id.swipeUp) menu.add(0, R.id.swipeUp, 0, data.second)
|
||||||
|
if (it.first == R.id.spotify) menu.add(0, R.id.spotify, 0, data.third)
|
||||||
|
}
|
||||||
|
popupMenu.setOnMenuItemClickListener { item: MenuItem ->
|
||||||
|
val itemId = item.itemId
|
||||||
|
if (itemId == R.id.spotify) openExternalLink(storiesViewModel.getAppAttribution())
|
||||||
|
else if (itemId == R.id.swipeUp) openExternalLink(storiesViewModel.getSwipeUp())
|
||||||
|
else if (itemId == R.id.mentions) createMentionDialog()
|
||||||
|
else if (itemId == R.id.slider) createSliderDialog()
|
||||||
|
else if (itemId == R.id.question) {
|
||||||
|
val question = storiesViewModel.getQuestion().value
|
||||||
|
if (question != null) createReplyDialog(question.question)
|
||||||
|
}
|
||||||
|
else if (itemId == R.id.quiz) {
|
||||||
|
val quiz = storiesViewModel.getQuiz().value
|
||||||
|
if (quiz != null) createChoiceDialog(
|
||||||
|
quiz.question,
|
||||||
|
quiz.tallies,
|
||||||
|
{ _, w -> storiesViewModel.answerQuiz(w) },
|
||||||
|
quiz.viewerAnswer,
|
||||||
|
quiz.correctAnswer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else if (itemId == R.id.poll) {
|
||||||
|
val poll = storiesViewModel.getPoll().value
|
||||||
|
if (poll != null) createChoiceDialog(
|
||||||
|
poll.question,
|
||||||
|
poll.tallies,
|
||||||
|
{ _, w -> storiesViewModel.answerPoll(w) },
|
||||||
|
poll.viewerVote,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else if (itemId == R.id.viewStoryPost) {
|
||||||
|
storiesViewModel.getLinkedPost().observe(viewLifecycleOwner, {
|
||||||
|
if (it == null) Toast.makeText(context, "Error: LiveData is null", Toast.LENGTH_SHORT).show()
|
||||||
|
else when (it.status) {
|
||||||
|
Resource.Status.SUCCESS -> {
|
||||||
|
if (it.data != null) {
|
||||||
|
val actionBar = fragmentActivity.supportActionBar
|
||||||
|
if (actionBar != null) {
|
||||||
|
actionBar.title = null
|
||||||
|
actionBar.subtitle = null
|
||||||
|
}
|
||||||
|
val navController =
|
||||||
|
NavHostFragment.findNavController(this@StoryViewerFragment)
|
||||||
|
val bundle = Bundle()
|
||||||
|
bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, it.data)
|
||||||
|
try {
|
||||||
|
navController.navigate(R.id.action_global_post_view, bundle)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "openPostDialog: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Resource.Status.ERROR -> {
|
||||||
|
Toast.makeText(context, "Error: " + it.message, Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
Resource.Status.LOADING -> {
|
||||||
|
Toast.makeText(context, R.string.opening_post, Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
popupMenu.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openExternalLink(url: String?) {
|
||||||
|
val context = context ?: return
|
||||||
|
if (url == null) return
|
||||||
|
AlertDialog.Builder(context)
|
||||||
|
.setTitle(R.string.swipe_up_confirmation)
|
||||||
|
.setMessage(url).setPositiveButton(R.string.yes, { _, _ -> Utils.openURL(context, url) })
|
||||||
|
.setNegativeButton(R.string.no, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
@ -199,7 +199,7 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
private val onProfilePicClickListener = View.OnClickListener {
|
private val onProfilePicClickListener = View.OnClickListener {
|
||||||
val hasStories = viewModel.userStories.value?.data?.isNotEmpty() ?: false
|
val hasStories = viewModel.userStories.value?.data != null
|
||||||
if (!hasStories) {
|
if (!hasStories) {
|
||||||
showProfilePicDialog()
|
showProfilePicDialog()
|
||||||
return@OnClickListener
|
return@OnClickListener
|
||||||
@ -514,7 +514,7 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall
|
|||||||
highlightsAdapter?.submitList(it.data)
|
highlightsAdapter?.submitList(it.data)
|
||||||
}
|
}
|
||||||
viewModel.userStories.observe(viewLifecycleOwner) {
|
viewModel.userStories.observe(viewLifecycleOwner) {
|
||||||
binding.header.mainProfileImage.setStoriesBorder(if (it.data.isNullOrEmpty()) 0 else 1)
|
binding.header.mainProfileImage.setStoriesBorder(if (it.data == null) 0 else 1)
|
||||||
}
|
}
|
||||||
viewModel.eventLiveData.observe(viewLifecycleOwner) {
|
viewModel.eventLiveData.observe(viewLifecycleOwner) {
|
||||||
val event = it?.getContentIfNotHandled() ?: return@observe
|
val event = it?.getContentIfNotHandled() ?: return@observe
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package awais.instagrabber.models.enums
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
enum class StoryPaginationType : Serializable {
|
||||||
|
FORWARD, BACKWARD, DO_NOTHING, ERROR
|
||||||
|
}
|
@ -34,7 +34,7 @@ interface StoriesService {
|
|||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST("/api/v1/media/{storyId}/{stickerId}/{action}/")
|
@POST("/api/v1/media/{storyId}/{stickerId}/{action}/")
|
||||||
suspend fun respondToSticker(
|
suspend fun respondToSticker(
|
||||||
@Path("storyId") storyId: String,
|
@Path("storyId") storyId: Long,
|
||||||
@Path("stickerId") stickerId: Long,
|
@Path("stickerId") stickerId: Long,
|
||||||
@Path("action") action: String, // story_poll_vote, story_question_response, story_slider_vote, story_quiz_answer
|
@Path("action") action: String, // story_poll_vote, story_question_response, story_slider_vote, story_quiz_answer
|
||||||
@FieldMap form: Map<String, String>,
|
@FieldMap form: Map<String, String>,
|
||||||
|
@ -10,8 +10,8 @@ import java.io.Serializable
|
|||||||
|
|
||||||
data class StoryMedia(
|
data class StoryMedia(
|
||||||
// inherited from Media
|
// inherited from Media
|
||||||
val pk: String? = null,
|
val pk: Long = -1,
|
||||||
val id: String? = null,
|
val id: String = "",
|
||||||
val takenAt: Long = -1,
|
val takenAt: Long = -1,
|
||||||
val user: User? = null,
|
val user: User? = null,
|
||||||
val canReshare: Boolean = false,
|
val canReshare: Boolean = false,
|
||||||
|
@ -16,7 +16,6 @@ import awais.instagrabber.repositories.responses.User
|
|||||||
import awais.instagrabber.repositories.responses.UserProfileContextLink
|
import awais.instagrabber.repositories.responses.UserProfileContextLink
|
||||||
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
|
||||||
import awais.instagrabber.repositories.responses.stories.Story
|
import awais.instagrabber.repositories.responses.stories.Story
|
||||||
import awais.instagrabber.repositories.responses.stories.StoryMedia
|
|
||||||
import awais.instagrabber.utils.ControlledRunner
|
import awais.instagrabber.utils.ControlledRunner
|
||||||
import awais.instagrabber.utils.Event
|
import awais.instagrabber.utils.Event
|
||||||
import awais.instagrabber.utils.SingleRunner
|
import awais.instagrabber.utils.SingleRunner
|
||||||
@ -153,9 +152,9 @@ class ProfileFragmentViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val storyFetchControlledRunner = ControlledRunner<List<StoryMedia>?>()
|
private val storyFetchControlledRunner = ControlledRunner<Story?>()
|
||||||
val userStories: LiveData<Resource<List<StoryMedia>?>> = currentUserProfileActionLiveData.switchMap { currentUserAndProfilePair ->
|
val userStories: LiveData<Resource<Story?>> = currentUserProfileActionLiveData.switchMap { currentUserAndProfilePair ->
|
||||||
liveData<Resource<List<StoryMedia>?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
|
liveData<Resource<Story?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
|
||||||
val (currentUserResource, profileResource, action) = currentUserAndProfilePair
|
val (currentUserResource, profileResource, action) = currentUserAndProfilePair
|
||||||
if (action != INIT && action != REFRESH) {
|
if (action != INIT && action != REFRESH) {
|
||||||
return@liveData
|
return@liveData
|
||||||
@ -231,7 +230,7 @@ class ProfileFragmentViewModel(
|
|||||||
return graphQLRepository.fetchUser(stateUsername)
|
return graphQLRepository.fetchUser(stateUsername)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchUserStory(fetchedUser: User): List<StoryMedia> = storiesRepository.getStories(
|
private suspend fun fetchUserStory(fetchedUser: User): Story? = storiesRepository.getStories(
|
||||||
StoryViewerOptions.forUser(fetchedUser.pk, fetchedUser.fullName)
|
StoryViewerOptions.forUser(fetchedUser.pk, fetchedUser.fullName)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -0,0 +1,458 @@
|
|||||||
|
package awais.instagrabber.viewmodels
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import awais.instagrabber.R
|
||||||
|
import awais.instagrabber.managers.DirectMessagesManager
|
||||||
|
import awais.instagrabber.models.enums.FavoriteType
|
||||||
|
import awais.instagrabber.models.enums.MediaItemType
|
||||||
|
import awais.instagrabber.models.enums.StoryPaginationType
|
||||||
|
import awais.instagrabber.models.Resource
|
||||||
|
import awais.instagrabber.models.Resource.Companion.error
|
||||||
|
import awais.instagrabber.models.Resource.Companion.loading
|
||||||
|
import awais.instagrabber.models.Resource.Companion.success
|
||||||
|
import awais.instagrabber.models.enums.BroadcastItemType
|
||||||
|
import awais.instagrabber.repositories.requests.StoryViewerOptions
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
|
||||||
|
import awais.instagrabber.repositories.responses.stories.*
|
||||||
|
import awais.instagrabber.repositories.responses.Media
|
||||||
|
import awais.instagrabber.utils.*
|
||||||
|
import awais.instagrabber.webservices.MediaRepository
|
||||||
|
import awais.instagrabber.webservices.StoriesRepository
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class StoryFragmentViewModel : ViewModel() {
|
||||||
|
// large data
|
||||||
|
private val currentStory = MutableLiveData<Story>()
|
||||||
|
private val currentMedia = MutableLiveData<StoryMedia>()
|
||||||
|
|
||||||
|
// small data
|
||||||
|
private val storyTitle = MutableLiveData<String>()
|
||||||
|
private val date = MutableLiveData<String>()
|
||||||
|
private val type = MutableLiveData<MediaItemType>()
|
||||||
|
private val poll = MutableLiveData<PollSticker>()
|
||||||
|
private val quiz = MutableLiveData<QuizSticker>()
|
||||||
|
private val question = MutableLiveData<QuestionSticker>()
|
||||||
|
private val slider = MutableLiveData<SliderSticker>()
|
||||||
|
private val swipeUp = MutableLiveData<String>()
|
||||||
|
private val linkedPost = MutableLiveData<String>()
|
||||||
|
private val appAttribution = MutableLiveData<StoryAppAttribution>()
|
||||||
|
private val reelMentions = MutableLiveData<List<Triple<String, String?, FavoriteType>>>()
|
||||||
|
|
||||||
|
// process
|
||||||
|
private val currentIndex = MutableLiveData<Int>()
|
||||||
|
private val pagination = MutableLiveData(StoryPaginationType.DO_NOTHING)
|
||||||
|
private val options = MutableLiveData<Triple<List<Pair<Int, Int>>, String?, String?>>()
|
||||||
|
private val seen = MutableLiveData<Triple<String, Long, Long>>()
|
||||||
|
|
||||||
|
// utils
|
||||||
|
private var messageManager: DirectMessagesManager? = null
|
||||||
|
private val cookie = Utils.settingsHelper.getString(Constants.COOKIE)
|
||||||
|
private val deviceId = Utils.settingsHelper.getString(Constants.DEVICE_UUID)
|
||||||
|
private val csrfToken = getCsrfTokenFromCookie(cookie)
|
||||||
|
private val userId = getUserIdFromCookie(cookie)
|
||||||
|
private val storiesRepository: StoriesRepository by lazy { StoriesRepository.getInstance() }
|
||||||
|
private val mediaRepository: MediaRepository by lazy { MediaRepository.getInstance() }
|
||||||
|
|
||||||
|
/* set functions */
|
||||||
|
|
||||||
|
fun setStory(story: Story) {
|
||||||
|
if (story.items == null || story.items.size == 0) {
|
||||||
|
pagination.postValue(StoryPaginationType.ERROR)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentStory.postValue(story)
|
||||||
|
storyTitle.postValue(story.title ?: story.user?.username)
|
||||||
|
if (story.broadcast != null) {
|
||||||
|
date.postValue(story.dateTime)
|
||||||
|
type.postValue(MediaItemType.MEDIA_TYPE_LIVE)
|
||||||
|
pagination.postValue(StoryPaginationType.DO_NOTHING)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMedia(index: Int) {
|
||||||
|
if (currentStory.value?.items == null) return
|
||||||
|
if (index < 0 || index >= currentStory.value!!.items!!.size) {
|
||||||
|
pagination.postValue(if (index < 0) StoryPaginationType.BACKWARD else StoryPaginationType.FORWARD)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentIndex.postValue(index)
|
||||||
|
val story: Story? = currentStory.value
|
||||||
|
val media = story!!.items!!.get(index)
|
||||||
|
currentMedia.postValue(media)
|
||||||
|
date.postValue(media.date)
|
||||||
|
type.postValue(media.type)
|
||||||
|
initStickers(media)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSingleMedia(media: StoryMedia) {
|
||||||
|
currentStory.postValue(null)
|
||||||
|
currentIndex.postValue(0)
|
||||||
|
currentMedia.postValue(media)
|
||||||
|
date.postValue(media.date)
|
||||||
|
type.postValue(media.type)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initStickers(media: StoryMedia) {
|
||||||
|
val builder = ImmutableList.builder<Pair<Int, Int>>()
|
||||||
|
var linkedText: String? = null
|
||||||
|
var appText: String? = null
|
||||||
|
if (setMentions(media)) builder.add(Pair(R.id.mentions, R.string.story_mentions))
|
||||||
|
if (setQuiz(media)) builder.add(Pair(R.id.quiz, R.string.story_quiz))
|
||||||
|
if (setQuestion(media)) builder.add(Pair(R.id.question, R.string.story_question))
|
||||||
|
if (setPoll(media)) builder.add(Pair(R.id.poll, R.string.story_poll))
|
||||||
|
if (setSlider(media)) builder.add(Pair(R.id.slider, R.string.story_slider))
|
||||||
|
if (setLinkedPost(media)) builder.add(Pair(R.id.viewStoryPost, R.string.view_post))
|
||||||
|
if (setStoryCta(media)) {
|
||||||
|
linkedText = media.linkText
|
||||||
|
builder.add(Pair(R.id.swipeUp, 0))
|
||||||
|
}
|
||||||
|
if (setStoryAppAttribution(media)) {
|
||||||
|
appText = media.storyAppAttribution!!.appActionText
|
||||||
|
builder.add(Pair(R.id.spotify, 0))
|
||||||
|
}
|
||||||
|
options.postValue(Triple(builder.build(), linkedText, appText))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setMentions(media: StoryMedia): Boolean {
|
||||||
|
val mentions: MutableList<Triple<String, String?, FavoriteType>> = mutableListOf()
|
||||||
|
if (media.reelMentions != null)
|
||||||
|
mentions.addAll(media.reelMentions.map{
|
||||||
|
Triple("@" + it.user?.username, it.user?.username, FavoriteType.USER)
|
||||||
|
})
|
||||||
|
if (media.storyHashtags != null)
|
||||||
|
mentions.addAll(media.storyHashtags.map{
|
||||||
|
Triple("#" + it.hashtag?.name, it.hashtag?.name, FavoriteType.HASHTAG)
|
||||||
|
})
|
||||||
|
if (media.storyLocations != null)
|
||||||
|
mentions.addAll(media.storyLocations.map{
|
||||||
|
Triple(it.location?.name ?: "", it.location?.pk?.toString(10), FavoriteType.LOCATION)
|
||||||
|
})
|
||||||
|
reelMentions.postValue(mentions.filterNot { it.second.isNullOrEmpty() } .distinct())
|
||||||
|
return !mentions.isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setPoll(media: StoryMedia): Boolean {
|
||||||
|
poll.postValue(media.storyPolls?.get(0)?.pollSticker ?: return false)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setQuiz(media: StoryMedia): Boolean {
|
||||||
|
quiz.postValue(media.storyQuizs?.get(0)?.quizSticker ?: return false)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setQuestion(media: StoryMedia): Boolean {
|
||||||
|
val questionSticker = media.storyQuestions?.get(0)?.questionSticker ?: return false
|
||||||
|
if (questionSticker.questionType.equals("music")) return false
|
||||||
|
question.postValue(questionSticker)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setSlider(media: StoryMedia): Boolean {
|
||||||
|
slider.postValue(media.storySliders?.get(0)?.sliderSticker ?: return false)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setLinkedPost(media: StoryMedia): Boolean {
|
||||||
|
linkedPost.postValue(media.storyFeedMedia?.get(0)?.mediaId ?: return false)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setStoryCta(media: StoryMedia): Boolean {
|
||||||
|
val webUri = media.storyCta?.get(0)?.links?.get(0)?.webUri ?: return false
|
||||||
|
val parsedUri = Uri.parse(webUri)
|
||||||
|
val cleanUri = if (parsedUri.host.equals("l.instagram.com")) parsedUri.getQueryParameter("u")
|
||||||
|
else null
|
||||||
|
swipeUp.postValue(if (cleanUri != null && Uri.parse(cleanUri).scheme?.startsWith("http") == true) cleanUri
|
||||||
|
else webUri)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setStoryAppAttribution(media: StoryMedia): Boolean {
|
||||||
|
appAttribution.postValue(media.storyAppAttribution ?: return false)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/* get functions */
|
||||||
|
|
||||||
|
fun getCurrentStory(): LiveData<Story> {
|
||||||
|
return currentStory
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentIndex(): LiveData<Int> {
|
||||||
|
return currentIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentMedia(): LiveData<StoryMedia> {
|
||||||
|
return currentMedia
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPagination(): LiveData<StoryPaginationType> {
|
||||||
|
return pagination
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDate(): LiveData<String> {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTitle(): LiveData<String> {
|
||||||
|
return storyTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getType(): LiveData<MediaItemType> {
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMedia(): LiveData<StoryMedia> {
|
||||||
|
return currentMedia
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMention(index: Int): Triple<String, String?, FavoriteType>? {
|
||||||
|
return reelMentions.value?.get(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMentionTexts(): Array<String> {
|
||||||
|
return reelMentions.value!!.map { it.first } .toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPoll(): LiveData<PollSticker> {
|
||||||
|
return poll
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getQuestion(): LiveData<QuestionSticker> {
|
||||||
|
return question
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getQuiz(): LiveData<QuizSticker> {
|
||||||
|
return quiz
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSlider(): LiveData<SliderSticker> {
|
||||||
|
return slider
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLinkedPost(): LiveData<Resource<Media?>> {
|
||||||
|
val data = MutableLiveData<Resource<Media?>>()
|
||||||
|
data.postValue(loading(null))
|
||||||
|
val postId = linkedPost.value
|
||||||
|
if (postId == null) data.postValue(error("No post ID supplied", null))
|
||||||
|
else viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val media = mediaRepository.fetch(postId.toLong())
|
||||||
|
data.postValue(success(media))
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
data.postValue(error(e.message, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSwipeUp(): String? {
|
||||||
|
return swipeUp.value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAppAttribution(): String? {
|
||||||
|
return appAttribution.value?.url
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOptions(): LiveData<Triple<List<Pair<Int, Int>>, String?, String?>> {
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
/* action functions */
|
||||||
|
|
||||||
|
fun answerPoll(w: Int): LiveData<Resource<Any?>> {
|
||||||
|
val data = MutableLiveData<Resource<Any?>>()
|
||||||
|
data.postValue(loading(null))
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val oldPoll: PollSticker = poll.value!!
|
||||||
|
val response = storiesRepository.respondToPoll(
|
||||||
|
csrfToken!!,
|
||||||
|
userId,
|
||||||
|
deviceId,
|
||||||
|
currentMedia.value!!.pk,
|
||||||
|
oldPoll.pollId,
|
||||||
|
w
|
||||||
|
)
|
||||||
|
if (!"ok".equals(response.status))
|
||||||
|
throw Exception("Instagram returned status \"" + response.status + "\"")
|
||||||
|
val tally = oldPoll.tallies.get(w)
|
||||||
|
val newTally = tally.copy(count = tally.count + 1)
|
||||||
|
val newTallies = oldPoll.tallies.toMutableList()
|
||||||
|
newTallies.set(w, newTally)
|
||||||
|
poll.postValue(oldPoll.copy(viewerVote = w, tallies = newTallies.toList()))
|
||||||
|
data.postValue(success(null))
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
data.postValue(error(e.message, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
fun answerQuiz(w: Int): LiveData<Resource<Any?>> {
|
||||||
|
val data = MutableLiveData<Resource<Any?>>()
|
||||||
|
data.postValue(loading(null))
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val oldQuiz = quiz.value!!
|
||||||
|
val response = storiesRepository.respondToQuiz(
|
||||||
|
csrfToken!!,
|
||||||
|
userId,
|
||||||
|
deviceId,
|
||||||
|
currentMedia.value!!.pk,
|
||||||
|
oldQuiz.quizId,
|
||||||
|
w
|
||||||
|
)
|
||||||
|
if (!"ok".equals(response.status))
|
||||||
|
throw Exception("Instagram returned status \"" + response.status + "\"")
|
||||||
|
val tally = oldQuiz.tallies.get(w)
|
||||||
|
val newTally = tally.copy(count = tally.count + 1)
|
||||||
|
val newTallies = oldQuiz.tallies.toMutableList()
|
||||||
|
newTallies.set(w, newTally)
|
||||||
|
quiz.postValue(oldQuiz.copy(viewerAnswer = w, tallies = newTallies.toList()))
|
||||||
|
data.postValue(success(null))
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
data.postValue(error(e.message, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
fun answerQuestion(a: String): LiveData<Resource<Any?>> {
|
||||||
|
val data = MutableLiveData<Resource<Any?>>()
|
||||||
|
data.postValue(loading(null))
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val response = storiesRepository.respondToQuestion(
|
||||||
|
csrfToken!!,
|
||||||
|
userId,
|
||||||
|
deviceId,
|
||||||
|
currentMedia.value!!.pk,
|
||||||
|
question.value!!.questionId,
|
||||||
|
a
|
||||||
|
)
|
||||||
|
if (!"ok".equals(response.status))
|
||||||
|
throw Exception("Instagram returned status \"" + response.status + "\"")
|
||||||
|
data.postValue(success(null))
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
data.postValue(error(e.message, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
fun answerSlider(a: Double): LiveData<Resource<Any?>> {
|
||||||
|
val data = MutableLiveData<Resource<Any?>>()
|
||||||
|
data.postValue(loading(null))
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val oldSlider = slider.value!!
|
||||||
|
val response = storiesRepository.respondToSlider(
|
||||||
|
csrfToken!!,
|
||||||
|
userId,
|
||||||
|
deviceId,
|
||||||
|
currentMedia.value!!.pk,
|
||||||
|
oldSlider.sliderId,
|
||||||
|
a
|
||||||
|
)
|
||||||
|
if (!"ok".equals(response.status))
|
||||||
|
throw Exception("Instagram returned status \"" + response.status + "\"")
|
||||||
|
val newVoteCount = (oldSlider.sliderVoteCount ?: 0) + 1
|
||||||
|
val newAverage = if (oldSlider.sliderVoteAverage == null) a
|
||||||
|
else (oldSlider.sliderVoteAverage * oldSlider.sliderVoteCount!! + a) / newVoteCount
|
||||||
|
slider.postValue(oldSlider.copy(viewerCanVote = false,
|
||||||
|
sliderVoteCount = newVoteCount,
|
||||||
|
viewerVote = a,
|
||||||
|
sliderVoteAverage = newAverage))
|
||||||
|
data.postValue(success(null))
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
data.postValue(error(e.message, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reply(a: String): LiveData<Resource<Any?>>? {
|
||||||
|
if (messageManager == null) {
|
||||||
|
messageManager = DirectMessagesManager
|
||||||
|
}
|
||||||
|
return messageManager?.replyToStory(
|
||||||
|
currentStory.value?.user?.pk,
|
||||||
|
currentStory.value?.id,
|
||||||
|
currentMedia.value?.id,
|
||||||
|
a,
|
||||||
|
viewModelScope
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shareDm(result: RankedRecipient) {
|
||||||
|
if (messageManager == null) {
|
||||||
|
messageManager = DirectMessagesManager
|
||||||
|
}
|
||||||
|
val mediaId = currentMedia.value?.id ?: return
|
||||||
|
val reelId = currentStory.value?.id ?: return
|
||||||
|
messageManager?.sendMedia(result, mediaId, reelId, BroadcastItemType.STORY, viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shareDm(recipients: Set<RankedRecipient>) {
|
||||||
|
if (messageManager == null) {
|
||||||
|
messageManager = DirectMessagesManager
|
||||||
|
}
|
||||||
|
val mediaId = currentMedia.value?.id ?: return
|
||||||
|
val reelId = currentStory.value?.id ?: return
|
||||||
|
messageManager?.sendMedia(recipients, mediaId, reelId, BroadcastItemType.STORY, viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun paginate(backward: Boolean) {
|
||||||
|
var index = currentIndex.value!!
|
||||||
|
index = if (backward) index - 1 else index + 1
|
||||||
|
if (index < 0 || index >= currentStory.value!!.items!!.size) skip(backward)
|
||||||
|
setMedia(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun skip(backward: Boolean) {
|
||||||
|
pagination.postValue(if (backward) StoryPaginationType.BACKWARD else StoryPaginationType.FORWARD)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchStory(fetchOptions: StoryViewerOptions?): LiveData<Resource<Any?>> {
|
||||||
|
val data = MutableLiveData<Resource<Any?>>()
|
||||||
|
data.postValue(loading(null))
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val story = storiesRepository.getStories(fetchOptions!!)
|
||||||
|
setStory(story!!)
|
||||||
|
data.postValue(success(null))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
data.postValue(error(e.message, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchSingleMedia(mediaId: Long): LiveData<Resource<Any?>> {
|
||||||
|
val data = MutableLiveData<Resource<Any?>>()
|
||||||
|
data.postValue(loading(null))
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val storyMedia = storiesRepository.fetch(mediaId)
|
||||||
|
setSingleMedia(storyMedia!!)
|
||||||
|
data.postValue(success(null))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
data.postValue(error(e.message, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ import awais.instagrabber.repositories.responses.stories.ArchiveResponse
|
|||||||
import awais.instagrabber.repositories.responses.stories.Story
|
import awais.instagrabber.repositories.responses.stories.Story
|
||||||
import awais.instagrabber.repositories.responses.stories.StoryMedia
|
import awais.instagrabber.repositories.responses.stories.StoryMedia
|
||||||
import awais.instagrabber.repositories.responses.stories.StoryStickerResponse
|
import awais.instagrabber.repositories.responses.stories.StoryStickerResponse
|
||||||
import awais.instagrabber.utils.TextUtils.isEmpty
|
|
||||||
import awais.instagrabber.utils.Utils
|
import awais.instagrabber.utils.Utils
|
||||||
import awais.instagrabber.webservices.RetrofitFactory.retrofit
|
import awais.instagrabber.webservices.RetrofitFactory.retrofit
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@ -60,35 +59,34 @@ open class StoriesRepository(private val service: StoriesService) {
|
|||||||
"is_in_archive_home" to "true",
|
"is_in_archive_home" to "true",
|
||||||
"include_cover" to "1",
|
"include_cover" to "1",
|
||||||
)
|
)
|
||||||
if (!isEmpty(maxId)) {
|
if (!maxId.isNullOrEmpty()) {
|
||||||
form["max_id"] = maxId // NOT TESTED
|
form["max_id"] = maxId // NOT TESTED
|
||||||
}
|
}
|
||||||
return service.fetchArchive(form)
|
return service.fetchArchive(form)
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun getStories(options: StoryViewerOptions): List<StoryMedia> {
|
open suspend fun getStories(options: StoryViewerOptions): Story? {
|
||||||
return when (options.type) {
|
return when (options.type) {
|
||||||
StoryViewerOptions.Type.HIGHLIGHT,
|
StoryViewerOptions.Type.HIGHLIGHT,
|
||||||
StoryViewerOptions.Type.STORY_ARCHIVE
|
StoryViewerOptions.Type.STORY_ARCHIVE
|
||||||
-> {
|
-> {
|
||||||
val response = service.getReelsMedia(options.name)
|
val response = service.getReelsMedia(options.name)
|
||||||
val story: Story? = response.reels?.get(options.name)
|
response.reels?.get(options.name)
|
||||||
story?.items ?: emptyList()
|
|
||||||
}
|
}
|
||||||
StoryViewerOptions.Type.USER -> {
|
StoryViewerOptions.Type.USER -> {
|
||||||
val response = service.getUserStories(options.id.toString())
|
val response = service.getUserStories(options.id.toString())
|
||||||
response.reel?.items ?: emptyList()
|
response.reel
|
||||||
}
|
}
|
||||||
// should not reach beyond this point
|
// should not reach beyond this point
|
||||||
StoryViewerOptions.Type.LOCATION -> {
|
StoryViewerOptions.Type.LOCATION -> {
|
||||||
val response = service.getStories("locations", options.id.toString())
|
val response = service.getStories("locations", options.id.toString())
|
||||||
response.story?.items ?: emptyList()
|
response.story
|
||||||
}
|
}
|
||||||
StoryViewerOptions.Type.HASHTAG -> {
|
StoryViewerOptions.Type.HASHTAG -> {
|
||||||
val response = service.getStories("tags", options.name)
|
val response = service.getStories("tags", options.name)
|
||||||
response.story?.items ?: emptyList()
|
response.story
|
||||||
}
|
}
|
||||||
else -> emptyList()
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +94,7 @@ open class StoriesRepository(private val service: StoriesService) {
|
|||||||
csrfToken: String,
|
csrfToken: String,
|
||||||
userId: Long,
|
userId: Long,
|
||||||
deviceUuid: String,
|
deviceUuid: String,
|
||||||
storyId: String,
|
storyId: Long,
|
||||||
stickerId: Long,
|
stickerId: Long,
|
||||||
action: String,
|
action: String,
|
||||||
arg1: String,
|
arg1: String,
|
||||||
@ -119,7 +117,7 @@ open class StoriesRepository(private val service: StoriesService) {
|
|||||||
csrfToken: String,
|
csrfToken: String,
|
||||||
userId: Long,
|
userId: Long,
|
||||||
deviceUuid: String,
|
deviceUuid: String,
|
||||||
storyId: String,
|
storyId: Long,
|
||||||
stickerId: Long,
|
stickerId: Long,
|
||||||
answer: String,
|
answer: String,
|
||||||
): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_question_response", "response", answer)
|
): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_question_response", "response", answer)
|
||||||
@ -128,7 +126,7 @@ open class StoriesRepository(private val service: StoriesService) {
|
|||||||
csrfToken: String,
|
csrfToken: String,
|
||||||
userId: Long,
|
userId: Long,
|
||||||
deviceUuid: String,
|
deviceUuid: String,
|
||||||
storyId: String,
|
storyId: Long,
|
||||||
stickerId: Long,
|
stickerId: Long,
|
||||||
answer: Int,
|
answer: Int,
|
||||||
): StoryStickerResponse {
|
): StoryStickerResponse {
|
||||||
@ -139,7 +137,7 @@ open class StoriesRepository(private val service: StoriesService) {
|
|||||||
csrfToken: String,
|
csrfToken: String,
|
||||||
userId: Long,
|
userId: Long,
|
||||||
deviceUuid: String,
|
deviceUuid: String,
|
||||||
storyId: String,
|
storyId: Long,
|
||||||
stickerId: Long,
|
stickerId: Long,
|
||||||
answer: Int,
|
answer: Int,
|
||||||
): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_poll_vote", "vote", answer.toString())
|
): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_poll_vote", "vote", answer.toString())
|
||||||
@ -148,7 +146,7 @@ open class StoriesRepository(private val service: StoriesService) {
|
|||||||
csrfToken: String,
|
csrfToken: String,
|
||||||
userId: Long,
|
userId: Long,
|
||||||
deviceUuid: String,
|
deviceUuid: String,
|
||||||
storyId: String,
|
storyId: Long,
|
||||||
stickerId: Long,
|
stickerId: Long,
|
||||||
answer: Double,
|
answer: Double,
|
||||||
): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_slider_vote", "vote", answer.toString())
|
): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_slider_vote", "vote", answer.toString())
|
||||||
|
10
app/src/main/res/drawable/ic_story_sticker.xml
Normal file
10
app/src/main/res/drawable/ic_story_sticker.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M22,10l-6,-6L4,4c-1.1,0 -2,0.9 -2,2v12.01c0,1.1 0.9,1.99 2,1.99l16,-0.01c1.1,0 2,-0.89 2,-1.99v-8zM15,5.5l5.5,5.5L15,11L15,5.5z"/>
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/ic_story_viewer_list.xml
Normal file
10
app/src/main/res/drawable/ic_story_viewer_list.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M7,19h10L17,4L7,4v15zM2,17h4L6,6L2,6v11zM18,6v11h4L22,6h-4z"/>
|
||||||
|
</vector>
|
@ -9,7 +9,7 @@
|
|||||||
android:id="@+id/story_container"
|
android:id="@+id/story_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toTopOf="@id/postActions"
|
app:layout_constraintBottom_toTopOf="@id/buttons_barrier"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
@ -38,132 +38,150 @@
|
|||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/postActions"
|
android:id="@+id/storiesList"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom"
|
android:clipToPadding="false"
|
||||||
app:layout_constraintTop_toBottomOf="@id/story_container"
|
app:layout_constraintBottom_toTopOf="@id/buttons_barrier"
|
||||||
app:layout_constraintBottom_toTopOf="@id/storiesList"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
android:background="#0000">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatButton
|
<androidx.constraintlayout.widget.Barrier
|
||||||
android:id="@+id/viewStoryPost"
|
android:id="@+id/buttons_barrier"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
app:barrierAllowsGoneWidgets="true"
|
||||||
android:text="@string/view_story_post"
|
app:barrierDirection="bottom"
|
||||||
android:textColor="@color/btn_green_text_color"
|
app:layout_constraintTop_toBottomOf="@id/story_container"
|
||||||
android:visibility="gone"
|
app:layout_constraintBottom_toTopOf="@id/btnBackward"
|
||||||
app:backgroundTint="@color/btn_green_background" />
|
app:constraint_referenced_ids="story_container" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatButton
|
|
||||||
android:id="@+id/poll"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="@string/vote_story_poll"
|
|
||||||
android:textColor="@color/btn_blue_text_color"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:backgroundTint="@color/btn_blue_background" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatButton
|
|
||||||
android:id="@+id/answer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="@string/respond_story"
|
|
||||||
android:textColor="@color/btn_blue_text_color"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:backgroundTint="@color/btn_blue_background" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatButton
|
|
||||||
android:id="@+id/quiz"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="@string/story_quiz"
|
|
||||||
android:textColor="@color/btn_blue_text_color"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:backgroundTint="@color/btn_blue_background" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatButton
|
|
||||||
android:id="@+id/slider"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="@string/story_slider"
|
|
||||||
android:textColor="@color/btn_blue_text_color"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:backgroundTint="@color/btn_blue_background" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatButton
|
|
||||||
android:id="@+id/swipeUp"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="SAMPLE TEXT"
|
|
||||||
android:textColor="@color/btn_blue_text_color"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:backgroundTint="@color/btn_blue_background" />
|
|
||||||
<androidx.appcompat.widget.AppCompatButton
|
|
||||||
android:id="@+id/mention"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="@string/story_mentions"
|
|
||||||
android:textColor="@color/btn_orange_text_color"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:backgroundTint="@color/btn_orange_background" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatButton
|
|
||||||
android:id="@+id/spotify"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="@string/spotify"
|
|
||||||
android:textColor="@color/btn_green_text_color"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:backgroundTint="@color/btn_green_background" />
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnBackward"
|
android:id="@+id/btnBackward"
|
||||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
android:layout_width="40dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="@dimen/story_item_height"
|
android:layout_height="48dp"
|
||||||
|
android:enabled="false"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
app:icon="@drawable/exo_ic_skip_previous"
|
app:icon="@drawable/exo_ic_skip_previous"
|
||||||
app:iconGravity="textStart"
|
app:iconGravity="textStart"
|
||||||
app:iconPadding="0dp"
|
app:iconPadding="0dp"
|
||||||
|
app:iconSize="24dp"
|
||||||
|
app:iconTint="@color/ic_read_button_tint"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/storiesList"
|
app:layout_constraintEnd_toStartOf="@id/btnShare"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@id/storiesList" />
|
app:layout_constraintTop_toBottomOf="@id/buttons_barrier"
|
||||||
|
app:rippleColor="@color/grey_300" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/storiesList"
|
android:id="@+id/btnShare"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="48dp"
|
||||||
android:clipToPadding="false"
|
android:enabled="false"
|
||||||
app:layout_constraintTop_toBottomOf="@id/postActions"
|
android:visibility="visible"
|
||||||
|
app:icon="?attr/actionModeShareDrawable"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconPadding="0dp"
|
||||||
|
app:iconSize="24dp"
|
||||||
|
app:iconTint="@color/ic_read_button_tint"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/stickers"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/btnBackward"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/buttons_barrier"
|
||||||
|
app:rippleColor="@color/grey_300" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/stickers"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:enabled="false"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:icon="@drawable/ic_story_sticker"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconPadding="0dp"
|
||||||
|
app:iconSize="24dp"
|
||||||
|
app:iconTint="@color/ic_read_button_tint"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/list_toggle"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/btnShare"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/buttons_barrier"
|
||||||
|
app:rippleColor="@color/grey_300" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/list_toggle"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:enabled="false"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:icon="@drawable/ic_story_viewer_list"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconPadding="0dp"
|
||||||
|
app:iconSize="24dp"
|
||||||
|
app:iconTint="@color/ic_read_button_tint"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/btnDownload"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/stickers"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/buttons_barrier"
|
||||||
|
app:rippleColor="@color/grey_300" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnDownload"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:enabled="false"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:icon="@drawable/ic_download"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconPadding="0dp"
|
||||||
|
app:iconSize="24dp"
|
||||||
|
app:iconTint="@color/ic_read_button_tint"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/btnReply"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/list_toggle"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/buttons_barrier"
|
||||||
|
app:rippleColor="@color/grey_300" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnReply"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
|
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:enabled="false"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:icon="@drawable/ic_round_send_24"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconPadding="0dp"
|
||||||
|
app:iconSize="24dp"
|
||||||
|
app:iconTint="@color/ic_read_button_tint"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/btnForward"
|
app:layout_constraintEnd_toStartOf="@id/btnForward"
|
||||||
app:layout_constraintStart_toEndOf="@id/btnBackward" />
|
app:layout_constraintStart_toEndOf="@id/btnDownload"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/buttons_barrier"
|
||||||
|
app:rippleColor="@color/grey_300" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnForward"
|
android:id="@+id/btnForward"
|
||||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="@dimen/story_item_height"
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:enabled="false"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
app:icon="@drawable/exo_ic_skip_next"
|
app:icon="@drawable/exo_ic_skip_next"
|
||||||
app:iconGravity="textStart"
|
app:iconGravity="textStart"
|
||||||
app:iconPadding="0dp"
|
app:iconPadding="0dp"
|
||||||
|
app:iconSize="24dp"
|
||||||
|
app:iconTint="@color/ic_read_button_tint"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@id/storiesList"
|
app:layout_constraintStart_toEndOf="@id/btnReply"
|
||||||
app:layout_constraintTop_toTopOf="@id/storiesList" />
|
app:layout_constraintTop_toBottomOf="@id/buttons_barrier"
|
||||||
|
app:rippleColor="@color/grey_300" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -2,21 +2,9 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_dms"
|
|
||||||
android:icon="@drawable/ic_round_send_24"
|
|
||||||
android:title="@string/reply_story"
|
|
||||||
android:titleCondensed="@string/reply_story"
|
|
||||||
app:showAsAction="never" />
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_profile"
|
android:id="@+id/action_profile"
|
||||||
android:title="@string/open_profile"
|
android:title="@string/open_profile"
|
||||||
android:titleCondensed="@string/open_profile"
|
android:titleCondensed="@string/open_profile"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
<item
|
|
||||||
android:id="@+id/action_download"
|
|
||||||
android:icon="@drawable/ic_download"
|
|
||||||
android:title="@string/action_download"
|
|
||||||
android:titleCondensed="@string/action_download"
|
|
||||||
app:showAsAction="never" />
|
|
||||||
</menu>
|
</menu>
|
@ -188,7 +188,6 @@
|
|||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/storyViewerFragment"
|
android:id="@+id/storyViewerFragment"
|
||||||
android:name="awais.instagrabber.fragments.StoryViewerFragment"
|
android:name="awais.instagrabber.fragments.StoryViewerFragment"
|
||||||
android:label="StoryViewerFragment"
|
|
||||||
tools:layout="@layout/fragment_story_viewer">
|
tools:layout="@layout/fragment_story_viewer">
|
||||||
<argument
|
<argument
|
||||||
android:name="options"
|
android:name="options"
|
||||||
|
@ -140,7 +140,6 @@
|
|||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/storyViewerFragment"
|
android:id="@+id/storyViewerFragment"
|
||||||
android:name="awais.instagrabber.fragments.StoryViewerFragment"
|
android:name="awais.instagrabber.fragments.StoryViewerFragment"
|
||||||
android:label="StoryViewerFragment"
|
|
||||||
tools:layout="@layout/fragment_story_viewer">
|
tools:layout="@layout/fragment_story_viewer">
|
||||||
<argument
|
<argument
|
||||||
android:name="options"
|
android:name="options"
|
||||||
|
@ -103,7 +103,6 @@
|
|||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/storyViewerFragment"
|
android:id="@+id/storyViewerFragment"
|
||||||
android:name="awais.instagrabber.fragments.StoryViewerFragment"
|
android:name="awais.instagrabber.fragments.StoryViewerFragment"
|
||||||
android:label="StoryViewerFragment"
|
|
||||||
tools:layout="@layout/fragment_story_viewer">
|
tools:layout="@layout/fragment_story_viewer">
|
||||||
<argument
|
<argument
|
||||||
android:name="options"
|
android:name="options"
|
||||||
|
@ -110,7 +110,6 @@
|
|||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/storyViewerFragment"
|
android:id="@+id/storyViewerFragment"
|
||||||
android:name="awais.instagrabber.fragments.StoryViewerFragment"
|
android:name="awais.instagrabber.fragments.StoryViewerFragment"
|
||||||
android:label="StoryViewerFragment"
|
|
||||||
tools:layout="@layout/fragment_story_viewer">
|
tools:layout="@layout/fragment_story_viewer">
|
||||||
<argument
|
<argument
|
||||||
android:name="options"
|
android:name="options"
|
||||||
|
@ -93,7 +93,6 @@
|
|||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/storyViewerFragment"
|
android:id="@+id/storyViewerFragment"
|
||||||
android:name="awais.instagrabber.fragments.StoryViewerFragment"
|
android:name="awais.instagrabber.fragments.StoryViewerFragment"
|
||||||
android:label="StoryViewerFragment"
|
|
||||||
tools:layout="@layout/fragment_story_viewer">
|
tools:layout="@layout/fragment_story_viewer">
|
||||||
<argument
|
<argument
|
||||||
android:name="options"
|
android:name="options"
|
||||||
|
@ -203,7 +203,6 @@
|
|||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/storyViewerFragment"
|
android:id="@+id/storyViewerFragment"
|
||||||
android:name="awais.instagrabber.fragments.StoryViewerFragment"
|
android:name="awais.instagrabber.fragments.StoryViewerFragment"
|
||||||
android:label="StoryViewerFragment"
|
|
||||||
tools:layout="@layout/fragment_story_viewer">
|
tools:layout="@layout/fragment_story_viewer">
|
||||||
|
|
||||||
<argument
|
<argument
|
||||||
|
@ -40,7 +40,6 @@
|
|||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/storyViewerFragment"
|
android:id="@+id/storyViewerFragment"
|
||||||
android:name="awais.instagrabber.fragments.StoryViewerFragment"
|
android:name="awais.instagrabber.fragments.StoryViewerFragment"
|
||||||
android:label="StoryViewerFragment"
|
|
||||||
tools:layout="@layout/fragment_story_viewer">
|
tools:layout="@layout/fragment_story_viewer">
|
||||||
<argument
|
<argument
|
||||||
android:name="options"
|
android:name="options"
|
||||||
|
@ -8,4 +8,13 @@
|
|||||||
<item name="share_dm" type="id" />
|
<item name="share_dm" type="id" />
|
||||||
<item name="download_current" type="id" />
|
<item name="download_current" type="id" />
|
||||||
<item name="download_all" type="id" />
|
<item name="download_all" type="id" />
|
||||||
|
<!-- story stickers -->
|
||||||
|
<item name="mentions" type="id" />
|
||||||
|
<item name="spotify" type="id" />
|
||||||
|
<item name="poll" type="id" />
|
||||||
|
<item name="question" type="id" />
|
||||||
|
<item name="quiz" type="id" />
|
||||||
|
<item name="slider" type="id" />
|
||||||
|
<item name="viewStoryPost" type="id" />
|
||||||
|
<item name="swipeUp" type="id" />
|
||||||
</resources>
|
</resources>
|
@ -69,8 +69,7 @@
|
|||||||
<string name="be_patient">Be patient!</string>
|
<string name="be_patient">Be patient!</string>
|
||||||
<string name="view_story_post">View Post</string>
|
<string name="view_story_post">View Post</string>
|
||||||
<string name="view_post">View Post</string>
|
<string name="view_post">View Post</string>
|
||||||
<string name="spotify" translatable="false">Spotify</string>
|
<string name="story_poll">Poll</string>
|
||||||
<string name="vote_story_poll">Vote</string>
|
|
||||||
<string name="votef_story_poll">Vote successful!</string>
|
<string name="votef_story_poll">Vote successful!</string>
|
||||||
<string name="voted_story_poll">You have already voted!</string>
|
<string name="voted_story_poll">You have already voted!</string>
|
||||||
<string name="respond_story">Respond</string>
|
<string name="respond_story">Respond</string>
|
||||||
@ -87,6 +86,7 @@
|
|||||||
<string name="story_slider">Slider</string>
|
<string name="story_slider">Slider</string>
|
||||||
<string name="story_quizzed">You have already answered!</string>
|
<string name="story_quizzed">You have already answered!</string>
|
||||||
<string name="story_mentions">Mentions</string>
|
<string name="story_mentions">Mentions</string>
|
||||||
|
<string name="story_question">Question</string>
|
||||||
<string name="priv_acc">This Account is Private</string>
|
<string name="priv_acc">This Account is Private</string>
|
||||||
<string name="priv_acc_confirm">You won\'t be able to access posts after unfollowing! Are you sure?</string>
|
<string name="priv_acc_confirm">You won\'t be able to access posts after unfollowing! Are you sure?</string>
|
||||||
<string name="are_you_sure">Are you sure?</string>
|
<string name="are_you_sure">Are you sure?</string>
|
||||||
@ -104,7 +104,7 @@
|
|||||||
<string name="delete_collection">Delete collection</string>
|
<string name="delete_collection">Delete collection</string>
|
||||||
<string name="delete_collection_confirm">Are you sure you want to delete this collection?</string>
|
<string name="delete_collection_confirm">Are you sure you want to delete this collection?</string>
|
||||||
<string name="delete_collection_note">All contained media will remain in other collections.</string>
|
<string name="delete_collection_note">All contained media will remain in other collections.</string>
|
||||||
<string name="add_to_collection">Add to collection...</string>
|
<string name="add_to_collection">Add to collection…</string>
|
||||||
<string name="remove_from_collection">Remove from collection</string>
|
<string name="remove_from_collection">Remove from collection</string>
|
||||||
<string name="liked">Liked</string>
|
<string name="liked">Liked</string>
|
||||||
<string name="saved">Saved</string>
|
<string name="saved">Saved</string>
|
||||||
@ -183,9 +183,9 @@
|
|||||||
<string name="dms_inbox_raven_media_screenshot">Screenshotted</string>
|
<string name="dms_inbox_raven_media_screenshot">Screenshotted</string>
|
||||||
<string name="dms_inbox_raven_media_cant_deliver">Cannot deliver</string>
|
<string name="dms_inbox_raven_media_cant_deliver">Cannot deliver</string>
|
||||||
<string name="dms_inbox_error_null_count">Unseen count response is null!</string>
|
<string name="dms_inbox_error_null_count">Unseen count response is null!</string>
|
||||||
<string name="dms_thread_message_hint">Message...</string>
|
<string name="dms_thread_message_hint">Message…</string>
|
||||||
<string name="dms_thread_audio_hint">Press and hold to record audio</string>
|
<string name="dms_thread_audio_hint">Press and hold to record audio</string>
|
||||||
<string name="dms_thread_updating">Updating...</string>
|
<string name="dms_thread_updating">Updating…</string>
|
||||||
<string name="dms_action_leave">Leave chat</string>
|
<string name="dms_action_leave">Leave chat</string>
|
||||||
<string name="dms_action_leave_question">Leave this chat?</string>
|
<string name="dms_action_leave_question">Leave this chat?</string>
|
||||||
<string name="dms_action_kick">Kick</string>
|
<string name="dms_action_kick">Kick</string>
|
||||||
@ -333,7 +333,7 @@
|
|||||||
<string name="comment">Comment</string>
|
<string name="comment">Comment</string>
|
||||||
<string name="layout">Layout</string>
|
<string name="layout">Layout</string>
|
||||||
<string name="feed_stories">Feed stories</string>
|
<string name="feed_stories">Feed stories</string>
|
||||||
<string name="opening_post">Opening post...</string>
|
<string name="opening_post">Opening post…</string>
|
||||||
<string name="share">Share</string>
|
<string name="share">Share</string>
|
||||||
<string name="layout_style">Layout style</string>
|
<string name="layout_style">Layout style</string>
|
||||||
<string name="column_count">Column count</string>
|
<string name="column_count">Column count</string>
|
||||||
@ -511,7 +511,7 @@
|
|||||||
<string name="click_to_show_full">Click to show full like count</string>
|
<string name="click_to_show_full">Click to show full like count</string>
|
||||||
<string name="no_profile_pic_found">No profile pic found!</string>
|
<string name="no_profile_pic_found">No profile pic found!</string>
|
||||||
<string name="swipe_up_confirmation">Are you sure you want to open this link?</string>
|
<string name="swipe_up_confirmation">Are you sure you want to open this link?</string>
|
||||||
<string name="sending">Sending...</string>
|
<string name="sending">Sending…</string>
|
||||||
<string name="share_via_dm">Share via DM</string>
|
<string name="share_via_dm">Share via DM</string>
|
||||||
<string name="share_link">Share link…</string>
|
<string name="share_link">Share link…</string>
|
||||||
<string name="slide_to_cancel">Slide to Cancel</string>
|
<string name="slide_to_cancel">Slide to Cancel</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user