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

458 lines
17 KiB
Kotlin

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
}
}