package awais.instagrabber.viewmodels import android.app.Application import android.content.ContentResolver import android.net.Uri import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.* import awais.instagrabber.customviews.emoji.Emoji import awais.instagrabber.managers.DirectMessagesManager import awais.instagrabber.managers.DirectMessagesManager.inboxManager import awais.instagrabber.managers.ThreadManager import awais.instagrabber.models.Resource import awais.instagrabber.models.Resource.Companion.error import awais.instagrabber.models.Resource.Companion.success import awais.instagrabber.repositories.responses.User import awais.instagrabber.repositories.responses.directmessages.DirectItem import awais.instagrabber.repositories.responses.directmessages.DirectThread import awais.instagrabber.repositories.responses.directmessages.RankedRecipient import awais.instagrabber.repositories.responses.giphy.GiphyGif import awais.instagrabber.utils.* import awais.instagrabber.utils.MediaUtils.OnInfoLoadListener import awais.instagrabber.utils.MediaUtils.VideoInfo import awais.instagrabber.utils.VoiceRecorder.VoiceRecorderCallback import awais.instagrabber.utils.VoiceRecorder.VoiceRecordingResult class DirectThreadViewModel( application: Application, val threadId: String, pending: Boolean, val currentUser: User, ) : AndroidViewModel(application) { // private val TAG = DirectThreadViewModel::class.java.simpleName // private static final String ERROR_INVALID_THREAD = "Invalid thread"; private val contentResolver: ContentResolver = application.contentResolver private val recordingsDir: DocumentFile? = DownloadUtils.getRecordingsDir() private var voiceRecorder: VoiceRecorder? = null private lateinit var threadManager: ThreadManager val viewerId: Long val threadTitle: LiveData by lazy { threadManager.threadTitle } val thread: LiveData by lazy { threadManager.thread } val items: LiveData> by lazy { Transformations.map(threadManager.items) { it.filter { thread -> thread.hideInThread == 0 } } } val isFetching: LiveData> by lazy { threadManager.fetching } val users: LiveData> by lazy { threadManager.users } val leftUsers: LiveData> by lazy { threadManager.leftUsers } val pendingRequestsCount: LiveData by lazy { threadManager.pendingRequestsCount } val inputMode: LiveData by lazy { threadManager.inputMode } val isPending: LiveData by lazy { threadManager.isPending } val replyToItem: LiveData by lazy { threadManager.replyToItem } fun moveFromPending() { val messagesManager = DirectMessagesManager messagesManager.moveThreadFromPending(threadId) threadManager = messagesManager.getThreadManager(threadId, false, currentUser, contentResolver) } fun removeThread() { threadManager.removeThread() } fun fetchChats() { threadManager.fetchChats(viewModelScope) } fun refreshChats() { threadManager.refreshChats(viewModelScope) } fun sendText(text: String): LiveData> { return threadManager.sendText(text, viewModelScope) } fun sendUri(uri: Uri): LiveData> { return threadManager.sendUri(uri, viewModelScope) } fun startRecording(): LiveData> { val data = MutableLiveData>() voiceRecorder = VoiceRecorder(recordingsDir!!, object : VoiceRecorderCallback { override fun onStart() {} override fun onComplete(result: VoiceRecordingResult) { // Log.d(TAG, "onComplete: recording complete. Scanning file..."); MediaUtils.getVoiceInfo( contentResolver, result.file.uri, object : OnInfoLoadListener { override fun onLoad(videoInfo: VideoInfo?) { if (videoInfo == null) return threadManager.sendVoice( data, result.file.uri, result.waveform, result.samplingFreq, videoInfo.duration, result.file.length(), viewModelScope ) } override fun onFailure(t: Throwable) { data.postValue(error(t.message, null)) } }) } override fun onCancel() {} }) voiceRecorder?.startRecording(contentResolver) return data } fun stopRecording(delete: Boolean) { voiceRecorder?.stopRecording(delete) voiceRecorder = null } fun sendReaction(item: DirectItem, emoji: Emoji): LiveData> { return threadManager.sendReaction(item, emoji, viewModelScope) } fun sendDeleteReaction(itemId: String): LiveData> { return threadManager.sendDeleteReaction(itemId, viewModelScope) } fun unsend(item: DirectItem): LiveData> { return threadManager.unsend(item, viewModelScope) } fun sendAnimatedMedia(giphyGif: GiphyGif): LiveData> { return threadManager.sendAnimatedMedia(giphyGif, viewModelScope) } fun getUser(userId: Long): User? { var match: User? = null users.value?.let { match = it.firstOrNull { user -> user.pk == userId } } if (match == null) { leftUsers.value?.let { match = it.firstOrNull { user -> user.pk == userId } } } return match } fun forward(recipients: Set, itemToForward: DirectItem) { threadManager.forward(recipients, itemToForward, viewModelScope) } fun forward(recipient: RankedRecipient, itemToForward: DirectItem) { threadManager.forward(recipient, itemToForward, viewModelScope) } fun setReplyToItem(item: DirectItem?) { // Log.d(TAG, "setReplyToItem: " + item); threadManager.setReplyToItem(item) } fun acceptRequest(): LiveData> { return threadManager.acceptRequest(viewModelScope) } fun declineRequest(): LiveData> { return threadManager.declineRequest(viewModelScope) } fun markAsSeen(): LiveData> { val thread = thread.value ?: return successEventResObjectLiveData val items = thread.items if (items.isNullOrEmpty()) return successEventResObjectLiveData val directItem = items.firstOrNull { (_, userId) -> userId != currentUser.pk } ?: return successEventResObjectLiveData val lastSeenAt = thread.lastSeenAt if (lastSeenAt != null) { val seenAt = lastSeenAt[currentUser.pk] ?: return successEventResObjectLiveData try { val timestamp = seenAt.timestamp ?: return successEventResObjectLiveData val itemIdMatches = seenAt.itemId == directItem.itemId val timestampMatches = timestamp.toLong() >= directItem.getTimestamp() if (itemIdMatches || timestampMatches) { return successEventResObjectLiveData } } catch (ignored: Exception) { return successEventResObjectLiveData } } return threadManager.markAsSeen(directItem, viewModelScope) } private val successEventResObjectLiveData: MutableLiveData> get() { val data = MutableLiveData>() data.postValue(success(Any())) return data } fun deleteThreadIfRequired() { val thread = thread.value ?: return if (thread.isTemp && thread.items.isNullOrEmpty()) { val inboxManager = inboxManager inboxManager.removeThread(threadId) } } init { val cookie = Utils.settingsHelper.getString(Constants.COOKIE) viewerId = getUserIdFromCookie(cookie) val deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID) val csrfToken = getCsrfTokenFromCookie(cookie) require(!csrfToken.isNullOrBlank() && viewerId != 0L && deviceUuid.isNotBlank()) { "User is not logged in!" } threadManager = DirectMessagesManager.getThreadManager(threadId, pending, currentUser, contentResolver) threadManager.fetchPendingRequests(viewModelScope) } }