Browse Source

More suspend funs

renovate/org.robolectric-robolectric-4.x
Ammar Githam 4 years ago
parent
commit
741a997424
  1. 35
      app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java
  2. 62
      app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.kt
  3. 50
      app/src/main/java/awais/instagrabber/managers/InboxManager.kt
  4. 279
      app/src/main/java/awais/instagrabber/managers/ThreadManager.kt
  5. 25
      app/src/main/java/awais/instagrabber/models/enums/DirectItemType.kt
  6. 6
      app/src/main/java/awais/instagrabber/repositories/DirectMessagesRepository.kt
  7. 19
      app/src/main/java/awais/instagrabber/utils/CoroutineUtils.kt
  8. 1
      app/src/main/java/awais/instagrabber/viewmodels/DirectInboxViewModel.kt
  9. 21
      app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt
  10. 42
      app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.kt

35
app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java

@ -88,9 +88,9 @@ import awais.instagrabber.repositories.requests.directmessages.ThreadIdOrUserIds
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.StoryStickerResponse;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
@ -231,32 +231,21 @@ public class StoryViewerFragment extends Fragment {
return;
}
final DirectThread thread = response.body();
final Call<DirectThreadBroadcastResponse> request = directMessagesService.broadcastStoryReply(
directMessagesService.broadcastStoryReply(
ThreadIdOrUserIds.of(thread.getThreadId()),
input.getText().toString(),
currentStory.getStoryMediaId(),
String.valueOf(currentStory.getUserId())
String.valueOf(currentStory.getUserId()),
CoroutineUtilsKt.getContinuation((directThreadBroadcastResponse, throwable) -> {
if (throwable != null) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
Log.e(TAG, "onFailure: ", throwable);
return;
}
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
})
);
request.enqueue(new Callback<DirectThreadBroadcastResponse>() {
@Override
public void onResponse(@NonNull final Call<DirectThreadBroadcastResponse> call,
@NonNull final Response<DirectThreadBroadcastResponse> response) {
if (!response.isSuccessful()) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(@NonNull final Call<DirectThreadBroadcastResponse> call, @NonNull final Throwable t) {
try {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
Log.e(TAG, "onFailure: ", t);
} catch (Throwable ignored) {
}
}
});
}
@Override

62
app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.kt

