Browse Source

Convert DirectThreadViewModel to kotlin and fix ThreadManager

renovate/org.robolectric-robolectric-4.x
Ammar Githam 4 years ago
parent
commit
3db7b53757
  1. 10
      app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
  2. 2
      app/src/main/java/awais/instagrabber/managers/ThreadManager.kt
  3. 440
      app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt

10
app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java

@ -617,11 +617,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private void setObservers() { private void setObservers() {
threadLiveData = viewModel.getThread(); threadLiveData = viewModel.getThread();
if (threadLiveData == null) { // if (threadLiveData == null) {
final NavController navController = NavHostFragment.findNavController(this); // final NavController navController = NavHostFragment.findNavController(this);
navController.navigateUp(); // navController.navigateUp();
return; // return;
} // }
pendingLiveData = viewModel.isPending(); pendingLiveData = viewModel.isPending();
pendingLiveData.observe(getViewLifecycleOwner(), isPending -> { pendingLiveData.observe(getViewLifecycleOwner(), isPending -> {
if (isPending == null) { if (isPending == null) {

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

@ -73,7 +73,7 @@ class ThreadManager private constructor(
if (inboxResource == null) return@map null if (inboxResource == null) return@map null
val (threads) = inboxResource.data ?: return@map null val (threads) = inboxResource.data ?: return@map null
if (threads.isNullOrEmpty()) return@map null if (threads.isNullOrEmpty()) return@map null
val thread = threads.asSequence().filterNotNull().firstOrNull() val thread = threads.firstOrNull { it.threadId == threadId }
thread?.also { thread?.also {
cursor = thread.oldestCursor cursor = thread.oldestCursor
hasOlder = thread.hasOlder hasOlder = thread.hasOlder

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

@ -1,336 +1,230 @@
package awais.instagrabber.viewmodels; package awais.instagrabber.viewmodels
import android.app.Application
import android.content.ContentResolver
import android.media.MediaScannerConnection
import android.net.Uri
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
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
import awais.instagrabber.utils.extensions.TAG
import java.io.File
import java.util.*
class DirectThreadViewModel(
application: Application,
val threadId: String,
pending: Boolean,
val currentUser: User,
) : AndroidViewModel(application) {
// private val TAG = DirectThreadViewModel::class.java.simpleName
import android.app.Application;
import android.content.ContentResolver;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import awais.instagrabber.customviews.emoji.Emoji;
import awais.instagrabber.managers.DirectMessagesManager;
import awais.instagrabber.managers.InboxManager;
import awais.instagrabber.managers.ThreadManager;
import awais.instagrabber.models.Resource;
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.DirectThreadLastSeenAt;
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
import awais.instagrabber.repositories.responses.giphy.GiphyGif;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DirectoryUtils;
import awais.instagrabber.utils.MediaController;
import awais.instagrabber.utils.MediaUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.VoiceRecorder;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class DirectThreadViewModel extends AndroidViewModel {
private static final String TAG = DirectThreadViewModel.class.getSimpleName();
// private static final String ERROR_INVALID_THREAD = "Invalid thread"; // private static final String ERROR_INVALID_THREAD = "Invalid thread";
private val contentResolver: ContentResolver = application.contentResolver
private val recordingsDir: File = DirectoryUtils.getOutputMediaDirectory(application, "Recordings")
private var voiceRecorder: VoiceRecorder? = null
private lateinit var threadManager: ThreadManager
private final ContentResolver contentResolver; val viewerId: Long
private final File recordingsDir; val threadTitle: LiveData<String?> by lazy { threadManager.threadTitle }
private final Application application; val thread: LiveData<DirectThread?> by lazy { threadManager.thread }
private final long viewerId; val items: LiveData<List<DirectItem>> by lazy {
private final String threadId; Transformations.map(threadManager.items) { it.filter { thread -> thread.hideInThread == 0 } }
private final User currentUser;
private ThreadManager threadManager;
private VoiceRecorder voiceRecorder;
public DirectThreadViewModel(@NonNull final Application application,
@NonNull final String threadId,
final boolean pending,
@NonNull final User currentUser) {
super(application);
this.application = application;
this.threadId = threadId;
this.currentUser = currentUser;
final String cookie = settingsHelper.getString(Constants.COOKIE);
viewerId = CookieUtils.getUserIdFromCookie(cookie);
final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID);
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
if (TextUtils.isEmpty(csrfToken) || viewerId <= 0 || TextUtils.isEmpty(deviceUuid)) {
throw new IllegalArgumentException("User is not logged in!");
}
contentResolver = application.getContentResolver();
recordingsDir = DirectoryUtils.getOutputMediaDirectory(application, "Recordings");
final DirectMessagesManager messagesManager = DirectMessagesManager.INSTANCE;
threadManager = messagesManager.getThreadManager(threadId, pending, currentUser, contentResolver);
threadManager.fetchPendingRequests();
}
public void moveFromPending() {
final DirectMessagesManager messagesManager = DirectMessagesManager.INSTANCE;
messagesManager.moveThreadFromPending(threadId);
threadManager = messagesManager.getThreadManager(threadId, false, currentUser, contentResolver);
}
public void removeThread() {
threadManager.removeThread();
}
public String getThreadId() {
return threadId;
}
public LiveData<String> getThreadTitle() {
return threadManager.getThreadTitle();
}
public LiveData<DirectThread> getThread() {
return threadManager.getThread();
}
public LiveData<List<DirectItem>> getItems() {
return Transformations.map(threadManager.getItems(), items -> items.stream()
.filter(directItem -> directItem.getHideInThread() == 0)
.collect(Collectors.toList()));
}
public LiveData<Resource<Object>> isFetching() {
return threadManager.isFetching();
}
public LiveData<List<User>> getUsers() {
return threadManager.getUsers();
} }
val isFetching: LiveData<Resource<Any?>> by lazy { threadManager.isFetching() }
val users: LiveData<List<User>> by lazy { threadManager.users }
val leftUsers: LiveData<List<User>> by lazy { threadManager.leftUsers }
val pendingRequestsCount: LiveData<Int> by lazy { threadManager.pendingRequestsCount }
val inputMode: LiveData<Int> by lazy { threadManager.inputMode }
val isPending: LiveData<Boolean> by lazy { threadManager.isPending }
val replyToItem: LiveData<DirectItem?> by lazy { threadManager.getReplyToItem() }
public LiveData<List<User>> getLeftUsers() { fun moveFromPending() {
return threadManager.getLeftUsers(); val messagesManager = DirectMessagesManager
messagesManager.moveThreadFromPending(threadId)
threadManager = messagesManager.getThreadManager(threadId, false, currentUser, contentResolver)
} }
public LiveData<Integer> getPendingRequestsCount() { fun removeThread() {
return threadManager.getPendingRequestsCount(); threadManager.removeThread()
} }
public LiveData<Integer> getInputMode() { fun fetchChats() {
return threadManager.getInputMode(); threadManager.fetchChats()
} }
public LiveData<Boolean> isPending() { fun refreshChats() {
return threadManager.isPending(); threadManager.refreshChats()
} }
public long getViewerId() { fun sendText(text: String): LiveData<Resource<Any?>> {
return viewerId; return threadManager.sendText(text)
} }
public LiveData<DirectItem> getReplyToItem() { fun sendUri(entry: MediaController.MediaEntry): LiveData<Resource<Any?>> {
return threadManager.getReplyToItem(); return threadManager.sendUri(entry)
} }
public void fetchChats() { fun sendUri(uri: Uri): LiveData<Resource<Any?>> {
threadManager.fetchChats(); return threadManager.sendUri(uri)
} }
public void refreshChats() { fun startRecording(): LiveData<Resource<Any?>> {
threadManager.refreshChats(); val data = MutableLiveData<Resource<Any?>>()
} voiceRecorder = VoiceRecorder(recordingsDir, object : VoiceRecorderCallback {
override fun onStart() {}
public LiveData<Resource<Object>> sendText(final String text) { override fun onComplete(result: VoiceRecordingResult) {
return threadManager.sendText(text); Log.d(TAG, "onComplete: recording complete. Scanning file...")
}
public LiveData<Resource<Object>> sendUri(final MediaController.MediaEntry entry) {
return threadManager.sendUri(entry);
}
public LiveData<Resource<Object>> sendUri(final Uri uri) {
return threadManager.sendUri(uri);
}
public LiveData<Resource<Object>> startRecording() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
voiceRecorder = new VoiceRecorder(recordingsDir, new VoiceRecorder.VoiceRecorderCallback() {
@Override
public void onStart() {}
@Override
public void onComplete(final VoiceRecorder.VoiceRecordingResult result) {
Log.d(TAG, "onComplete: recording complete. Scanning file...");
MediaScannerConnection.scanFile( MediaScannerConnection.scanFile(
application, getApplication(),
new String[]{result.getFile().getAbsolutePath()}, arrayOf(result.file.absolutePath),
new String[]{result.getMimeType()}, arrayOf(result.mimeType)
(path, uri) -> { ) { _: String?, uri: Uri? ->
if (uri == null) { if (uri == null) {
final String msg = "Scan failed!"; val msg = "Scan failed!"
Log.e(TAG, msg); Log.e(TAG, msg)
data.postValue(Resource.error(msg, null)); data.postValue(error(msg, null))
return; return@scanFile
} }
Log.d(TAG, "onComplete: scan complete"); Log.d(TAG, "onComplete: scan complete")
MediaUtils.getVoiceInfo(contentResolver, uri, new MediaUtils.OnInfoLoadListener<MediaUtils.VideoInfo>() { MediaUtils.getVoiceInfo(contentResolver, uri, object : OnInfoLoadListener<VideoInfo?> {
@Override override fun onLoad(videoInfo: VideoInfo?) {
public void onLoad(@Nullable final MediaUtils.VideoInfo videoInfo) { if (videoInfo == null) return
if (videoInfo == null) return; threadManager.sendVoice(data,
threadManager.sendVoice(data, uri,
uri, result.waveform,
result.getWaveform(), result.samplingFreq,
result.getSamplingFreq(), videoInfo.duration,
videoInfo == null ? 0 : videoInfo.duration, videoInfo.size)
videoInfo == null ? 0 : videoInfo.size);
}
@Override
public void onFailure(final Throwable t) {
data.postValue(Resource.error(t.getMessage(), null));
}
});
} }
);
}
@Override
public void onCancel() {
override fun onFailure(t: Throwable) {
data.postValue(error(t.message, null))
}
})
}
} }
});
voiceRecorder.startRecording();
return data;
}
public void stopRecording(final boolean delete) { override fun onCancel() {}
if (voiceRecorder == null) return; })
voiceRecorder.stopRecording(delete); voiceRecorder?.startRecording()
voiceRecorder = null; return data
} }
public LiveData<Resource<Object>> sendReaction(@NonNull final DirectItem item, @NonNull final Emoji emoji) { fun stopRecording(delete: Boolean) {
return threadManager.sendReaction(item, emoji); voiceRecorder?.stopRecording(delete)
voiceRecorder = null
} }
public LiveData<Resource<Object>> sendDeleteReaction(@NonNull final String itemId) { fun sendReaction(item: DirectItem, emoji: Emoji): LiveData<Resource<Any?>> {
return threadManager.sendDeleteReaction(itemId); return threadManager.sendReaction(item, emoji)
} }
public LiveData<Resource<Object>> unsend(final DirectItem item) { fun sendDeleteReaction(itemId: String): LiveData<Resource<Any?>> {
return threadManager.unsend(item); return threadManager.sendDeleteReaction(itemId)
} }
public LiveData<Resource<Object>> sendAnimatedMedia(@NonNull final GiphyGif giphyGif) { fun unsend(item: DirectItem): LiveData<Resource<Any?>> {
return threadManager.sendAnimatedMedia(giphyGif); return threadManager.unsend(item)
} }
public User getCurrentUser() { fun sendAnimatedMedia(giphyGif: GiphyGif): LiveData<Resource<Any?>> {
return currentUser; return threadManager.sendAnimatedMedia(giphyGif)
} }
@Nullable fun getUser(userId: Long): User? {
public User getUser(final long userId) { var match: User? = null
final LiveData<List<User>> users = getUsers(); users.value?.let { match = it.firstOrNull { user -> user.pk == userId } }
User match = null;
if (users != null && users.getValue() != null) {
final List<User> userList = users.getValue();
match = userList.stream()
.filter(Objects::nonNull)
.filter(user -> user.getPk() == userId)
.findFirst()
.orElse(null);
}
if (match == null) { if (match == null) {
final LiveData<List<User>> leftUsers = getLeftUsers(); leftUsers.value?.let { match = it.firstOrNull { user -> user.pk == userId } }
if (leftUsers != null && leftUsers.getValue() != null) {
final List<User> userList = leftUsers.getValue();
match = userList.stream()
.filter(Objects::nonNull)
.filter(user -> user.getPk() == userId)
.findFirst()
.orElse(null);
}
} }
return match; return match
} }
public void forward(final Set<RankedRecipient> recipients, final DirectItem itemToForward) { fun forward(recipients: Set<RankedRecipient>, itemToForward: DirectItem) {
threadManager.forward(recipients, itemToForward); threadManager.forward(recipients, itemToForward)
} }
public void forward(final RankedRecipient recipient, final DirectItem itemToForward) { fun forward(recipient: RankedRecipient, itemToForward: DirectItem) {
threadManager.forward(recipient, itemToForward); threadManager.forward(recipient, itemToForward)
} }
public void setReplyToItem(@Nullable final DirectItem item) { fun setReplyToItem(item: DirectItem?) {
// Log.d(TAG, "setReplyToItem: " + item); // Log.d(TAG, "setReplyToItem: " + item);
threadManager.setReplyToItem(item); threadManager.setReplyToItem(item)
} }
public LiveData<Resource<Object>> acceptRequest() { fun acceptRequest(): LiveData<Resource<Any?>> {
return threadManager.acceptRequest(); return threadManager.acceptRequest()
} }
public LiveData<Resource<Object>> declineRequest() { fun declineRequest(): LiveData<Resource<Any?>> {
return threadManager.declineRequest(); return threadManager.declineRequest()
} }
public LiveData<Resource<Object>> markAsSeen() { fun markAsSeen(): LiveData<Resource<Any?>> {
if (currentUser == null) { val thread = thread.value ?: return successEventResObjectLiveData
return getSuccessEventResObjectLiveData(); val items = thread.items
} if (items.isNullOrEmpty()) return successEventResObjectLiveData
final DirectThread thread = getThread().getValue(); val directItem = items.firstOrNull { (_, userId) -> userId != currentUser.pk } ?: return successEventResObjectLiveData
if (thread == null) { val lastSeenAt = thread.lastSeenAt
return getSuccessEventResObjectLiveData();
}
final List<DirectItem> items = thread.getItems();
if (items == null || items.isEmpty()) {
return getSuccessEventResObjectLiveData();
}
final Optional<DirectItem> itemOptional = items.stream()
.filter(item -> item.getUserId() != currentUser.getPk())
.findFirst();
if (!itemOptional.isPresent()) {
return getSuccessEventResObjectLiveData();
}
final DirectItem directItem = itemOptional.get();
final Map<Long, DirectThreadLastSeenAt> lastSeenAt = thread.getLastSeenAt();
if (lastSeenAt != null) { if (lastSeenAt != null) {
final DirectThreadLastSeenAt seenAt = lastSeenAt.get(currentUser.getPk()); val seenAt = lastSeenAt[currentUser.pk] ?: return successEventResObjectLiveData
try { try {
if (seenAt != null val timestamp = seenAt.timestamp ?: return successEventResObjectLiveData
&& (Objects.equals(seenAt.getItemId(), directItem.getItemId()) val itemIdMatches = seenAt.itemId == directItem.itemId
|| Long.parseLong(seenAt.getTimestamp()) >= directItem.getTimestamp())) { val timestampMatches = timestamp.toLong() >= directItem.getTimestamp()
return getSuccessEventResObjectLiveData(); if (itemIdMatches || timestampMatches) {
return successEventResObjectLiveData
} }
} catch (Exception ignored) { } catch (ignored: Exception) {
return getSuccessEventResObjectLiveData(); return successEventResObjectLiveData
} }
} }
return threadManager.markAsSeen(directItem); return threadManager.markAsSeen(directItem)
} }
@NonNull private val successEventResObjectLiveData: MutableLiveData<Resource<Any?>>
private MutableLiveData<Resource<Object>> getSuccessEventResObjectLiveData() { get() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); val data = MutableLiveData<Resource<Any?>>()
data.postValue(Resource.success(new Object())); data.postValue(success(Any()))
return data; return data
} }
public void deleteThreadIfRequired() { fun deleteThreadIfRequired() {
final DirectThread thread = getThread().getValue(); val thread = thread.value ?: return
if (thread == null) return; if (thread.isTemp && thread.items.isNullOrEmpty()) {
if (thread.isTemp() && (thread.getItems() == null || thread.getItems().isEmpty())) { val inboxManager = inboxManager
final InboxManager inboxManager = DirectMessagesManager.INSTANCE.getInboxManager(); inboxManager.removeThread(threadId)
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()
}
} }
|||||||
100:0
Loading…
Cancel
Save