@ -12,7 +12,6 @@ import awais.instagrabber.models.Resource.Companion.success
import awais.instagrabber.repositories.requests.directmessages.ThreadIdOrUserIds.Companion.of
import awais.instagrabber.repositories.responses.User
import awais.instagrabber.repositories.responses.directmessages.DirectThread
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
import awais.instagrabber.utils.Constants
import awais.instagrabber.utils.Utils
@ -21,6 +20,8 @@ import awais.instagrabber.utils.getUserIdFromCookie
import awais.instagrabber.webservices.DirectMessagesService
import awais.instagrabber.webservices.DirectMessagesService.Companion.getInstance
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
@ -137,7 +138,7 @@ object DirectMessagesManager {
// create thread and forward
createThread(recipient.user.pk) { (threadId) ->
val threadIdTemp = threadId ?: return@createThread
sendMedia(threadIdTemp, mediaId) {
sendMedia(threadIdTemp, mediaId, scope) {
if (refreshInbox) {
inboxManager.refresh(scope)
}
@ -149,7 +150,7 @@ object DirectMessagesManager {
// just forward
val thread = recipient.thread
val threadId = thread.threadId ?: return
sendMedia(threadId, mediaId) {
sendMedia(threadId, mediaId, scope) {
if (refreshInbox) {
inboxManager.refresh(scope)
}
@ -160,55 +161,26 @@ object DirectMessagesManager {
private fun sendMedia(
threadId: String,
mediaId: String,
scope: CoroutineScope,
callback: (() -> Unit)?,
): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>()
data.postValue(loading(null))
val request = service.broadcastMediaShare(
UUID.randomUUID().toString(),
of(threadId),
mediaId
)
request.enqueue(object : Callback<DirectThreadBroadcastResponse?> {
override fun onResponse(
call: Call<DirectThreadBroadcastResponse?>,
response: Response<DirectThreadBroadcastResponse?>,
) {
if (response.isSuccessful) {
data.postValue(success(Any()))
callback?.invoke()
return
}
val errorBody = response.errorBody()
if (errorBody != null) {
try {
val string = errorBody.string()
val msg = String.format(Locale.US,
"onResponse: url: %s, responseCode: %d, errorBody: %s",
call.request().url().toString(),
response.code(),
string)
Log.e(TAG, msg)
data.postValue(error(msg, null))
} catch (e: IOException) {
Log.e(TAG, "onResponse: ", e)
data.postValue(error(e.message, null))
}
callback?.invoke()
return
}
val msg = "onResponse: request was not successful and response error body was null"
Log.e(TAG, msg)
data.postValue(error(msg, null))
scope.launch(Dispatchers.IO) {
try {
service.broadcastMediaShare(
UUID.randomUUID().toString(),
of(threadId),
mediaId
)
data.postValue(success(Any()))
callback?.invoke()
}
override fun onFailure(call: Call<DirectThreadBroadcastResponse?>, t: Throwable) {
Log.e(TAG, "onFailure: ", t)
data.postValue(error(t.message, null))
} catch (e: Exception) {
Log.e(TAG, "sendMedia: ", e)
data.postValue(error(e.message, null))
callback?.invoke()
}
})
}
return data
}

50
app/src/main/java/awais/instagrabber/managers/InboxManager.kt

@ -21,8 +21,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.util.*
import java.util.concurrent.TimeUnit
@ -66,49 +64,23 @@ class InboxManager private constructor(private val pending: Boolean) {
inbox.postValue(error(e.message, currentDirectInbox))
hasOlder = false
}
// inboxRequest?.enqueue(object : Callback<DirectInboxResponse?> {
// override fun onResponse(call: Call<DirectInboxResponse?>, response: Response<DirectInboxResponse?>) {
// val body = response.body()
// if (body == null) {
// Log.e(TAG, "parseInboxResponse: Response is null")
// inbox.postValue(error(R.string.generic_null_response, currentDirectInbox))
// hasOlder = false
// return
// }
//
// }
//
// override fun onFailure(call: Call<DirectInboxResponse?>, t: Throwable) {
// Log.e(TAG, "Failed fetching dm inbox", t)
// inbox.postValue(error(t.message, currentDirectInbox))
// hasOlder = false
// }
// })
}
}
fun fetchUnseenCount() {
fun fetchUnseenCount(scope: CoroutineScope) {
val unseenCountResource = unseenCount.value
if (unseenCountResource != null && unseenCountResource.status === Resource.Status.LOADING) return
stopCurrentUnseenCountRequest()
unseenCount.postValue(loading(currentUnseenCount))
unseenCountRequest = service.fetchUnseenCount()
unseenCountRequest?.enqueue(object : Callback<DirectBadgeCount?> {
override fun onResponse(call: Call<DirectBadgeCount?>, response: Response<DirectBadgeCount?>) {
val directBadgeCount = response.body()
if (directBadgeCount == null) {
Log.e(TAG, "onResponse: directBadgeCount Response is null")
unseenCount.postValue(error(R.string.dms_inbox_error_null_count, currentUnseenCount))
return
}
scope.launch(Dispatchers.IO) {
try {
val directBadgeCount = service.fetchUnseenCount()
unseenCount.postValue(success(directBadgeCount.badgeCount))
} catch (e: Exception) {
Log.e(TAG, "Failed fetching unseen count", e)
unseenCount.postValue(error(e.message, currentUnseenCount))
}
override fun onFailure(call: Call<DirectBadgeCount?>, t: Throwable) {
Log.e(TAG, "Failed fetching unseen count", t)
unseenCount.postValue(error(t.message, currentUnseenCount))
}
})
}
}
fun refresh(scope: CoroutineScope) {
@ -117,7 +89,7 @@ class InboxManager private constructor(private val pending: Boolean) {
hasOlder = true
fetchInbox(scope)
if (!pending) {
fetchUnseenCount()
fetchUnseenCount(scope)
}
}
@ -350,9 +322,5 @@ class InboxManager private constructor(private val pending: Boolean) {
val threads = inbox?.threads ?: emptyList()
ImmutableList.sortedCopyOf(THREAD_COMPARATOR, threads)
})
// fetchInbox()
if (!pending) {
fetchUnseenCount()
}
}
}

279
app/src/main/java/awais/instagrabber/managers/ThreadManager.kt

@ -10,11 +10,11 @@ import androidx.lifecycle.Transformations.distinctUntilChanged
import androidx.lifecycle.Transformations.map
import awais.instagrabber.R
import awais.instagrabber.customviews.emoji.Emoji
import awais.instagrabber.models.enums.DirectItemType.Companion.getName
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.DirectItemType
import awais.instagrabber.repositories.requests.UploadFinishOptions
import awais.instagrabber.repositories.requests.VideoOptions
import awais.instagrabber.repositories.requests.directmessages.ThreadIdOrUserIds
@ -400,7 +400,7 @@ class ThreadManager private constructor(
return temp
}
fun sendText(text: String): LiveData<Resource<Any?>> {
fun sendText(text: String, scope: CoroutineScope): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>()
val userId = getCurrentUserId(data) ?: return data
val clientContext = UUID.randomUUID().toString()
@ -412,29 +412,36 @@ class ThreadManager private constructor(
data.postValue(loading(directItem))
val repliedToItemId = replyToItemValue?.itemId
val repliedToClientContext = replyToItemValue?.clientContext
val request = service.broadcastText(
clientContext,
threadIdOrUserIds,
text,
repliedToItemId,
repliedToClientContext
)
enqueueRequest(request, data, directItem)
scope.launch(Dispatchers.IO) {
try {
val response = service.broadcastText(
clientContext,
threadIdOrUserIds,
text,
repliedToItemId,
repliedToClientContext
)
parseResponse(response, data, directItem)
} catch (e: Exception) {
data.postValue(error(e.message, directItem))
Log.e(TAG, "sendText: ", e)
}
}
return data
}
fun sendUri(entry: MediaController.MediaEntry): LiveData<Resource<Any?>> {
fun sendUri(entry: MediaController.MediaEntry, scope: CoroutineScope): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>()
val uri = Uri.fromFile(File(entry.path))
if (!entry.isVideo) {
sendPhoto(data, uri, entry.width, entry.height)
sendPhoto(data, uri, entry.width, entry.height, scope)
return data
}
sendVideo(data, uri, entry.size, entry.duration, entry.width, entry.height)
sendVideo(data, uri, entry.size, entry.duration, entry.width, entry.height, scope)
return data
}
fun sendUri(uri: Uri): LiveData<Resource<Any?>> {
fun sendUri(uri: Uri, scope: CoroutineScope): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>()
val mimeType = Utils.getMimeType(uri, contentResolver)
if (isEmpty(mimeType)) {
@ -443,16 +450,16 @@ class ThreadManager private constructor(
}
val isPhoto = mimeType != null && mimeType.startsWith("image")
if (isPhoto) {
sendPhoto(data, uri)
sendPhoto(data, uri, scope)
return data
}
if (mimeType != null && mimeType.startsWith("video")) {
sendVideo(data, uri)
sendVideo(data, uri, scope)
}
return data
}
fun sendAnimatedMedia(giphyGif: GiphyGif): LiveData<Resource<Any?>> {
fun sendAnimatedMedia(giphyGif: GiphyGif, scope: CoroutineScope): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>()
val userId = getCurrentUserId(data) ?: return data
val clientContext = UUID.randomUUID().toString()
@ -460,12 +467,19 @@ class ThreadManager private constructor(
directItem.isPending = true
addItems(0, listOf(directItem))
data.postValue(loading(directItem))
val request = service.broadcastAnimatedMedia(
clientContext,
threadIdOrUserIds,
giphyGif
)
enqueueRequest(request, data, directItem)
scope.launch(Dispatchers.IO) {
try {
val request = service.broadcastAnimatedMedia(
clientContext,
threadIdOrUserIds,
giphyGif
)
parseResponse(request, data, directItem)
} catch (e: Exception) {
data.postValue(error(e.message, directItem))
Log.e(TAG, "sendAnimatedMedia: ", e)
}
}
return data
}
@ -476,6 +490,7 @@ class ThreadManager private constructor(
samplingFreq: Int,
duration: Long,
byteLength: Long,
scope: CoroutineScope,
) {
if (duration > 60000) {
// instagram does not allow uploading audio longer than 60 secs for Direct messages
@ -502,14 +517,21 @@ class ThreadManager private constructor(
uploadFinishRequest.enqueue(object : Callback<String?> {
override fun onResponse(call: Call<String?>, response: Response<String?>) {
if (response.isSuccessful) {
val request = service.broadcastVoice(
clientContext,
threadIdOrUserIds,
uploadDmVoiceOptions.uploadId,
waveform,
samplingFreq
)
enqueueRequest(request, data, directItem)
scope.launch(Dispatchers.IO) {
try {
val request = service.broadcastVoice(
clientContext,
threadIdOrUserIds,
uploadDmVoiceOptions.uploadId,
waveform,
samplingFreq
)
parseResponse(request, data, directItem)
} catch (e: Exception) {
data.postValue(error(e.message, directItem))
Log.e(TAG, "sendVoice: ", e)
}
}
return
}
if (response.errorBody() != null) {
@ -537,6 +559,7 @@ class ThreadManager private constructor(
fun sendReaction(
item: DirectItem,
emoji: Emoji,
scope: CoroutineScope,
): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>()
val userId = getCurrentUserId(data)
@ -557,18 +580,24 @@ class ThreadManager private constructor(
data.postValue(error("itemId is null", null))
return data
}
val request = service.broadcastReaction(
clientContext,
threadIdOrUserIds,
itemId,
emojiUnicode,
false
)
handleBroadcastReactionRequest(data, item, request)
scope.launch(Dispatchers.IO) {
try {
service.broadcastReaction(
clientContext,
threadIdOrUserIds,
itemId,
emojiUnicode,
false
)
} catch (e: Exception) {
data.postValue(error(e.message, null))
Log.e(TAG, "sendReaction: ", e)
}
}
return data
}
fun sendDeleteReaction(itemId: String): LiveData<Resource<Any?>> {
fun sendDeleteReaction(itemId: String, scope: CoroutineScope): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>()
val item = getItem(itemId)
if (item == null) {
@ -588,8 +617,14 @@ class ThreadManager private constructor(
data.postValue(error("itemId is null", null))
return data
}
val request = service.broadcastReaction(clientContext, threadIdOrUserIds, itemId1, null, true)
handleBroadcastReactionRequest(data, item, request)
scope.launch(Dispatchers.IO) {
try {
service.broadcastReaction(clientContext, threadIdOrUserIds, itemId1, null, true)
} catch (e: Exception) {
data.postValue(error(e.message, null))
Log.e(TAG, "sendDeleteReaction: ", e)
}
}
return data
}
@ -660,7 +695,7 @@ class ThreadManager private constructor(
data.postValue(error("item type is null", null))
return data
}
val itemTypeName = getName(itemType)
val itemTypeName = DirectItemType.getName(itemType)
if (itemTypeName == null) {
Log.e(TAG, "forward: itemTypeName was null!")
data.postValue(error("itemTypeName is null", null))
@ -798,6 +833,7 @@ class ThreadManager private constructor(
private fun sendPhoto(
data: MutableLiveData<Resource<Any?>>,
uri: Uri,
scope: CoroutineScope,
) {
try {
val dimensions = BitmapUtils.decodeDimensions(contentResolver, uri)
@ -805,7 +841,7 @@ class ThreadManager private constructor(
data.postValue(error("Decoding dimensions failed", null))
return
}
sendPhoto(data, uri, dimensions.first, dimensions.second)
sendPhoto(data, uri, dimensions.first, dimensions.second, scope)
} catch (e: IOException) {
data.postValue(error(e.message, null))
Log.e(TAG, "sendPhoto: ", e)
@ -817,6 +853,7 @@ class ThreadManager private constructor(
uri: Uri,
width: Int,
height: Int,
scope: CoroutineScope,
) {
val userId = getCurrentUserId(data) ?: return
val clientContext = UUID.randomUUID().toString()
@ -829,8 +866,15 @@ class ThreadManager private constructor(
if (handleInvalidResponse(data, response)) return
val response1 = response.response ?: return
val uploadId = response1.optString("upload_id")
val request = service.broadcastPhoto(clientContext, threadIdOrUserIds, uploadId)
enqueueRequest(request, data, directItem)
scope.launch(Dispatchers.IO) {
try {
val response2 = service.broadcastPhoto(clientContext, threadIdOrUserIds, uploadId)
parseResponse(response2, data, directItem)
} catch (e: Exception) {
data.postValue(error(e.message, null))
Log.e(TAG, "sendPhoto: ", e)
}
}
}
override fun onFailure(t: Throwable) {
@ -843,6 +887,7 @@ class ThreadManager private constructor(
private fun sendVideo(
data: MutableLiveData<Resource<Any?>>,
uri: Uri,
scope: CoroutineScope,
) {
MediaUtils.getVideoInfo(contentResolver, uri, object : OnInfoLoadListener<VideoInfo?> {
override fun onLoad(info: VideoInfo?) {
@ -850,7 +895,7 @@ class ThreadManager private constructor(
data.postValue(error("Could not get the video info", null))
return
}
sendVideo(data, uri, info.size, info.duration, info.width, info.height)
sendVideo(data, uri, info.size, info.duration, info.width, info.height, scope)
}
override fun onFailure(t: Throwable) {
@ -866,6 +911,7 @@ class ThreadManager private constructor(
duration: Long,
width: Int,
height: Int,
scope: CoroutineScope,
) {
if (duration > 60000) {
// instagram does not allow uploading videos longer than 60 secs for Direct messages
@ -892,14 +938,21 @@ class ThreadManager private constructor(
uploadFinishRequest.enqueue(object : Callback<String?> {
override fun onResponse(call: Call<String?>, response: Response<String?>) {
if (response.isSuccessful) {
val request = service.broadcastVideo(
clientContext,
threadIdOrUserIds,
uploadDmVideoOptions.uploadId,
"",
true
)
enqueueRequest(request, data, directItem)
scope.launch(Dispatchers.IO) {
try {
val response1 = service.broadcastVideo(
clientContext,
threadIdOrUserIds,
uploadDmVideoOptions.uploadId,
"",
true
)
parseResponse(response1, data, directItem)
} catch (e: Exception) {
data.postValue(error(e.message, null))
Log.e(TAG, "sendVideo: ", e)
}
}
return
}
if (response.errorBody() != null) {
@ -924,64 +977,36 @@ class ThreadManager private constructor(
})
}
private fun enqueueRequest(
request: Call<DirectThreadBroadcastResponse?>,
private fun parseResponse(
response: DirectThreadBroadcastResponse,
data: MutableLiveData<Resource<Any?>>,
directItem: DirectItem,
) {
request.enqueue(object : Callback<DirectThreadBroadcastResponse?> {
override fun onResponse(
call: Call<DirectThreadBroadcastResponse?>,
response: Response<DirectThreadBroadcastResponse?>,
) {
if (response.isSuccessful) {
val broadcastResponse = response.body()
if (broadcastResponse == null) {
data.postValue(error(R.string.generic_null_response, directItem))
Log.e(TAG, "enqueueRequest: onResponse: response body is null")
return
}
val payloadClientContext: String?
val timestamp: Long
val itemId: String?
val payload = broadcastResponse.payload
if (payload == null) {
val messageMetadata = broadcastResponse.messageMetadata
if (messageMetadata == null || messageMetadata.isEmpty()) {
data.postValue(success(directItem))
return
}
val (clientContext, itemId1, timestamp1) = messageMetadata[0]
payloadClientContext = clientContext
itemId = itemId1
timestamp = timestamp1
} else {
payloadClientContext = payload.clientContext
timestamp = payload.timestamp
itemId = payload.itemId
}
if (payloadClientContext == null) {
data.postValue(error("clientContext in response was null", null))
return
}
updateItemSent(payloadClientContext, timestamp, itemId)
data.postValue(success(directItem))
return
}
if (response.errorBody() != null) {
handleErrorBody(call, response, data)
}
data.postValue(error(R.string.generic_failed_request, directItem))
}
override fun onFailure(
call: Call<DirectThreadBroadcastResponse?>,
t: Throwable,
) {
data.postValue(error(t.message, directItem))
Log.e(TAG, "enqueueRequest: onFailure: ", t)
val payloadClientContext: String?
val timestamp: Long
val itemId: String?
val payload = response.payload
if (payload == null) {
val messageMetadata = response.messageMetadata
if (messageMetadata == null || messageMetadata.isEmpty()) {
data.postValue(success(directItem))
return
}
})
val (clientContext, itemId1, timestamp1) = messageMetadata[0]
payloadClientContext = clientContext
itemId = itemId1
timestamp = timestamp1
} else {
payloadClientContext = payload.clientContext
timestamp = payload.timestamp
itemId = payload.itemId
}
if (payloadClientContext == null) {
data.postValue(error("clientContext in response was null", null))
return
}
updateItemSent(payloadClientContext, timestamp, itemId)
data.postValue(success(directItem))
}
private fun updateItemSent(
@ -1054,38 +1079,6 @@ class ThreadManager private constructor(
.firstOrNull()
}
private fun handleBroadcastReactionRequest(
data: MutableLiveData<Resource<Any?>>,
item: DirectItem,
request: Call<DirectThreadBroadcastResponse?>,
) {
request.enqueue(object : Callback<DirectThreadBroadcastResponse?> {
override fun onResponse(
call: Call<DirectThreadBroadcastResponse?>,
response: Response<DirectThreadBroadcastResponse?>,
) {
if (!response.isSuccessful) {
if (response.errorBody() != null) {
handleErrorBody(call, response, data)
return
}
data.postValue(error(R.string.generic_failed_request, item))
return
}
val body = response.body()
if (body == null) {
data.postValue(error(R.string.generic_null_response, item))
}
// otherwise nothing to do? maybe update the timestamp in the emoji?
}
override fun onFailure(call: Call<DirectThreadBroadcastResponse?>, t: Throwable) {
data.postValue(error(t.message, item))
Log.e(TAG, "enqueueRequest: onFailure: ", t)
}
})
}
private fun stopCurrentRequest() {
chatsRequest?.let {
if (it.isExecuted || it.isCanceled) return
@ -1583,7 +1576,7 @@ class ThreadManager private constructor(
})
}
fun markAsSeen(directItem: DirectItem): LiveData<Resource<Any?>> {
fun markAsSeen(directItem: DirectItem, scope: CoroutineScope): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>()
data.postValue(loading(null))
val request = service.markAsSeen(threadId, directItem)
@ -1606,7 +1599,7 @@ class ThreadManager private constructor(
data.postValue(error(R.string.generic_null_response, null))
return
}
inboxManager.fetchUnseenCount()
inboxManager.fetchUnseenCount(scope)
val payload = seenResponse.payload ?: return
val timestamp = payload.timestamp
val thread = thread.value ?: return

25
app/src/main/java/awais/instagrabber/models/enums/DirectItemType.kt

@ -1,46 +1,62 @@
package awais.instagrabber.models.enums
import com.google.gson.annotations.SerializedName
import java.io.Serializable
import java.util.*
enum class DirectItemType(val id: Int) : Serializable {
UNKNOWN(0),
@SerializedName("text")
TEXT(1),
@SerializedName("like")
LIKE(2),
@SerializedName("link")
LINK(3),
@SerializedName("media")
MEDIA(4),
@SerializedName("raven_media")
RAVEN_MEDIA(5),
@SerializedName("profile")
PROFILE(6),
@SerializedName("video_call_event")
VIDEO_CALL_EVENT(7),
@SerializedName("animated_media")
ANIMATED_MEDIA(8),
@SerializedName("voice_media")
VOICE_MEDIA(9),
@SerializedName("media_share")
MEDIA_SHARE(10),
@SerializedName("reel_share")
REEL_SHARE(11),
@SerializedName("action_log")
ACTION_LOG(12),
@SerializedName("placeholder")
PLACEHOLDER(13),
@SerializedName("story_share")
STORY_SHARE(14),
@SerializedName("clip")
CLIP(15), // media_share but reel
@SerializedName("felix_share")
FELIX_SHARE(16), // media_share but igtv
@SerializedName("location")
LOCATION(17),
@SerializedName("xma")
XMA(18); // self avatar stickers
@ -52,7 +68,6 @@ enum class DirectItemType(val id: Int) : Serializable {
return map[id]
}
@JvmStatic
fun getName(directItemType: DirectItemType): String? {
when (directItemType) {
TEXT -> return "text"
@ -72,12 +87,12 @@ enum class DirectItemType(val id: Int) : Serializable {
CLIP -> return "clip"
FELIX_SHARE -> return "felix_share"
LOCATION -> return "location"
else -> return null
}
return null
}
init {
for (type in DirectItemType.values()) {
for (type in values()) {
map[type.id] = type
}
}

6
app/src/main/java/awais/instagrabber/repositories/DirectMessagesRepository.kt

@ -18,14 +18,14 @@ interface DirectMessagesRepository {
): DirectThreadFeedResponse
@GET("/api/v1/direct_v2/get_badge_count/?no_raven=1")
fun fetchUnseenCount(): Call<DirectBadgeCount?>
suspend fun fetchUnseenCount(): DirectBadgeCount
@FormUrlEncoded
@POST("/api/v1/direct_v2/threads/broadcast/{item}/")
fun broadcast(
suspend fun broadcast(
@Path("item") item: String,
@FieldMap signedForm: Map<String, String>,
): Call<DirectThreadBroadcastResponse?>
): DirectThreadBroadcastResponse
@FormUrlEncoded
@POST("/api/v1/direct_v2/threads/{threadId}/add_user/")

19
app/src/main/java/awais/instagrabber/utils/CoroutineUtils.kt

@ -0,0 +1,19 @@
package awais.instagrabber.utils
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import java.util.function.BiConsumer
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
@JvmOverloads
fun <R> getContinuation(onFinished: BiConsumer<R?, Throwable?>, dispatcher: CoroutineDispatcher = Dispatchers.Default): Continuation<R> {
return object : Continuation<R> {
override val context: CoroutineContext
get() = dispatcher
override fun resumeWith(result: Result<R>) {
onFinished.accept(result.getOrNull(), result.exceptionOrNull())
}
}
}

1
app/src/main/java/awais/instagrabber/viewmodels/DirectInboxViewModel.kt

@ -32,5 +32,6 @@ class DirectInboxViewModel : ViewModel() {
init {
inboxManager.fetchInbox(viewModelScope)
inboxManager.fetchUnseenCount(viewModelScope)
}
}

21
app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt

@ -74,15 +74,15 @@ class DirectThreadViewModel(
}
fun sendText(text: String): LiveData<Resource<Any?>> {
return threadManager.sendText(text)
return threadManager.sendText(text, viewModelScope)
}
fun sendUri(entry: MediaController.MediaEntry): LiveData<Resource<Any?>> {
return threadManager.sendUri(entry)
return threadManager.sendUri(entry, viewModelScope)
}
fun sendUri(uri: Uri): LiveData<Resource<Any?>> {
return threadManager.sendUri(uri)
return threadManager.sendUri(uri, viewModelScope)
}
fun startRecording(): LiveData<Resource<Any?>> {
@ -106,12 +106,15 @@ class DirectThreadViewModel(
MediaUtils.getVoiceInfo(contentResolver, uri, object : OnInfoLoadListener<VideoInfo?> {
override fun onLoad(videoInfo: VideoInfo?) {
if (videoInfo == null) return
threadManager.sendVoice(data,
threadManager.sendVoice(
data,
uri,
result.waveform,
result.samplingFreq,
videoInfo.duration,
videoInfo.size)
videoInfo.size,
viewModelScope,
)
}
override fun onFailure(t: Throwable) {
@ -133,11 +136,11 @@ class DirectThreadViewModel(
}
fun sendReaction(item: DirectItem, emoji: Emoji): LiveData<Resource<Any?>> {
return threadManager.sendReaction(item, emoji)
return threadManager.sendReaction(item, emoji, viewModelScope)
}
fun sendDeleteReaction(itemId: String): LiveData<Resource<Any?>> {
return threadManager.sendDeleteReaction(itemId)
return threadManager.sendDeleteReaction(itemId, viewModelScope)
}
fun unsend(item: DirectItem): LiveData<Resource<Any?>> {
@ -145,7 +148,7 @@ class DirectThreadViewModel(
}
fun sendAnimatedMedia(giphyGif: GiphyGif): LiveData<Resource<Any?>> {
return threadManager.sendAnimatedMedia(giphyGif)
return threadManager.sendAnimatedMedia(giphyGif, viewModelScope)
}
fun getUser(userId: Long): User? {
@ -197,7 +200,7 @@ class DirectThreadViewModel(
return successEventResObjectLiveData
}
}
return threadManager.markAsSeen(directItem)
return threadManager.markAsSeen(directItem, viewModelScope)
}
private val successEventResObjectLiveData: MutableLiveData<Resource<Any?>>

42
app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.kt

@ -53,17 +53,15 @@ class DirectMessagesService private constructor(
return repository.fetchThread(threadId, queryMap)
}
fun fetchUnseenCount(): Call<DirectBadgeCount?> {
return repository.fetchUnseenCount()
}
suspend fun fetchUnseenCount(): DirectBadgeCount = repository.fetchUnseenCount()
fun broadcastText(
suspend fun broadcastText(
clientContext: String,
threadIdOrUserIds: ThreadIdOrUserIds,
text: String,
repliedToItemId: String?,
repliedToClientContext: String?,
): Call<DirectThreadBroadcastResponse?> {
): DirectThreadBroadcastResponse {
val urls = extractUrls(text)
if (urls.isNotEmpty()) {
return broadcastLink(clientContext, threadIdOrUserIds, text, urls, repliedToItemId, repliedToClientContext)
@ -76,14 +74,14 @@ class DirectMessagesService private constructor(
return broadcast(broadcastOptions)
}
private fun broadcastLink(
private suspend fun broadcastLink(
clientContext: String,
threadIdOrUserIds: ThreadIdOrUserIds,
linkText: String,
urls: List<String>,
repliedToItemId: String?,
repliedToClientContext: String?,
): Call<DirectThreadBroadcastResponse?> {
): DirectThreadBroadcastResponse {
val broadcastOptions = LinkBroadcastOptions(clientContext, threadIdOrUserIds, linkText, urls)
if (!repliedToItemId.isNullOrBlank() && !repliedToClientContext.isNullOrBlank()) {
broadcastOptions.repliedToItemId = repliedToItemId
@ -92,70 +90,70 @@ class DirectMessagesService private constructor(
return broadcast(broadcastOptions)
}
fun broadcastPhoto(
suspend fun broadcastPhoto(
clientContext: String,
threadIdOrUserIds: ThreadIdOrUserIds,
uploadId: String,
): Call<DirectThreadBroadcastResponse?> {
): DirectThreadBroadcastResponse {
return broadcast(PhotoBroadcastOptions(clientContext, threadIdOrUserIds, true, uploadId))
}
fun broadcastVideo(
suspend fun broadcastVideo(
clientContext: String,
threadIdOrUserIds: ThreadIdOrUserIds,
uploadId: String,
videoResult: String,
sampled: Boolean,
): Call<DirectThreadBroadcastResponse?> {
): DirectThreadBroadcastResponse {
return broadcast(VideoBroadcastOptions(clientContext, threadIdOrUserIds, videoResult, uploadId, sampled))
}
fun broadcastVoice(
suspend fun broadcastVoice(
clientContext: String,
threadIdOrUserIds: ThreadIdOrUserIds,
uploadId: String,
waveform: List<Float>,
samplingFreq: Int,
): Call<DirectThreadBroadcastResponse?> {
): DirectThreadBroadcastResponse {
return broadcast(VoiceBroadcastOptions(clientContext, threadIdOrUserIds, uploadId, waveform, samplingFreq))
}
fun broadcastStoryReply(
suspend fun broadcastStoryReply(
threadIdOrUserIds: ThreadIdOrUserIds,
text: String,
mediaId: String,
reelId: String,
): Call<DirectThreadBroadcastResponse?> {
): DirectThreadBroadcastResponse {
return broadcast(StoryReplyBroadcastOptions(UUID.randomUUID().toString(), threadIdOrUserIds, text, mediaId, reelId))
}
fun broadcastReaction(
suspend fun broadcastReaction(
clientContext: String,
threadIdOrUserIds: ThreadIdOrUserIds,
itemId: String,
emoji: String?,
delete: Boolean,
): Call<DirectThreadBroadcastResponse?> {
): DirectThreadBroadcastResponse {
return broadcast(ReactionBroadcastOptions(clientContext, threadIdOrUserIds, itemId, emoji, delete))
}
fun broadcastAnimatedMedia(
suspend fun broadcastAnimatedMedia(
clientContext: String,
threadIdOrUserIds: ThreadIdOrUserIds,
giphyGif: GiphyGif,
): Call<DirectThreadBroadcastResponse?> {
): DirectThreadBroadcastResponse {
return broadcast(AnimatedMediaBroadcastOptions(clientContext, threadIdOrUserIds, giphyGif))
}
fun broadcastMediaShare(
suspend fun broadcastMediaShare(
clientContext: String,
threadIdOrUserIds: ThreadIdOrUserIds,
mediaId: String,
): Call<DirectThreadBroadcastResponse?> {
): DirectThreadBroadcastResponse {
return broadcast(MediaShareBroadcastOptions(clientContext, threadIdOrUserIds, mediaId))
}
private fun broadcast(broadcastOptions: BroadcastOptions): Call<DirectThreadBroadcastResponse?> {
private suspend fun broadcast(broadcastOptions: BroadcastOptions): DirectThreadBroadcastResponse {
require(!isEmpty(broadcastOptions.clientContext)) { "Broadcast requires a valid client context value" }
val form = mutableMapOf<String, Any>()
val threadId = broadcastOptions.threadId

Loading…
Cancel
Save