diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index 6a48fecb..56fba457 100755
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -36,5 +36,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 5a6611dc..01d39a3a 100755
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -61,7 +61,7 @@ dependencies {
def appcompat_version = "1.2.0"
def nav_version = '2.3.2'
- implementation 'com.google.android.material:material:1.3.0-alpha04'
+ implementation 'com.google.android.material:material:1.3.0-beta01'
implementation 'com.google.android.exoplayer:exoplayer-core:2.12.0'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.12.0'
@@ -81,13 +81,22 @@ dependencies {
implementation 'com.google.guava:guava:27.0.1-android'
// Room
- def room_version = "2.2.5"
+ def room_version = "2.2.6"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-guava:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
- // implementation 'com.github.hendrawd:StorageUtil:1.1.0'
- implementation 'com.github.ammargitham:AutoLinkTextViewV2:master-SNAPSHOT'
+ // CameraX
+ def camerax_version = "1.0.0-rc01"
+ implementation "androidx.camera:camera-camera2:$camerax_version"
+ implementation "androidx.camera:camera-lifecycle:$camerax_version"
+ implementation "androidx.camera:camera-view:1.0.0-alpha20"
+
+
+ // EmojiCompat
+ def emoji_compat_version = "1.1.0"
+ implementation "androidx.emoji:emoji:$emoji_compat_version"
+ implementation "androidx.emoji:emoji-appcompat:$emoji_compat_version"
implementation 'org.jsoup:jsoup:1.13.1'
implementation 'com.facebook.fresco:fresco:2.3.0'
@@ -99,8 +108,12 @@ dependencies {
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'org.apache.commons:commons-imaging:1.0-alpha2'
+ implementation 'com.ibm.icu:icu4j:68.1'
+ implementation 'com.github.ammargitham:AutoLinkTextViewV2:master-SNAPSHOT'
+ implementation 'com.github.ammargitham:uCrop:2.3-native-beta-2'
+ implementation 'com.github.ammargitham:android-gpuimage:2.1.1-beta4'
- debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
+ debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.0'
}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index aeb70430..ff15034a 100755
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -23,6 +23,7 @@
#noinspection ShrinkerUnresolvedReference
#-keep class !com.google.android.exoplayer2.**, ** { *; }
-#-keep class !awais.instagrabber.** { *; }
+-dontobfuscate
--dontobfuscate
\ No newline at end of file
+# prevent shrinking retrofit response entities
+-keep class awais.instagrabber.repositories.responses.** { *; }
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 556de192..ba525c14 100755
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,6 +6,13 @@
+
+
+
+
+
+ android:windowSoftInputMode="adjustResize">
@@ -114,6 +121,15 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.MainActivity" />
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java b/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java
index 874b425f..c5cd0d69 100644
--- a/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java
+++ b/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java
@@ -3,6 +3,7 @@ package awais.instagrabber;
import android.app.Application;
import android.content.ClipboardManager;
import android.content.Context;
+import android.os.Handler;
import android.util.Log;
import com.facebook.drawee.backends.pipeline.Fresco;
@@ -15,10 +16,13 @@ import java.util.UUID;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.LocaleUtils;
import awais.instagrabber.utils.SettingsHelper;
+import awais.instagrabber.utils.TextUtils;
import awaisomereport.CrashReporter;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.CookieUtils.NET_COOKIE_MANAGER;
+import static awais.instagrabber.utils.Utils.applicationHandler;
+import static awais.instagrabber.utils.Utils.cacheDir;
import static awais.instagrabber.utils.Utils.clipboardManager;
import static awais.instagrabber.utils.Utils.datetimeParser;
import static awais.instagrabber.utils.Utils.logCollector;
@@ -59,6 +63,14 @@ public final class InstaGrabberApplication extends Application {
if (settingsHelper == null)
settingsHelper = new SettingsHelper(this);
+ if (applicationHandler == null) {
+ applicationHandler = new Handler(getApplicationContext().getMainLooper());
+ }
+
+ if (cacheDir == null) {
+ cacheDir = getCacheDir().getAbsolutePath();
+ }
+
LocaleUtils.setLocale(getBaseContext());
if (clipboardManager == null)
@@ -70,6 +82,8 @@ public final class InstaGrabberApplication extends Application {
settingsHelper.getString(Constants.CUSTOM_DATE_TIME_FORMAT) :
settingsHelper.getString(Constants.DATE_TIME_FORMAT), LocaleUtils.getCurrentLocale());
- settingsHelper.putString(Constants.DEVICE_UUID, UUID.randomUUID().toString());
+ if (TextUtils.isEmpty(settingsHelper.getString(Constants.DEVICE_UUID))) {
+ settingsHelper.putString(Constants.DEVICE_UUID, UUID.randomUUID().toString());
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/activities/CameraActivity.java b/app/src/main/java/awais/instagrabber/activities/CameraActivity.java
new file mode 100644
index 00000000..169efb27
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/activities/CameraActivity.java
@@ -0,0 +1,276 @@
+package awais.instagrabber.activities;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.hardware.display.DisplayManager;
+import android.media.MediaScannerConnection;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.webkit.MimeTypeMap;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.core.CameraInfoUnavailableException;
+import androidx.camera.core.CameraSelector;
+import androidx.camera.core.ImageCapture;
+import androidx.camera.core.ImageCaptureException;
+import androidx.camera.core.Preview;
+import androidx.camera.lifecycle.ProcessCameraProvider;
+import androidx.core.content.ContextCompat;
+
+import com.google.common.io.Files;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import awais.instagrabber.databinding.ActivityCameraBinding;
+import awais.instagrabber.utils.DirectoryUtils;
+import awais.instagrabber.utils.PermissionUtils;
+import awais.instagrabber.utils.Utils;
+
+public class CameraActivity extends BaseLanguageActivity {
+ private static final String TAG = CameraActivity.class.getSimpleName();
+ private static final int CAMERA_REQUEST_CODE = 100;
+ private static final String FILE_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS";
+ private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(FILE_FORMAT, Locale.US);
+
+ private ActivityCameraBinding binding;
+ private ImageCapture imageCapture;
+ private File outputDirectory;
+ private ExecutorService cameraExecutor;
+ private int displayId = -1;
+
+ private final DisplayManager.DisplayListener displayListener = new DisplayManager.DisplayListener() {
+ @Override
+ public void onDisplayAdded(final int displayId) {}
+
+ @Override
+ public void onDisplayRemoved(final int displayId) {}
+
+ @Override
+ public void onDisplayChanged(final int displayId) {
+ if (displayId == CameraActivity.this.displayId) {
+ imageCapture.setTargetRotation(binding.getRoot().getDisplay().getRotation());
+ }
+ }
+ };
+ private DisplayManager displayManager;
+ private ProcessCameraProvider cameraProvider;
+ private int lensFacing;
+
+ @Override
+ protected void onCreate(@Nullable final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = ActivityCameraBinding.inflate(LayoutInflater.from(getBaseContext()));
+ setContentView(binding.getRoot());
+ Utils.transparentStatusBar(this, true, false);
+ displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
+ outputDirectory = DirectoryUtils.getOutputMediaDirectory(this, "Camera");
+ cameraExecutor = Executors.newSingleThreadExecutor();
+ displayManager.registerDisplayListener(displayListener, null);
+ binding.viewFinder.post(() -> {
+ displayId = binding.viewFinder.getDisplay().getDisplayId();
+ updateUi();
+ checkPermissionsAndSetupCamera();
+ });
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ // Make sure that all permissions are still present, since the
+ // user could have removed them while the app was in paused state.
+ if (!PermissionUtils.hasCameraPerms(this)) {
+ PermissionUtils.requestCameraPerms(this, CAMERA_REQUEST_CODE);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull final Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ // Redraw the camera UI controls
+ updateUi();
+
+ // Enable or disable switching between cameras
+ updateCameraSwitchButton();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ Utils.transparentStatusBar(this, false, false);
+ cameraExecutor.shutdown();
+ displayManager.unregisterDisplayListener(displayListener);
+ }
+
+ private void updateUi() {
+ binding.cameraCaptureButton.setOnClickListener(v -> takePhoto());
+ // Disable the button until the camera is set up
+ binding.switchCamera.setEnabled(false);
+ // Listener for button used to switch cameras. Only called if the button is enabled
+ binding.switchCamera.setOnClickListener(v -> {
+ lensFacing = CameraSelector.LENS_FACING_FRONT == lensFacing ? CameraSelector.LENS_FACING_BACK
+ : CameraSelector.LENS_FACING_FRONT;
+ // Re-bind use cases to update selected camera
+ bindCameraUseCases();
+ });
+ binding.close.setOnClickListener(v -> {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ });
+ }
+
+ private void checkPermissionsAndSetupCamera() {
+ if (PermissionUtils.hasCameraPerms(this)) {
+ setupCamera();
+ return;
+ }
+ PermissionUtils.requestCameraPerms(this, CAMERA_REQUEST_CODE);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
+ if (requestCode == CAMERA_REQUEST_CODE) {
+ if (PermissionUtils.hasCameraPerms(this)) {
+ setupCamera();
+ }
+ }
+ }
+
+ private void setupCamera() {
+ final ListenableFuture cameraProviderFuture = ProcessCameraProvider.getInstance(this);
+ cameraProviderFuture.addListener(() -> {
+ try {
+ cameraProvider = cameraProviderFuture.get();
+ // Select lensFacing depending on the available cameras
+ lensFacing = -1;
+ if (hasBackCamera()) {
+ lensFacing = CameraSelector.LENS_FACING_BACK;
+ } else if (hasFrontCamera()) {
+ lensFacing = CameraSelector.LENS_FACING_FRONT;
+ }
+ if (lensFacing == -1) {
+ throw new IllegalStateException("Back and front camera are unavailable");
+ }
+ // Enable or disable switching between cameras
+ updateCameraSwitchButton();
+ // Build and bind the camera use cases
+ bindCameraUseCases();
+ } catch (ExecutionException | InterruptedException | CameraInfoUnavailableException e) {
+ Log.e(TAG, "setupCamera: ", e);
+ }
+
+ }, ContextCompat.getMainExecutor(this));
+ }
+
+ private void bindCameraUseCases() {
+ final int rotation = binding.viewFinder.getDisplay().getRotation();
+
+ // CameraSelector
+ final CameraSelector cameraSelector = new CameraSelector.Builder()
+ .requireLensFacing(lensFacing)
+ .build();
+
+ // Preview
+ final Preview preview = new Preview.Builder()
+ // Set initial target rotation
+ .setTargetRotation(rotation)
+ .build();
+
+ // ImageCapture
+ imageCapture = new ImageCapture.Builder()
+ .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
+ // Set initial target rotation, we will have to call this again if rotation changes
+ // during the lifecycle of this use case
+ .setTargetRotation(rotation)
+ .build();
+
+ cameraProvider.unbindAll();
+ cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture);
+
+ preview.setSurfaceProvider(binding.viewFinder.getSurfaceProvider());
+ }
+
+ private void takePhoto() {
+ if (imageCapture == null) return;
+ final File photoFile = new File(outputDirectory, SIMPLE_DATE_FORMAT.format(System.currentTimeMillis()) + ".jpg");
+ final ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(photoFile).build();
+ imageCapture.takePicture(
+ outputFileOptions,
+ cameraExecutor,
+ new ImageCapture.OnImageSavedCallback() {
+ @Override
+ public void onImageSaved(@NonNull final ImageCapture.OutputFileResults outputFileResults) {
+ final Uri uri = Uri.fromFile(photoFile);
+ //noinspection UnstableApiUsage
+ final String mimeType = MimeTypeMap.getSingleton()
+ .getMimeTypeFromExtension(Files.getFileExtension(photoFile.getName()));
+ MediaScannerConnection.scanFile(
+ CameraActivity.this,
+ new String[]{photoFile.getAbsolutePath()},
+ new String[]{mimeType},
+ (path, uri1) -> {
+ Log.d(TAG, "onImageSaved: scan complete");
+ final Intent intent = new Intent();
+ intent.setData(uri1);
+ setResult(Activity.RESULT_OK, intent);
+ finish();
+ });
+ Log.d(TAG, "onImageSaved: " + uri);
+ }
+
+ @Override
+ public void onError(@NonNull final ImageCaptureException exception) {
+ Log.e(TAG, "onError: ", exception);
+ }
+ }
+ );
+ // We can only change the foreground Drawable using API level 23+ API
+ // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ // // Display flash animation to indicate that photo was captured
+ // final ConstraintLayout container = binding.getRoot();
+ // container.postDelayed(() -> {
+ // container.setForeground(new ColorDrawable(Color.WHITE));
+ // container.postDelayed(() -> container.setForeground(null), 50);
+ // }, 100);
+ // }
+ }
+
+ /**
+ * Enabled or disabled a button to switch cameras depending on the available cameras
+ */
+ private void updateCameraSwitchButton() {
+ try {
+ binding.switchCamera.setEnabled(hasBackCamera() && hasFrontCamera());
+ } catch (CameraInfoUnavailableException e) {
+ binding.switchCamera.setEnabled(false);
+ }
+ }
+
+ /**
+ * Returns true if the device has an available back camera. False otherwise
+ */
+ private boolean hasBackCamera() throws CameraInfoUnavailableException {
+ if (cameraProvider == null) return false;
+ return cameraProvider.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA);
+ }
+
+ /**
+ * Returns true if the device has an available front camera. False otherwise
+ */
+ private boolean hasFrontCamera() throws CameraInfoUnavailableException {
+ if (cameraProvider == null) {
+ return false;
+ }
+ return cameraProvider.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java
index d488b69b..08a0322a 100644
--- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java
+++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java
@@ -1,5 +1,6 @@
package awais.instagrabber.activities;
+import android.animation.LayoutTransition;
import android.annotation.SuppressLint;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -25,11 +26,15 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.NotificationManagerCompat;
+import androidx.core.provider.FontRequest;
+import androidx.emoji.text.EmojiCompat;
+import androidx.emoji.text.FontRequestEmojiCompatConfig;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData;
@@ -53,6 +58,7 @@ import awais.instagrabber.R;
import awais.instagrabber.adapters.SuggestionsAdapter;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.asyncs.SuggestionsFetcher;
+import awais.instagrabber.customviews.emoji.EmojiVariantManager;
import awais.instagrabber.databinding.ActivityMainBinding;
import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.fragments.main.FeedFragment;
@@ -61,11 +67,13 @@ import awais.instagrabber.models.IntentModel;
import awais.instagrabber.models.SuggestionModel;
import awais.instagrabber.models.enums.SuggestionType;
import awais.instagrabber.services.ActivityCheckerService;
+import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.FlavorTown;
import awais.instagrabber.utils.IntentUtils;
import awais.instagrabber.utils.TextUtils;
+import awais.instagrabber.utils.emoji.EmojiParser;
import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController;
import static awais.instagrabber.utils.Utils.settingsHelper;
@@ -139,6 +147,12 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
bindActivityCheckerService();
}
getSupportFragmentManager().addOnBackStackChangedListener(this);
+ // Initialise the internal map
+ AppExecutors.getInstance().tasksThread().execute(() -> {
+ EmojiParser.getInstance();
+ EmojiVariantManager.getInstance();
+ });
+ initEmojiCompat();
}
@Override
@@ -449,6 +463,14 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
if (navController == null) return;
NavigationUI.setupWithNavController(toolbar, navController);
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
+ if (destination.getId() == R.id.directMessagesThreadFragment && arguments != null) {
+ // Set the thread title earlier for better ux
+ final String title = arguments.getString("title");
+ final ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null && !TextUtils.isEmpty(title)) {
+ actionBar.setTitle(title);
+ }
+ }
// below is a hack to check if we are at the end of the current stack, to setup the search view
binding.appBarLayout.setExpanded(true, true);
final int destinationId = destination.getId();
@@ -637,4 +659,40 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
public CollapsingToolbarLayout getCollapsingToolbarView() {
return binding.collapsingToolbarLayout;
}
+
+ public AppBarLayout getAppbarLayout() {
+ return binding.appBarLayout;
+ }
+
+ public void removeLayoutTransition() {
+ binding.getRoot().setLayoutTransition(null);
+ }
+
+ public void setLayoutTransition() {
+ binding.getRoot().setLayoutTransition(new LayoutTransition());
+ }
+
+ private void initEmojiCompat() {
+ // Use a downloadable font for EmojiCompat
+ final FontRequest fontRequest = new FontRequest(
+ "com.google.android.gms.fonts",
+ "com.google.android.gms",
+ "Noto Color Emoji Compat",
+ R.array.com_google_android_gms_fonts_certs);
+ final EmojiCompat.Config config = new FontRequestEmojiCompatConfig(getApplicationContext(), fontRequest);
+ config.setReplaceAll(true)
+ .setUseEmojiAsDefaultStyle(true)
+ .registerInitCallback(new EmojiCompat.InitCallback() {
+ @Override
+ public void onInitialized() {
+ Log.i(TAG, "EmojiCompat initialized");
+ }
+
+ @Override
+ public void onFailed(@Nullable Throwable throwable) {
+ Log.e(TAG, "EmojiCompat initialization failed", throwable);
+ }
+ });
+ EmojiCompat.init(config);
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java
new file mode 100644
index 00000000..7b725e91
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java
@@ -0,0 +1,329 @@
+package awais.instagrabber.adapters;
+
+import android.text.format.DateFormat;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.AdapterListUpdateCallback;
+import androidx.recyclerview.widget.AsyncDifferConfig;
+import androidx.recyclerview.widget.AsyncListDiffer;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemActionLogViewHolder;
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemAnimatedMediaViewHolder;
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemDefaultViewHolder;
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemLikeViewHolder;
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemLinkViewHolder;
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemMediaShareViewHolder;
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemMediaViewHolder;
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemPlaceholderViewHolder;
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemProfileViewHolder;
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemRavenMediaViewHolder;
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemReelShareViewHolder;
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemStoryShareViewHolder;
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemTextViewHolder;
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemVideoCallEventViewHolder;
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemViewHolder;
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemVoiceMediaViewHolder;
+import awais.instagrabber.databinding.LayoutDmActionLogBinding;
+import awais.instagrabber.databinding.LayoutDmAnimatedMediaBinding;
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.databinding.LayoutDmHeaderBinding;
+import awais.instagrabber.databinding.LayoutDmLikeBinding;
+import awais.instagrabber.databinding.LayoutDmLinkBinding;
+import awais.instagrabber.databinding.LayoutDmMediaBinding;
+import awais.instagrabber.databinding.LayoutDmMediaShareBinding;
+import awais.instagrabber.databinding.LayoutDmProfileBinding;
+import awais.instagrabber.databinding.LayoutDmRavenMediaBinding;
+import awais.instagrabber.databinding.LayoutDmReelShareBinding;
+import awais.instagrabber.databinding.LayoutDmStoryShareBinding;
+import awais.instagrabber.databinding.LayoutDmTextBinding;
+import awais.instagrabber.databinding.LayoutDmVoiceMediaBinding;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.models.enums.DirectItemType;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.utils.DateUtils;
+
+public final class DirectItemsAdapter extends RecyclerView.Adapter {
+ private static final String TAG = DirectItemsAdapter.class.getSimpleName();
+
+ private final ProfileModel currentUser;
+ private DirectThread thread;
+ private final AsyncListDiffer differ;
+
+ private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() {
+ @Override
+ public boolean areItemsTheSame(@NonNull final DirectItemOrHeader oldItem, @NonNull final DirectItemOrHeader newItem) {
+ final boolean bothHeaders = oldItem.isHeader() && newItem.isHeader();
+ final boolean bothItems = !oldItem.isHeader() && !newItem.isHeader();
+ boolean areSameType = bothHeaders || bothItems;
+ if (!areSameType) return false;
+ if (bothHeaders) {
+ return oldItem.date.equals(newItem.date);
+ }
+ if (oldItem.item != null && newItem.item != null) {
+ return oldItem.item.getClientContext().equals(newItem.item.getClientContext());
+ }
+ return false;
+ }
+
+ @Override
+ public boolean areContentsTheSame(@NonNull final DirectItemOrHeader oldItem, @NonNull final DirectItemOrHeader newItem) {
+ final boolean bothHeaders = oldItem.isHeader() && newItem.isHeader();
+ final boolean bothItems = !oldItem.isHeader() && !newItem.isHeader();
+ boolean areSameType = bothHeaders || bothItems;
+ if (!areSameType) return false;
+ if (bothHeaders) {
+ return oldItem.date.equals(newItem.date);
+ }
+ return oldItem.item.getTimestamp() == newItem.item.getTimestamp()
+ && oldItem.item.isPending() == newItem.item.isPending(); // todo need to be more specific
+ }
+ };
+
+ public DirectItemsAdapter(@NonNull final ProfileModel currentUser) {
+ this.currentUser = currentUser;
+ differ = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
+ new AsyncDifferConfig.Builder<>(diffCallback).build());
+ // this.onClickListener = onClickListener;
+ // this.mentionClickListener = mentionClickListener;
+ }
+
+ @NonNull
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
+ final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
+ if (type == -1) {
+ // header
+ return new HeaderViewHolder(LayoutDmHeaderBinding.inflate(layoutInflater, parent, false));
+ }
+ final LayoutDmBaseBinding baseBinding = LayoutDmBaseBinding.inflate(layoutInflater, parent, false);
+ final DirectItemType directItemType = DirectItemType.valueOf(type);
+ return getItemViewHolder(layoutInflater, baseBinding, directItemType);
+ }
+
+ private DirectItemViewHolder getItemViewHolder(final LayoutInflater layoutInflater,
+ final LayoutDmBaseBinding baseBinding,
+ final DirectItemType directItemType) {
+ switch (directItemType) {
+ case TEXT: {
+ final LayoutDmTextBinding binding = LayoutDmTextBinding.inflate(layoutInflater, baseBinding.message, false);
+ return new DirectItemTextViewHolder(baseBinding, binding, currentUser, thread, null, null);
+ }
+ case LIKE: {
+ final LayoutDmLikeBinding binding = LayoutDmLikeBinding.inflate(layoutInflater, baseBinding.message, false);
+ return new DirectItemLikeViewHolder(baseBinding, binding, currentUser, thread, null, null);
+ }
+ case LINK: {
+ final LayoutDmLinkBinding binding = LayoutDmLinkBinding.inflate(layoutInflater, baseBinding.message, false);
+ return new DirectItemLinkViewHolder(baseBinding, binding, currentUser, thread, null, null);
+ }
+ case ACTION_LOG: {
+ final LayoutDmActionLogBinding binding = LayoutDmActionLogBinding.inflate(layoutInflater, baseBinding.message, false);
+ return new DirectItemActionLogViewHolder(baseBinding, binding, currentUser, thread, null, null);
+ }
+ case VIDEO_CALL_EVENT: {
+ final LayoutDmActionLogBinding binding = LayoutDmActionLogBinding.inflate(layoutInflater, baseBinding.message, false);
+ return new DirectItemVideoCallEventViewHolder(baseBinding, binding, currentUser, thread, null, null);
+ }
+ case PLACEHOLDER: {
+ final LayoutDmTextBinding binding = LayoutDmTextBinding.inflate(layoutInflater, baseBinding.message, false);
+ return new DirectItemPlaceholderViewHolder(baseBinding, binding, currentUser, thread, null, null);
+ }
+ case ANIMATED_MEDIA: {
+ final LayoutDmAnimatedMediaBinding binding = LayoutDmAnimatedMediaBinding.inflate(layoutInflater, baseBinding.message, false);
+ return new DirectItemAnimatedMediaViewHolder(baseBinding, binding, currentUser, thread, null, null);
+ }
+ case VOICE_MEDIA: {
+ final LayoutDmVoiceMediaBinding binding = LayoutDmVoiceMediaBinding.inflate(layoutInflater, baseBinding.message, false);
+ return new DirectItemVoiceMediaViewHolder(baseBinding, binding, currentUser, thread, null, null);
+ }
+ case LOCATION:
+ case PROFILE: {
+ final LayoutDmProfileBinding binding = LayoutDmProfileBinding.inflate(layoutInflater, baseBinding.message, false);
+ return new DirectItemProfileViewHolder(baseBinding, binding, currentUser, thread, null, null);
+ }
+ case MEDIA: {
+ final LayoutDmMediaBinding binding = LayoutDmMediaBinding.inflate(layoutInflater, baseBinding.message, false);
+ return new DirectItemMediaViewHolder(baseBinding, binding, currentUser, thread, null, null);
+ }
+ case CLIP:
+ case FELIX_SHARE:
+ case MEDIA_SHARE: {
+ final LayoutDmMediaShareBinding binding = LayoutDmMediaShareBinding.inflate(layoutInflater, baseBinding.message, false);
+ return new DirectItemMediaShareViewHolder(baseBinding, binding, currentUser, thread, null, null);
+ }
+ case STORY_SHARE: {
+ final LayoutDmStoryShareBinding binding = LayoutDmStoryShareBinding.inflate(layoutInflater, baseBinding.message, false);
+ return new DirectItemStoryShareViewHolder(baseBinding, binding, currentUser, thread, null, null);
+ }
+ case REEL_SHARE: {
+ final LayoutDmReelShareBinding binding = LayoutDmReelShareBinding.inflate(layoutInflater, baseBinding.message, false);
+ return new DirectItemReelShareViewHolder(baseBinding, binding, currentUser, thread, null, null);
+ }
+ case RAVEN_MEDIA: {
+ final LayoutDmRavenMediaBinding binding = LayoutDmRavenMediaBinding.inflate(layoutInflater, baseBinding.message, false);
+ return new DirectItemRavenMediaViewHolder(baseBinding, binding, currentUser, thread, null, null);
+ }
+ default: {
+ final LayoutDmTextBinding binding = LayoutDmTextBinding.inflate(layoutInflater, baseBinding.message, false);
+ return new DirectItemDefaultViewHolder(baseBinding, binding, currentUser, thread, null, null);
+ }
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
+ final DirectItemOrHeader itemOrHeader = getItem(position);
+ if (itemOrHeader.isHeader()) {
+ ((HeaderViewHolder) holder).bind(itemOrHeader.date);
+ return;
+ }
+ if (thread == null) return;
+ ((DirectItemViewHolder) holder).bind(itemOrHeader.item);
+ }
+
+ protected DirectItemOrHeader getItem(int position) {
+ return differ.getCurrentList().get(position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return differ.getCurrentList().size();
+ }
+
+ @Override
+ public int getItemViewType(final int position) {
+ final DirectItemOrHeader itemOrHeader = getItem(position);
+ if (itemOrHeader.isHeader()) {
+ return -1;
+ }
+ return itemOrHeader.item.getItemType().getId();
+ }
+
+ @Override
+ public long getItemId(final int position) {
+ final DirectItemOrHeader itemOrHeader = getItem(position);
+ if (itemOrHeader.isHeader()) {
+ return itemOrHeader.date.hashCode();
+ }
+ return itemOrHeader.item.getClientContext().hashCode();
+ }
+
+ public void setThread(final DirectThread thread) {
+ if (thread == null) return;
+ this.thread = thread;
+ }
+
+ public void submitList(@Nullable final List list) {
+ if (list == null) {
+ differ.submitList(null);
+ return;
+ }
+ differ.submitList(sectionAndSort(list));
+ }
+
+ public void submitList(@Nullable final List list, @Nullable final Runnable commitCallback) {
+ if (list == null) {
+ differ.submitList(null, commitCallback);
+ return;
+ }
+ differ.submitList(sectionAndSort(list), commitCallback);
+ }
+
+ private List sectionAndSort(final List list) {
+ final List itemOrHeaders = new ArrayList<>();
+ Date prevSectionDate = null;
+ for (int i = 0; i < list.size(); i++) {
+ final DirectItem item = list.get(i);
+ if (item == null) continue;
+ final DirectItemOrHeader prev = itemOrHeaders.isEmpty() ? null : itemOrHeaders.get(itemOrHeaders.size() - 1);
+ if (prev != null && prev.item != null && DateUtils.isSameDay(prev.item.getDate(), item.getDate())) {
+ // just add item
+ final DirectItemOrHeader itemOrHeader = new DirectItemOrHeader();
+ itemOrHeader.item = item;
+ itemOrHeaders.add(itemOrHeader);
+ if (i == list.size() - 1) {
+ // add header
+ final DirectItemOrHeader itemOrHeader2 = new DirectItemOrHeader();
+ itemOrHeader2.date = prevSectionDate;
+ itemOrHeaders.add(itemOrHeader2);
+ }
+ continue;
+ }
+ if (prevSectionDate != null) {
+ // add header
+ final DirectItemOrHeader itemOrHeader = new DirectItemOrHeader();
+ itemOrHeader.date = prevSectionDate;
+ itemOrHeaders.add(itemOrHeader);
+ }
+ // Add item
+ final DirectItemOrHeader itemOrHeader = new DirectItemOrHeader();
+ itemOrHeader.item = item;
+ itemOrHeaders.add(itemOrHeader);
+ prevSectionDate = DateUtils.dateAtZeroHours(item.getDate());
+ }
+ return itemOrHeaders;
+ }
+
+ public List getList() {
+ return differ.getCurrentList();
+ }
+
+ @Override
+ public void onViewRecycled(@NonNull final RecyclerView.ViewHolder holder) {
+ if (holder instanceof DirectItemViewHolder) {
+ ((DirectItemViewHolder) holder).cleanup();
+ }
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(@NonNull final RecyclerView.ViewHolder holder) {
+ if (holder instanceof DirectItemViewHolder) {
+ ((DirectItemViewHolder) holder).cleanup();
+ }
+ }
+
+ public static class DirectItemOrHeader {
+ Date date;
+ DirectItem item;
+
+ public boolean isHeader() {
+ return date != null;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "DirectItemOrHeader{" +
+ "date=" + date +
+ ", item=" + (item != null ? item.getItemType() : null) +
+ '}';
+ }
+ }
+
+ public static class HeaderViewHolder extends RecyclerView.ViewHolder {
+ private final LayoutDmHeaderBinding binding;
+
+ public HeaderViewHolder(@NonNull final LayoutDmHeaderBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(final Date date) {
+ if (date == null) {
+ binding.header.setText("");
+ return;
+ }
+ binding.header.setText(DateFormat.getDateFormat(itemView.getContext()).format(date));
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/adapters/DirectMessageInboxAdapter.java b/app/src/main/java/awais/instagrabber/adapters/DirectMessageInboxAdapter.java
index c6701afc..1e42aadb 100644
--- a/app/src/main/java/awais/instagrabber/adapters/DirectMessageInboxAdapter.java
+++ b/app/src/main/java/awais/instagrabber/adapters/DirectMessageInboxAdapter.java
@@ -4,51 +4,67 @@ import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.AsyncDifferConfig;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
-import awais.instagrabber.adapters.viewholder.DirectMessageInboxItemViewHolder;
-import awais.instagrabber.databinding.LayoutDmInboxItemBinding;
-import awais.instagrabber.models.direct_messages.InboxThreadModel;
+import java.util.List;
-public final class DirectMessageInboxAdapter extends ListAdapter {
+import awais.instagrabber.adapters.viewholder.DirectInboxItemViewHolder;
+import awais.instagrabber.databinding.LayoutDmInboxItemBinding;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+
+public final class DirectMessageInboxAdapter extends ListAdapter {
private final OnItemClickListener onClickListener;
- private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() {
+ private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() {
@Override
- public boolean areItemsTheSame(@NonNull final InboxThreadModel oldItem, @NonNull final InboxThreadModel newItem) {
+ public boolean areItemsTheSame(@NonNull final DirectThread oldItem, @NonNull final DirectThread newItem) {
return oldItem.getThreadId().equals(newItem.getThreadId());
}
@Override
- public boolean areContentsTheSame(@NonNull final InboxThreadModel oldItem, @NonNull final InboxThreadModel newItem) {
- return oldItem.equals(newItem);
+ public boolean areContentsTheSame(@NonNull final DirectThread oldThread,
+ @NonNull final DirectThread newThread) {
+ final boolean titleEqual = oldThread.getThreadTitle().equals(newThread.getThreadTitle());
+ if (!titleEqual) return false;
+ final List oldItems = oldThread.getItems();
+ final List newItems = newThread.getItems();
+ if (oldItems == null || newItems == null) return false;
+ if (oldItems.size() != newItems.size()) return false;
+ final DirectItem oldItemFirst = oldThread.getFirstDirectItem();
+ final DirectItem newItemFirst = newThread.getFirstDirectItem();
+ if (oldItemFirst == null || newItemFirst == null) return false;
+ return oldItemFirst.getItemId().equals(newItemFirst.getItemId());
}
};
public DirectMessageInboxAdapter(final OnItemClickListener onClickListener) {
- super(diffCallback);
+ super(new AsyncDifferConfig.Builder<>(diffCallback).build());
this.onClickListener = onClickListener;
}
@NonNull
@Override
- public DirectMessageInboxItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
+ public DirectInboxItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final LayoutDmInboxItemBinding binding = LayoutDmInboxItemBinding.inflate(layoutInflater, parent, false);
- return new DirectMessageInboxItemViewHolder(binding);
+ return new DirectInboxItemViewHolder(binding, onClickListener);
}
@Override
- public void onBindViewHolder(@NonNull final DirectMessageInboxItemViewHolder holder, final int position) {
- final InboxThreadModel threadModel = getItem(position);
- if (onClickListener != null) {
- holder.itemView.setOnClickListener((v) -> onClickListener.onItemClick(threadModel));
- }
- holder.bind(threadModel);
+ public void onBindViewHolder(@NonNull final DirectInboxItemViewHolder holder, final int position) {
+ final DirectThread thread = getItem(position);
+ holder.bind(thread);
+ }
+
+ @Override
+ public long getItemId(final int position) {
+ return getItem(position).getThreadId().hashCode();
}
public interface OnItemClickListener {
- void onItemClick(final InboxThreadModel inboxThreadModel);
+ void onItemClick(final DirectThread thread);
}
}
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/adapters/DirectMessageItemsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/DirectMessageItemsAdapter.java
deleted file mode 100644
index a344a238..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/DirectMessageItemsAdapter.java
+++ /dev/null
@@ -1,152 +0,0 @@
-package awais.instagrabber.adapters;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.DiffUtil;
-import androidx.recyclerview.widget.ListAdapter;
-
-import java.util.List;
-
-import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageActionLogViewHolder;
-import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageAnimatedMediaViewHolder;
-import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageDefaultViewHolder;
-import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageItemViewHolder;
-import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageLinkViewHolder;
-import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageMediaShareViewHolder;
-import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageMediaViewHolder;
-import awais.instagrabber.adapters.viewholder.directmessages.DirectMessagePlaceholderViewHolder;
-import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageProfileViewHolder;
-import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageRavenMediaViewHolder;
-import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageReelShareViewHolder;
-import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageStoryShareViewHolder;
-import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageTextViewHolder;
-import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageVideoCallEventViewHolder;
-import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageVoiceMediaViewHolder;
-import awais.instagrabber.databinding.LayoutDmAnimatedMediaBinding;
-import awais.instagrabber.databinding.LayoutDmBaseBinding;
-import awais.instagrabber.databinding.LayoutDmLinkBinding;
-import awais.instagrabber.databinding.LayoutDmMediaBinding;
-import awais.instagrabber.databinding.LayoutDmMediaShareBinding;
-import awais.instagrabber.databinding.LayoutDmProfileBinding;
-import awais.instagrabber.databinding.LayoutDmRavenMediaBinding;
-import awais.instagrabber.databinding.LayoutDmStoryShareBinding;
-import awais.instagrabber.databinding.LayoutDmTextBinding;
-import awais.instagrabber.databinding.LayoutDmVoiceMediaBinding;
-import awais.instagrabber.interfaces.MentionClickListener;
-import awais.instagrabber.models.ProfileModel;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-import awais.instagrabber.models.enums.DirectItemType;
-
-public final class DirectMessageItemsAdapter extends ListAdapter {
- private final List users;
- private final List leftUsers;
- private final View.OnClickListener onClickListener;
- private final MentionClickListener mentionClickListener;
-
- private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() {
- @Override
- public boolean areItemsTheSame(@NonNull final DirectItemModel oldItem, @NonNull final DirectItemModel newItem) {
- return oldItem.getItemId().equals(newItem.getItemId());
- }
-
- @Override
- public boolean areContentsTheSame(@NonNull final DirectItemModel oldItem, @NonNull final DirectItemModel newItem) {
- return oldItem.getItemId().equals(newItem.getItemId());
- }
- };
-
- public DirectMessageItemsAdapter(final List users,
- final List leftUsers,
- final View.OnClickListener onClickListener,
- final MentionClickListener mentionClickListener) {
- super(diffCallback);
- this.users = users;
- this.leftUsers = leftUsers;
- this.onClickListener = onClickListener;
- this.mentionClickListener = mentionClickListener;
- }
-
- @NonNull
- @Override
- public DirectMessageItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
- final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
- final DirectItemType directItemType = DirectItemType.valueOf(type);
- final LayoutDmBaseBinding baseBinding = LayoutDmBaseBinding.inflate(layoutInflater, parent, false);
- final ViewGroup itemViewParent = baseBinding.messageCard;
- switch (directItemType) {
- case LIKE:
- case TEXT: {
- final LayoutDmTextBinding binding = LayoutDmTextBinding.inflate(layoutInflater, itemViewParent, false);
- return new DirectMessageTextViewHolder(baseBinding, binding, onClickListener, mentionClickListener);
- }
- case PLACEHOLDER: {
- final LayoutDmTextBinding binding = LayoutDmTextBinding.inflate(layoutInflater, itemViewParent, false);
- return new DirectMessagePlaceholderViewHolder(baseBinding, binding, onClickListener);
- }
- case ACTION_LOG: {
- final LayoutDmTextBinding binding = LayoutDmTextBinding.inflate(layoutInflater, itemViewParent, false);
- return new DirectMessageActionLogViewHolder(baseBinding, binding, onClickListener);
- }
- case LINK: {
- final LayoutDmLinkBinding binding = LayoutDmLinkBinding.inflate(layoutInflater, itemViewParent, false);
- return new DirectMessageLinkViewHolder(baseBinding, binding, onClickListener);
- }
- case MEDIA: {
- final LayoutDmMediaBinding binding = LayoutDmMediaBinding.inflate(layoutInflater, itemViewParent, false);
- return new DirectMessageMediaViewHolder(baseBinding, binding, onClickListener);
- }
- case PROFILE: {
- final LayoutDmProfileBinding binding = LayoutDmProfileBinding.inflate(layoutInflater, itemViewParent, false);
- return new DirectMessageProfileViewHolder(baseBinding, binding, onClickListener);
- }
- case REEL_SHARE: {
- final LayoutDmRavenMediaBinding binding = LayoutDmRavenMediaBinding.inflate(layoutInflater, itemViewParent, false);
- return new DirectMessageReelShareViewHolder(baseBinding, binding, onClickListener, mentionClickListener);
- }
- case MEDIA_SHARE:
- case FELIX_SHARE:
- case CLIP: {
- final LayoutDmMediaShareBinding binding = LayoutDmMediaShareBinding.inflate(layoutInflater, itemViewParent, false);
- return new DirectMessageMediaShareViewHolder(baseBinding, binding, onClickListener);
- }
- case RAVEN_MEDIA: {
- final LayoutDmRavenMediaBinding binding = LayoutDmRavenMediaBinding.inflate(layoutInflater, itemViewParent, false);
- return new DirectMessageRavenMediaViewHolder(baseBinding, binding, onClickListener);
- }
- case STORY_SHARE: {
- final LayoutDmStoryShareBinding binding = LayoutDmStoryShareBinding.inflate(layoutInflater, itemViewParent, false);
- return new DirectMessageStoryShareViewHolder(baseBinding, binding, onClickListener);
- }
- case VOICE_MEDIA: {
- final LayoutDmVoiceMediaBinding binding = LayoutDmVoiceMediaBinding.inflate(layoutInflater, itemViewParent, false);
- return new DirectMessageVoiceMediaViewHolder(baseBinding, binding, onClickListener);
- }
- case ANIMATED_MEDIA: {
- final LayoutDmAnimatedMediaBinding binding = LayoutDmAnimatedMediaBinding.inflate(layoutInflater, itemViewParent, false);
- return new DirectMessageAnimatedMediaViewHolder(baseBinding, binding, onClickListener);
- }
- case VIDEO_CALL_EVENT: {
- final LayoutDmTextBinding binding = LayoutDmTextBinding.inflate(layoutInflater, itemViewParent, false);
- return new DirectMessageVideoCallEventViewHolder(baseBinding, binding, onClickListener);
- }
- default: {
- final LayoutDmTextBinding binding = LayoutDmTextBinding.inflate(layoutInflater, itemViewParent, false);
- return new DirectMessageDefaultViewHolder(baseBinding, binding, onClickListener);
- }
- }
- }
-
- @Override
- public void onBindViewHolder(@NonNull final DirectMessageItemViewHolder holder, final int position) {
- final DirectItemModel directItemModel = getItem(position);
- holder.bind(directItemModel, users, leftUsers);
- }
-
- @Override
- public int getItemViewType(final int position) {
- return getItem(position).getItemType().getId();
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/adapters/DirectMessageMembersAdapter.java b/app/src/main/java/awais/instagrabber/adapters/DirectMessageMembersAdapter.java
index 01ed6bbd..60e22a15 100755
--- a/app/src/main/java/awais/instagrabber/adapters/DirectMessageMembersAdapter.java
+++ b/app/src/main/java/awais/instagrabber/adapters/DirectMessageMembersAdapter.java
@@ -14,11 +14,11 @@ import awais.instagrabber.databinding.ItemFollowBinding;
import awais.instagrabber.models.ProfileModel;
public final class DirectMessageMembersAdapter extends RecyclerView.Adapter {
- private final ProfileModel[] profileModels;
+ private final List profileModels;
private final List admins;
private final View.OnClickListener onClickListener;
- public DirectMessageMembersAdapter(final ProfileModel[] profileModels,
+ public DirectMessageMembersAdapter(final List profileModels,
final List admins,
final View.OnClickListener onClickListener) {
this.profileModels = profileModels;
@@ -36,12 +36,12 @@ public final class DirectMessageMembersAdapter extends RecyclerView.Adapter, FilterViewHolder> {
+
+ private static final DiffUtil.ItemCallback> DIFF_CALLBACK = new DiffUtil.ItemCallback>() {
+ @Override
+ public boolean areItemsTheSame(@NonNull final Filter> oldItem, @NonNull final Filter> newItem) {
+ return oldItem.getType().equals(newItem.getType());
+ }
+
+ @Override
+ public boolean areContentsTheSame(@NonNull final Filter> oldItem, @NonNull final Filter> newItem) {
+ return oldItem.getType().equals(newItem.getType());
+ }
+ };
+
+ private final Bitmap bitmap;
+ private final OnFilterClickListener onFilterClickListener;
+ private final Collection filters;
+ private final String originalKey;
+ private int selectedPosition = 0;
+
+ public FiltersAdapter(final Collection filters,
+ final String originalKey,
+ final Bitmap bitmap,
+ final OnFilterClickListener onFilterClickListener) {
+ super(DIFF_CALLBACK);
+ this.filters = filters;
+ this.originalKey = originalKey;
+ this.bitmap = bitmap;
+ this.onFilterClickListener = onFilterClickListener;
+ setHasStableIds(true);
+ }
+
+ @NonNull
+ @Override
+ public FilterViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
+ final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
+ final ItemFilterBinding binding = ItemFilterBinding.inflate(layoutInflater, parent, false);
+ return new FilterViewHolder(binding, filters, onFilterClickListener);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull final FilterViewHolder holder, final int position) {
+ holder.bind(position, originalKey, bitmap, getItem(position), selectedPosition == position);
+ }
+
+ @Override
+ public long getItemId(final int position) {
+ return getItem(position).getLabel();
+ }
+
+ public void setSelected(final int position) {
+ final int prev = this.selectedPosition;
+ this.selectedPosition = position;
+ notifyItemChanged(position);
+ notifyItemChanged(prev);
+ }
+
+ public void setSelectedFilter(final GPUImageFilter instance) {
+ final List> currentList = getCurrentList();
+ int index = -1;
+ for (int i = 0; i < currentList.size(); i++) {
+ final Filter> filter = currentList.get(i);
+ final GPUImageFilter filterInstance = filter.getInstance();
+ if (filterInstance.getClass() == instance.getClass()) {
+ index = i;
+ break;
+ }
+ }
+ if (index < 0) return;
+ setSelected(index);
+ }
+
+ public interface OnFilterClickListener {
+ void onClick(int position, Filter> filter);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/MediaItemsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/MediaItemsAdapter.java
new file mode 100644
index 00000000..e76429ef
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/MediaItemsAdapter.java
@@ -0,0 +1,111 @@
+package awais.instagrabber.adapters;
+
+import android.net.Uri;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.ListAdapter;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.facebook.drawee.backends.pipeline.Fresco;
+import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder;
+import com.facebook.drawee.controller.BaseControllerListener;
+import com.facebook.imagepipeline.common.ResizeOptions;
+import com.facebook.imagepipeline.image.ImageInfo;
+import com.facebook.imagepipeline.request.ImageRequest;
+import com.facebook.imagepipeline.request.ImageRequestBuilder;
+
+import java.io.File;
+
+import awais.instagrabber.databinding.ItemMediaBinding;
+import awais.instagrabber.utils.MediaController.MediaEntry;
+import awais.instagrabber.utils.TextUtils;
+import awais.instagrabber.utils.Utils;
+
+public class MediaItemsAdapter extends ListAdapter {
+
+ private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() {
+ @Override
+ public boolean areItemsTheSame(@NonNull final MediaEntry oldItem, @NonNull final MediaEntry newItem) {
+ return oldItem.imageId == newItem.imageId;
+ }
+
+ @Override
+ public boolean areContentsTheSame(@NonNull final MediaEntry oldItem, @NonNull final MediaEntry newItem) {
+ return oldItem.imageId == newItem.imageId;
+ }
+ };
+
+ private final OnItemClickListener onItemClickListener;
+
+ public MediaItemsAdapter(final OnItemClickListener onItemClickListener) {
+ super(diffCallback);
+ this.onItemClickListener = onItemClickListener;
+ }
+
+ @NonNull
+ @Override
+ public MediaItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
+ final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
+ final ItemMediaBinding binding = ItemMediaBinding.inflate(layoutInflater, parent, false);
+ return new MediaItemViewHolder(binding, onItemClickListener);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull final MediaItemViewHolder holder, final int position) {
+ holder.bind(getItem(position));
+ }
+
+ public static class MediaItemViewHolder extends RecyclerView.ViewHolder {
+ private static final String TAG = MediaItemViewHolder.class.getSimpleName();
+ private static final int size = Utils.displayMetrics.widthPixels / 3;
+
+ private final ItemMediaBinding binding;
+ private final OnItemClickListener onItemClickListener;
+
+ public MediaItemViewHolder(@NonNull final ItemMediaBinding binding,
+ final OnItemClickListener onItemClickListener) {
+ super(binding.getRoot());
+ this.binding = binding;
+ this.onItemClickListener = onItemClickListener;
+ }
+
+ public void bind(final MediaEntry item) {
+ final Uri uri = Uri.fromFile(new File(item.path));
+ if (onItemClickListener != null) {
+ itemView.setOnClickListener(v -> onItemClickListener.onItemClick(item));
+ }
+ final BaseControllerListener controllerListener = new BaseControllerListener() {
+ @Override
+ public void onFailure(final String id, final Throwable throwable) {
+ Log.e(TAG, "onFailure: ", throwable);
+ }
+ };
+ final ImageRequest request = ImageRequestBuilder
+ .newBuilderWithSource(uri)
+ .setLocalThumbnailPreviewsEnabled(true)
+ .setProgressiveRenderingEnabled(false)
+ .setResizeOptions(ResizeOptions.forDimensions(size, size))
+ .build();
+ final PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder()
+ .setImageRequest(request)
+ .setControllerListener(controllerListener);
+ binding.item.setController(builder.build());
+ if (item.isVideo && item.duration >= 0) {
+ final String timeString = TextUtils.millisToTimeString(item.duration);
+ binding.duration.setVisibility(View.VISIBLE);
+ binding.duration.setText(timeString);
+ } else {
+ binding.duration.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ public interface OnItemClickListener {
+ void onItemClick(MediaEntry entry);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/PostsMediaAdapter.java b/app/src/main/java/awais/instagrabber/adapters/PostsMediaAdapter.java
index 828a111d..86fef229 100755
--- a/app/src/main/java/awais/instagrabber/adapters/PostsMediaAdapter.java
+++ b/app/src/main/java/awais/instagrabber/adapters/PostsMediaAdapter.java
@@ -1,57 +1,57 @@
-package awais.instagrabber.adapters;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-
-import awais.instagrabber.R;
-import awais.instagrabber.adapters.viewholder.PostMediaViewHolder;
-import awais.instagrabber.databinding.ItemChildPostBinding;
-import awais.instagrabber.models.BasePostModel;
-import awais.instagrabber.models.ViewerPostModel;
-
-public final class PostsMediaAdapter extends RecyclerView.Adapter {
- private final View.OnClickListener clickListener;
- private ViewerPostModel[] postModels;
-
- public PostsMediaAdapter(final ViewerPostModel[] postModels, final View.OnClickListener clickListener) {
- this.postModels = postModels;
- this.clickListener = clickListener;
- }
-
- @NonNull
- @Override
- public PostMediaViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
- final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
- layoutInflater.inflate(R.layout.item_child_post, parent, false);
- final ItemChildPostBinding binding = ItemChildPostBinding.inflate(layoutInflater, parent, false);
- return new PostMediaViewHolder(binding);
- }
-
- @Override
- public void onBindViewHolder(@NonNull final PostMediaViewHolder holder, final int position) {
- final ViewerPostModel postModel = postModels[position];
- holder.bind(postModel, position, clickListener);
- }
-
- public void setData(final ViewerPostModel[] postModels) {
- this.postModels = postModels;
- notifyDataSetChanged();
- }
-
- public ViewerPostModel getItemAt(final int position) {
- return postModels == null ? null : postModels[position];
- }
-
- @Override
- public int getItemCount() {
- return postModels == null ? 0 : postModels.length;
- }
-
- public BasePostModel[] getPostModels() {
- return postModels;
- }
-}
\ No newline at end of file
+// package awais.instagrabber.adapters;
+//
+// import android.view.LayoutInflater;
+// import android.view.View;
+// import android.view.ViewGroup;
+//
+// import androidx.annotation.NonNull;
+// import androidx.recyclerview.widget.RecyclerView;
+//
+// import awais.instagrabber.R;
+// import awais.instagrabber.adapters.viewholder.PostMediaViewHolder;
+// import awais.instagrabber.databinding.ItemChildPostBinding;
+// import awais.instagrabber.models.BasePostModel;
+// import awais.instagrabber.models.ViewerPostModel;
+//
+// public final class PostsMediaAdapter extends RecyclerView.Adapter {
+// private final View.OnClickListener clickListener;
+// private ViewerPostModel[] postModels;
+//
+// public PostsMediaAdapter(final ViewerPostModel[] postModels, final View.OnClickListener clickListener) {
+// this.postModels = postModels;
+// this.clickListener = clickListener;
+// }
+//
+// @NonNull
+// @Override
+// public PostMediaViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
+// final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
+// layoutInflater.inflate(R.layout.item_child_post, parent, false);
+// final ItemChildPostBinding binding = ItemChildPostBinding.inflate(layoutInflater, parent, false);
+// return new PostMediaViewHolder(binding);
+// }
+//
+// @Override
+// public void onBindViewHolder(@NonNull final PostMediaViewHolder holder, final int position) {
+// final ViewerPostModel postModel = postModels[position];
+// holder.bind(postModel, position, clickListener);
+// }
+//
+// public void setData(final ViewerPostModel[] postModels) {
+// this.postModels = postModels;
+// notifyDataSetChanged();
+// }
+//
+// public ViewerPostModel getItemAt(final int position) {
+// return postModels == null ? null : postModels[position];
+// }
+//
+// @Override
+// public int getItemCount() {
+// return postModels == null ? 0 : postModels.length;
+// }
+//
+// public BasePostModel[] getPostModels() {
+// return postModels;
+// }
+// }
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/DirectInboxItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/DirectInboxItemViewHolder.java
new file mode 100644
index 00000000..fb93b02b
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/DirectInboxItemViewHolder.java
@@ -0,0 +1,353 @@
+package awais.instagrabber.adapters.viewholder;
+
+import android.graphics.Typeface;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.facebook.drawee.view.SimpleDraweeView;
+import com.google.common.collect.ImmutableList;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import awais.instagrabber.R;
+import awais.instagrabber.adapters.DirectMessageInboxAdapter.OnItemClickListener;
+import awais.instagrabber.databinding.LayoutDmInboxItemBinding;
+import awais.instagrabber.models.enums.DirectItemType;
+import awais.instagrabber.models.enums.MediaItemType;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemReelShare;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.repositories.responses.directmessages.DirectThreadDirectStory;
+import awais.instagrabber.repositories.responses.directmessages.DirectThreadLastSeenAt;
+import awais.instagrabber.repositories.responses.directmessages.DirectUser;
+import awais.instagrabber.repositories.responses.directmessages.RavenExpiringMediaActionSummary;
+import awais.instagrabber.utils.ResponseBodyUtils;
+import awais.instagrabber.utils.TextUtils;
+
+public final class DirectInboxItemViewHolder extends RecyclerView.ViewHolder {
+ // private static final String TAG = "DMInboxItemVH";
+ private final LayoutDmInboxItemBinding binding;
+ private final OnItemClickListener onClickListener;
+ private final List multipleProfilePics;
+ private final int childSmallSize;
+ private final int childTinySize;
+
+ public DirectInboxItemViewHolder(@NonNull final LayoutDmInboxItemBinding binding,
+ final OnItemClickListener onClickListener) {
+ super(binding.getRoot());
+ this.binding = binding;
+ this.onClickListener = onClickListener;
+ multipleProfilePics = ImmutableList.of(
+ binding.multiPic1,
+ binding.multiPic2,
+ binding.multiPic3
+ );
+ childSmallSize = itemView.getResources().getDimensionPixelSize(R.dimen.dm_inbox_avatar_size_small);
+ childTinySize = itemView.getResources().getDimensionPixelSize(R.dimen.dm_inbox_avatar_size_tiny);
+ }
+
+ public void bind(final DirectThread thread) {
+ if (thread == null) return;
+ if (onClickListener != null) {
+ itemView.setOnClickListener((v) -> onClickListener.onItemClick(thread));
+ }
+ setProfilePics(thread);
+ setTitle(thread);
+ final List items = thread.getItems();
+ if (items == null || items.isEmpty()) return;
+ final DirectItem item = thread.getFirstDirectItem();
+ if (item == null) return;
+ setDateTime(item);
+ setSubtitle(thread);
+ setReadState(thread);
+ }
+
+ private void setProfilePics(@NonNull final DirectThread thread) {
+ final List users = thread.getUsers();
+ if (users.size() > 1) {
+ binding.profilePic.setVisibility(View.GONE);
+ binding.multiPicContainer.setVisibility(View.VISIBLE);
+ for (int i = 0; i < Math.min(3, users.size()); ++i) {
+ final DirectUser user = users.get(i);
+ final SimpleDraweeView view = multipleProfilePics.get(i);
+ view.setVisibility(user == null ? View.GONE : View.VISIBLE);
+ if (user == null) return;
+ final String profilePicUrl = user.getProfilePicUrl();
+ view.setImageURI(profilePicUrl);
+ setChildSize(view, users.size());
+ if (i == 1) {
+ updateConstraints(view, users.size());
+ }
+ view.requestLayout();
+ }
+ return;
+ }
+ binding.profilePic.setVisibility(View.VISIBLE);
+ binding.multiPicContainer.setVisibility(View.GONE);
+ final String profilePicUrl = users.size() == 1 ? users.get(0).getProfilePicUrl() : null;
+ if (profilePicUrl == null) {
+ binding.profilePic.setController(null);
+ return;
+ }
+ binding.profilePic.setImageURI(profilePicUrl);
+ }
+
+ private void updateConstraints(final SimpleDraweeView view, final int length) {
+ final ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) view.getLayoutParams();
+ if (length >= 2) {
+ layoutParams.endToEnd = ConstraintSet.PARENT_ID;
+ layoutParams.bottomToBottom = ConstraintSet.PARENT_ID;
+ }
+ if (length == 3) {
+ layoutParams.startToStart = ConstraintSet.PARENT_ID;
+ layoutParams.topToTop = ConstraintSet.PARENT_ID;
+ }
+ }
+
+ private void setChildSize(final SimpleDraweeView view, final int length) {
+ final int size = length == 3 ? childTinySize : childSmallSize;
+ final ConstraintLayout.LayoutParams viewLayoutParams = new ConstraintLayout.LayoutParams(size, size);
+ view.setLayoutParams(viewLayoutParams);
+ }
+
+ private void setTitle(@NonNull final DirectThread thread) {
+ final String threadTitle = thread.getThreadTitle();
+ binding.threadTitle.setText(threadTitle);
+ }
+
+ private void setSubtitle(@NonNull final DirectThread thread) {
+ // If there is an unopened raven media, give it highest priority
+ final DirectThreadDirectStory directStory = thread.getDirectStory();
+ final long viewerId = thread.getViewerId();
+ if (directStory != null && !directStory.getItems().isEmpty()) {
+ final DirectItem item = directStory.getItems().get(0);
+ final MediaItemType mediaType = item.getVisualMedia().getMedia().getMediaType();
+ final String username = getUsername(thread.getUsers(), item.getUserId(), viewerId);
+ final String subtitle = getMediaSpecificSubtitle(username, mediaType);
+ binding.subtitle.setText(subtitle);
+ return;
+ }
+ final DirectItem item = thread.getFirstDirectItem();
+ if (item == null) return;
+ final long senderId = item.getUserId();
+ final DirectItemType itemType = item.getItemType();
+ String subtitle = null;
+ final String username = getUsername(thread.getUsers(), senderId, viewerId);
+ String message = "";
+ if (itemType == null) {
+ message = "Unsupported message";
+ } else {
+ switch (itemType) {
+ case TEXT:
+ message = item.getText();
+ break;
+ case LIKE:
+ message = item.getLike();
+ break;
+ case LINK:
+ message = item.getLink().getText();
+ break;
+ case PLACEHOLDER:
+ message = item.getPlaceholder().getMessage();
+ break;
+ case MEDIA_SHARE:
+ subtitle = String.format("%s shared a post", username != null ? username : "");
+ break;
+ case ANIMATED_MEDIA:
+ subtitle = String.format("%s shared a gif", username != null ? username : "");
+ break;
+ case PROFILE:
+ subtitle = String.format("%s shared a profile: @%s", username != null ? username : "", item.getProfile().getUsername());
+ break;
+ case LOCATION:
+ subtitle = String.format("%s shared a location: %s", username != null ? username : "", item.getLocation().getName());
+ break;
+ case MEDIA: {
+ final MediaItemType mediaType = item.getMedia().getMediaType();
+ subtitle = getMediaSpecificSubtitle(username, mediaType);
+ break;
+ }
+ case STORY_SHARE:
+ String format = "%s shared a story by @%s";
+ if (item.getStoryShare().getReelType().equals("highlight_reel")) {
+ format = "%s shared a story highlight by @%s";
+ }
+ subtitle = String.format(format, username != null ? username : "",
+ item.getStoryShare().getMedia().getUser().getUsername());
+ break;
+ case VOICE_MEDIA:
+ subtitle = String.format("%s sent a voice message", username != null ? username : "");
+ break;
+ case ACTION_LOG:
+ subtitle = item.getActionLog().getDescription();
+ break;
+ case VIDEO_CALL_EVENT:
+ subtitle = item.getVideoCallEvent().getDescription();
+ break;
+ case CLIP:
+ subtitle = String.format("%s shared a clip by @%s", username != null ? username : "",
+ item.getClip().getClip().getUser().getUsername());
+ break;
+ case FELIX_SHARE:
+ subtitle = String.format("%s shared an IGTV video by @%s", username != null ? username : "",
+ item.getFelixShare().getVideo().getUser().getUsername());
+ break;
+ case RAVEN_MEDIA:
+ subtitle = getRavenMediaSubtitle(item, username);
+ break;
+ case REEL_SHARE:
+ final DirectItemReelShare reelShare = item.getReelShare();
+ if (reelShare == null) {
+ subtitle = "";
+ break;
+ }
+ final String reelType = reelShare.getType();
+ switch (reelType) {
+ case "reply":
+ if (viewerId == item.getUserId()) {
+ subtitle = String.format("You replied to their story: %s", reelShare.getText());
+ } else {
+ subtitle = String.format("%s replied to your story: %s", username != null ? username : "", reelShare.getText());
+ }
+ break;
+ case "mention":
+ if (viewerId == item.getUserId()) {
+ // You mentioned the other person
+ final long mentionedUserId = item.getReelShare().getMentionedUserId();
+ final String otherUsername = getUsername(thread.getUsers(), mentionedUserId, viewerId);
+ subtitle = String.format("You mentioned @%s in your story", otherUsername);
+ } else {
+ // They mentioned you
+ subtitle = String.format("%s mentioned you in their story", username != null ? username : "");
+ }
+ break;
+ case "reaction":
+ if (viewerId == item.getUserId()) {
+ subtitle = String.format("You reacted to their story: %s", reelShare.getText());
+ } else {
+ subtitle = String.format("%s reacted to your story: %s", username != null ? username : "", reelShare.getText());
+ }
+ break;
+ default:
+ subtitle = "";
+ break;
+ }
+ break;
+ default:
+ message = "Unsupported message";
+ }
+ }
+ if (subtitle == null) {
+ if (thread.getUsers().size() > 1
+ || (thread.getUsers().size() == 1 && senderId == viewerId)) {
+ subtitle = String.format("%s: %s", username != null ? username : "", message);
+ } else {
+ subtitle = message;
+ }
+ }
+ binding.subtitle.setText(subtitle != null ? subtitle : "");
+ }
+
+ private String getMediaSpecificSubtitle(final String username, final MediaItemType mediaType) {
+ final String userSharedAnImage = String.format("%s shared an image", username != null ? username : "");
+ final String userSharedAVideo = String.format("%s shared a video", username != null ? username : "");
+ final String userSentAMessage = String.format("%s sent a message", username != null ? username : "");
+ String subtitle;
+ switch (mediaType) {
+ case MEDIA_TYPE_IMAGE:
+ subtitle = userSharedAnImage;
+ break;
+ case MEDIA_TYPE_VIDEO:
+ subtitle = userSharedAVideo;
+ break;
+ default:
+ subtitle = userSentAMessage;
+ break;
+ }
+ return subtitle;
+ }
+
+ private String getRavenMediaSubtitle(final DirectItem item,
+ final String username) {
+ String subtitle = "โ ";
+ final DirectItemVisualMedia visualMedia = item.getVisualMedia();
+ final RavenExpiringMediaActionSummary summary = visualMedia.getExpiringMediaActionSummary();
+ if (summary != null) {
+ final RavenExpiringMediaActionSummary.ActionType expiringMediaType = summary.getType();
+ int textRes = 0;
+ switch (expiringMediaType) {
+ case DELIVERED:
+ textRes = R.string.dms_inbox_raven_media_delivered;
+ break;
+ case SENT:
+ textRes = R.string.dms_inbox_raven_media_sent;
+ break;
+ case OPENED:
+ textRes = R.string.dms_inbox_raven_media_opened;
+ break;
+ case REPLAYED:
+ textRes = R.string.dms_inbox_raven_media_replayed;
+ break;
+ case SENDING:
+ textRes = R.string.dms_inbox_raven_media_sending;
+ break;
+ case BLOCKED:
+ textRes = R.string.dms_inbox_raven_media_blocked;
+ break;
+ case SUGGESTED:
+ textRes = R.string.dms_inbox_raven_media_suggested;
+ break;
+ case SCREENSHOT:
+ textRes = R.string.dms_inbox_raven_media_screenshot;
+ break;
+ case CANNOT_DELIVER:
+ textRes = R.string.dms_inbox_raven_media_cant_deliver;
+ break;
+ }
+ if (textRes > 0) {
+ subtitle += itemView.getContext().getString(textRes);
+ }
+ return subtitle;
+ }
+ final MediaItemType mediaType = visualMedia.getMedia().getMediaType();
+ subtitle = getMediaSpecificSubtitle(username, mediaType);
+ return subtitle;
+ }
+
+ private String getUsername(final List users,
+ final long userId,
+ final long viewerId) {
+ if (userId == viewerId) {
+ return "You";
+ }
+ final Optional senderOptional = users.stream()
+ .filter(Objects::nonNull)
+ .filter(user -> user.getPk() == userId)
+ .findFirst();
+ return senderOptional.map(DirectUser::getUsername).orElse(null);
+ }
+
+ private void setDateTime(@NonNull final DirectItem item) {
+ final long timestamp = item.getTimestamp() / 1000;
+ final String dateTimeString = TextUtils.getRelativeDateTimeString(itemView.getContext(), timestamp);
+ binding.tvDate.setText(dateTimeString);
+ }
+
+ private void setReadState(@NonNull final DirectThread thread) {
+ final DirectItem item = thread.getItems().get(0);
+ final Map lastSeenAtMap = thread.getLastSeenAt();
+ final boolean read = ResponseBodyUtils.isRead(item, lastSeenAtMap, Collections.singletonList(thread.getViewerId()), thread.getDirectStory());
+ binding.unread.setVisibility(read ? View.GONE : View.VISIBLE);
+ binding.threadTitle.setTypeface(binding.threadTitle.getTypeface(), read ? Typeface.NORMAL : Typeface.BOLD);
+ binding.subtitle.setTypeface(binding.subtitle.getTypeface(), read ? Typeface.NORMAL : Typeface.BOLD);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/DirectMessageInboxItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/DirectMessageInboxItemViewHolder.java
deleted file mode 100644
index f3285b1a..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/DirectMessageInboxItemViewHolder.java
+++ /dev/null
@@ -1,132 +0,0 @@
-package awais.instagrabber.adapters.viewholder;
-
-import android.content.Context;
-import android.net.Uri;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import androidx.annotation.NonNull;
-import androidx.core.text.HtmlCompat;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.facebook.drawee.backends.pipeline.Fresco;
-import com.facebook.drawee.view.SimpleDraweeView;
-import com.facebook.imagepipeline.common.ResizeOptions;
-import com.facebook.imagepipeline.request.ImageRequest;
-import com.facebook.imagepipeline.request.ImageRequestBuilder;
-
-import awais.instagrabber.R;
-import awais.instagrabber.databinding.LayoutDmInboxItemBinding;
-import awais.instagrabber.models.ProfileModel;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-import awais.instagrabber.models.direct_messages.InboxThreadModel;
-import awais.instagrabber.models.enums.DirectItemType;
-
-public final class DirectMessageInboxItemViewHolder extends RecyclerView.ViewHolder {
- private final LinearLayout multipleProfilePicsContainer;
- private final SimpleDraweeView[] multipleProfilePics;
- private final LayoutDmInboxItemBinding binding;
-
- public DirectMessageInboxItemViewHolder(@NonNull final LayoutDmInboxItemBinding binding) {
- super(binding.getRoot());
- this.binding = binding;
- multipleProfilePicsContainer = binding.multiPicContainer;
- final LinearLayout containerChild = (LinearLayout) multipleProfilePicsContainer.getChildAt(1);
- multipleProfilePics = new SimpleDraweeView[]{
- (SimpleDraweeView) multipleProfilePicsContainer.getChildAt(0),
- (SimpleDraweeView) containerChild.getChildAt(0),
- (SimpleDraweeView) containerChild.getChildAt(1)
- };
- binding.tvDate.setSelected(true);
- binding.tvUsername.setSelected(true);
- }
-
- public void bind(final InboxThreadModel model) {
- final DirectItemModel[] itemModels;
- if (model == null || (itemModels = model.getItems()) == null) {
- return;
- }
- itemView.setTag(model);
- final ProfileModel[] users = model.getUsers();
- if (users.length > 1) {
- binding.ivProfilePic.setVisibility(View.GONE);
- multipleProfilePicsContainer.setVisibility(View.VISIBLE);
- for (int i = 0; i < Math.min(3, users.length); ++i) {
- multipleProfilePics[i].setImageURI(users[i].getSdProfilePic());
- }
- } else {
- final String uriString = users.length == 1 ? users[0].getSdProfilePic() : null;
- if (uriString == null) {
- binding.ivProfilePic.setVisibility(View.GONE);
- } else {
- binding.ivProfilePic.setVisibility(View.VISIBLE);
- multipleProfilePicsContainer.setVisibility(View.GONE);
- final ImageRequest request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(uriString))
- .setResizeOptions(new ResizeOptions(50, 50))
- .build();
- binding.ivProfilePic.setController(
- Fresco.newDraweeControllerBuilder()
- .setOldController(binding.ivProfilePic.getController())
- .setImageRequest(request)
- .build()
- );
- }
- }
- binding.tvUsername.setText(model.getThreadTitle());
- final int length = itemModels.length;
- DirectItemModel lastItemModel = null;
- if (length != 0) {
- lastItemModel = itemModels[length - 1];
- }
- if (lastItemModel == null) {
- return;
- }
- final DirectItemType itemType = lastItemModel.getItemType();
- // binding.notTextType.setVisibility(itemType != DirectItemType.TEXT ? View.VISIBLE : View.GONE);
- final Context context = itemView.getContext();
- final CharSequence messageText;
- switch (itemType) {
- case TEXT:
- case LIKE:
- messageText = lastItemModel.getText();
- break;
- case LINK:
- messageText = context.getString(R.string.direct_messages_sent_link);
- break;
- case MEDIA:
- case MEDIA_SHARE:
- case RAVEN_MEDIA:
- case CLIP:
- case FELIX_SHARE:
- messageText = context.getString(R.string.direct_messages_sent_media);
- break;
- case ACTION_LOG:
- final DirectItemModel.DirectItemActionLogModel logModel = lastItemModel.getActionLogModel();
- messageText = logModel != null ? logModel.getDescription() : "...";
- break;
- case REEL_SHARE:
- final DirectItemModel.DirectItemReelShareModel reelShare = lastItemModel.getReelShare();
- if (reelShare == null)
- messageText = context.getString(R.string.direct_messages_sent_media);
- else {
- final String reelType = reelShare.getType();
- final int textRes;
- if ("reply".equals(reelType))
- textRes = R.string.direct_messages_replied_story;
- else if ("mention".equals(reelType))
- textRes = R.string.direct_messages_mention_story;
- else if ("reaction".equals(reelType))
- textRes = R.string.direct_messages_reacted_story;
- else textRes = R.string.direct_messages_sent_media;
-
- messageText = context.getString(textRes) + " : " + reelShare.getText();
- }
- break;
- default:
- messageText = "Unsupported message";
- }
- binding.tvComment.setText(HtmlCompat.fromHtml(messageText.toString(), HtmlCompat.FROM_HTML_MODE_COMPACT));
- binding.tvDate.setText(lastItemModel.getDateTime());
- binding.unread.setVisibility(model.getUnreadCount() > 0L ? View.VISIBLE : View.INVISIBLE);
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/FilterViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/FilterViewHolder.java
new file mode 100644
index 00000000..65637aea
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/FilterViewHolder.java
@@ -0,0 +1,72 @@
+package awais.instagrabber.adapters.viewholder;
+
+import android.graphics.Bitmap;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.Collection;
+
+import awais.instagrabber.adapters.FiltersAdapter;
+import awais.instagrabber.databinding.ItemFilterBinding;
+import awais.instagrabber.fragments.imageedit.filters.filters.Filter;
+import awais.instagrabber.utils.AppExecutors;
+import awais.instagrabber.utils.BitmapUtils;
+import jp.co.cyberagent.android.gpuimage.GPUImage;
+import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter;
+
+public class FilterViewHolder extends RecyclerView.ViewHolder {
+ private static final String TAG = FilterViewHolder.class.getSimpleName();
+
+ private final ItemFilterBinding binding;
+ private final Collection tuneFilters;
+ private final FiltersAdapter.OnFilterClickListener onFilterClickListener;
+ private final AppExecutors appExecutors;
+
+ public FilterViewHolder(@NonNull final ItemFilterBinding binding,
+ final Collection tuneFilters,
+ final FiltersAdapter.OnFilterClickListener onFilterClickListener) {
+ super(binding.getRoot());
+ this.binding = binding;
+ this.tuneFilters = tuneFilters;
+ this.onFilterClickListener = onFilterClickListener;
+ appExecutors = AppExecutors.getInstance();
+ }
+
+ public void bind(final int position, final String originalKey, final Bitmap originalBitmap, final Filter> item, final boolean isSelected) {
+ if (originalBitmap == null || item == null) return;
+ if (onFilterClickListener != null) {
+ itemView.setOnClickListener(v -> onFilterClickListener.onClick(position, item));
+ }
+ if (item.getLabel() != -1) {
+ binding.name.setVisibility(View.VISIBLE);
+ binding.name.setText(item.getLabel());
+ binding.name.setSelected(isSelected);
+ } else {
+ binding.name.setVisibility(View.GONE);
+ }
+ final String filterKey = item.getLabel() + "_" + originalKey;
+ // avoid resetting the bitmap
+ if (binding.preview.getTag() != null && binding.preview.getTag().equals(filterKey)) return;
+ binding.preview.setTag(filterKey);
+ final Bitmap bitmap = BitmapUtils.getBitmapFromMemCache(filterKey);
+ if (bitmap == null) {
+ final GPUImageFilter filter = item.getInstance();
+ appExecutors.tasksThread().submit(() -> {
+ GPUImage.getBitmapForMultipleFilters(
+ originalBitmap,
+ ImmutableList.builder().add(filter).addAll(tuneFilters).build(),
+ filteredBitmap -> {
+ BitmapUtils.addBitmapToMemoryCache(filterKey, filteredBitmap, true);
+ appExecutors.mainThread().execute(() -> binding.getRoot().post(() -> binding.preview.setImageBitmap(filteredBitmap)));
+ }
+ );
+ });
+ return;
+ }
+ binding.getRoot().post(() -> binding.preview.setImageBitmap(bitmap));
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/PostMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/PostMediaViewHolder.java
index d2d265a8..d719e299 100755
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/PostMediaViewHolder.java
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/PostMediaViewHolder.java
@@ -1,29 +1,29 @@
-package awais.instagrabber.adapters.viewholder;
-
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-
-import awais.instagrabber.databinding.ItemChildPostBinding;
-import awais.instagrabber.models.ViewerPostModel;
-
-public final class PostMediaViewHolder extends RecyclerView.ViewHolder {
-
- private final ItemChildPostBinding binding;
-
- public PostMediaViewHolder(@NonNull final ItemChildPostBinding binding) {
- super(binding.getRoot());
- this.binding = binding;
- }
-
- public void bind(final ViewerPostModel model, final int position, final View.OnClickListener clickListener) {
- if (model == null) return;
- // model.setPosition(position);
- itemView.setTag(model);
- itemView.setOnClickListener(clickListener);
- binding.selectedView.setVisibility(model.isCurrentSlide() ? View.VISIBLE : View.GONE);
- binding.isDownloaded.setVisibility(model.isDownloaded() ? View.VISIBLE : View.GONE);
- binding.icon.setImageURI(model.getDisplayUrl());
- }
-}
+// package awais.instagrabber.adapters.viewholder;
+//
+// import android.view.View;
+//
+// import androidx.annotation.NonNull;
+// import androidx.recyclerview.widget.RecyclerView;
+//
+// import awais.instagrabber.databinding.ItemChildPostBinding;
+// import awais.instagrabber.models.ViewerPostModel;
+//
+// public final class PostMediaViewHolder extends RecyclerView.ViewHolder {
+//
+// private final ItemChildPostBinding binding;
+//
+// public PostMediaViewHolder(@NonNull final ItemChildPostBinding binding) {
+// super(binding.getRoot());
+// this.binding = binding;
+// }
+//
+// public void bind(final ViewerPostModel model, final int position, final View.OnClickListener clickListener) {
+// if (model == null) return;
+// // model.setPosition(position);
+// itemView.setTag(model);
+// itemView.setOnClickListener(clickListener);
+// binding.selectedView.setVisibility(model.isCurrentSlide() ? View.VISIBLE : View.GONE);
+// binding.isDownloaded.setVisibility(model.isDownloaded() ? View.VISIBLE : View.GONE);
+// binding.icon.setImageURI(model.getDisplayUrl());
+// }
+// }
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemActionLogViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemActionLogViewHolder.java
new file mode 100644
index 00000000..600f224d
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemActionLogViewHolder.java
@@ -0,0 +1,72 @@
+package awais.instagrabber.adapters.viewholder.directmessages;
+
+import android.graphics.Typeface;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.style.ClickableSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+import awais.instagrabber.R;
+import awais.instagrabber.databinding.LayoutDmActionLogBinding;
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.interfaces.MentionClickListener;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemActionLog;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.utils.TextUtils;
+
+public class DirectItemActionLogViewHolder extends DirectItemViewHolder {
+
+ private final LayoutDmActionLogBinding binding;
+
+ public DirectItemActionLogViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
+ final LayoutDmActionLogBinding binding,
+ final ProfileModel currentUser,
+ final DirectThread thread,
+ final MentionClickListener mentionClickListener,
+ final View.OnClickListener onClickListener) {
+ super(baseBinding, currentUser, thread, onClickListener);
+ this.binding = binding;
+ setItemView(binding.getRoot());
+ }
+
+ @Override
+ public void bindItem(final DirectItem directItemModel, final MessageDirection messageDirection) {
+ final DirectItemActionLog actionLog = directItemModel.getActionLog();
+ final String text = actionLog.getDescription();
+ final SpannableStringBuilder sb = new SpannableStringBuilder(text);
+ final List bold = actionLog.getBold();
+ if (bold != null && !bold.isEmpty()) {
+ for (final DirectItemActionLog.TextRange textRange : bold) {
+ final StyleSpan boldStyleSpan = new StyleSpan(Typeface.BOLD);
+ sb.setSpan(boldStyleSpan, textRange.getStart(), textRange.getEnd(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ }
+ final List textAttributes = actionLog.getTextAttributes();
+ if (textAttributes != null && !textAttributes.isEmpty()) {
+ for (final DirectItemActionLog.TextRange textAttribute : textAttributes) {
+ if (!TextUtils.isEmpty(textAttribute.getColor())) {
+ final ForegroundColorSpan colorSpan = new ForegroundColorSpan(itemView.getResources().getColor(R.color.deep_orange_400));
+ sb.setSpan(colorSpan, textAttribute.getStart(), textAttribute.getEnd(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ if (!TextUtils.isEmpty(textAttribute.getIntent())) {
+ final ClickableSpan clickableSpan = new ClickableSpan() {
+ @Override
+ public void onClick(@NonNull final View widget) {
+
+ }
+ };
+ sb.setSpan(clickableSpan, textAttribute.getStart(), textAttribute.getEnd(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ }
+ }
+ binding.tvMessage.setText(sb);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemAnimatedMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemAnimatedMediaViewHolder.java
new file mode 100644
index 00000000..201d2a98
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemAnimatedMediaViewHolder.java
@@ -0,0 +1,71 @@
+package awais.instagrabber.adapters.viewholder.directmessages;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.core.util.Pair;
+
+import com.facebook.drawee.backends.pipeline.Fresco;
+
+import awais.instagrabber.R;
+import awais.instagrabber.databinding.LayoutDmAnimatedMediaBinding;
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.interfaces.MentionClickListener;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.repositories.responses.directmessages.AnimatedMediaFixedHeight;
+import awais.instagrabber.repositories.responses.directmessages.AnimatedMediaImages;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemAnimatedMedia;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.utils.NumberUtils;
+import awais.instagrabber.utils.Utils;
+
+public class DirectItemAnimatedMediaViewHolder extends DirectItemViewHolder {
+
+ private final LayoutDmAnimatedMediaBinding binding;
+ private final int maxHeight;
+ private final int maxWidth;
+
+ public DirectItemAnimatedMediaViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
+ @NonNull final LayoutDmAnimatedMediaBinding binding,
+ final ProfileModel currentUser,
+ final DirectThread thread,
+ final MentionClickListener mentionClickListener,
+ final View.OnClickListener onClickListener) {
+ super(baseBinding, currentUser, thread, onClickListener);
+ this.binding = binding;
+ maxHeight = itemView.getResources().getDimensionPixelSize(R.dimen.dm_media_img_max_height);
+ final int margin = itemView.getResources().getDimensionPixelSize(R.dimen.dm_message_item_margin);
+ maxWidth = Utils.displayMetrics.widthPixels - margin;
+ setItemView(binding.getRoot());
+ }
+
+ @Override
+ public void bindItem(final DirectItem item, final MessageDirection messageDirection) {
+ removeBg();
+ final DirectItemAnimatedMedia animatedMediaModel = item.getAnimatedMedia();
+ final AnimatedMediaImages images = animatedMediaModel.getImages();
+ if (images == null) return;
+ final AnimatedMediaFixedHeight fixedHeight = images.getFixedHeight();
+ if (fixedHeight == null) return;
+ final String url = fixedHeight.getWebp();
+ final Pair widthHeight = NumberUtils.calculateWidthHeight(
+ fixedHeight.getHeight(),
+ fixedHeight.getWidth(),
+ maxHeight,
+ maxWidth
+ );
+ binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
+ final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams();
+ final int width = widthHeight.first != null ? widthHeight.first : 0;
+ final int height = widthHeight.second != null ? widthHeight.second : 0;
+ layoutParams.width = width;
+ layoutParams.height = height;
+ binding.ivAnimatedMessage.requestLayout();
+ binding.ivAnimatedMessage.setController(Fresco.newDraweeControllerBuilder()
+ .setUri(url)
+ .setAutoPlayAnimations(true)
+ .build());
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemDefaultViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemDefaultViewHolder.java
new file mode 100644
index 00000000..72b32afa
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemDefaultViewHolder.java
@@ -0,0 +1,36 @@
+package awais.instagrabber.adapters.viewholder.directmessages;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import awais.instagrabber.R;
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.databinding.LayoutDmTextBinding;
+import awais.instagrabber.interfaces.MentionClickListener;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+
+public class DirectItemDefaultViewHolder extends DirectItemViewHolder {
+
+ private final LayoutDmTextBinding binding;
+
+ public DirectItemDefaultViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
+ @NonNull final LayoutDmTextBinding binding,
+ final ProfileModel currentUser,
+ final DirectThread thread,
+ final MentionClickListener mentionClickListener,
+ final View.OnClickListener onClickListener) {
+ super(baseBinding, currentUser, thread, onClickListener);
+ this.binding = binding;
+ // setItemView(binding.getRoot());
+ }
+
+ @Override
+ public void bindItem(final DirectItem directItemModel, final MessageDirection messageDirection) {
+ final Context context = itemView.getContext();
+ binding.tvMessage.setText(context.getText(R.string.dms_inbox_raven_message_unknown));
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemLikeViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemLikeViewHolder.java
new file mode 100644
index 00000000..59690233
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemLikeViewHolder.java
@@ -0,0 +1,33 @@
+package awais.instagrabber.adapters.viewholder.directmessages;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.databinding.LayoutDmLikeBinding;
+import awais.instagrabber.interfaces.MentionClickListener;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+
+public class DirectItemLikeViewHolder extends DirectItemViewHolder {
+
+ private final LayoutDmLikeBinding binding;
+
+ public DirectItemLikeViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
+ @NonNull final LayoutDmLikeBinding binding,
+ final ProfileModel currentUser,
+ final DirectThread thread,
+ final View.OnClickListener onClickListener,
+ final MentionClickListener mentionClickListener) {
+ super(baseBinding, currentUser, thread, onClickListener);
+ this.binding = binding;
+ setItemView(binding.getRoot());
+ }
+
+ @Override
+ public void bindItem(final DirectItem directItemModel, final MessageDirection messageDirection) {
+ removeBg();
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemLinkViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemLinkViewHolder.java
new file mode 100644
index 00000000..8018dc12
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemLinkViewHolder.java
@@ -0,0 +1,71 @@
+package awais.instagrabber.adapters.viewholder.directmessages;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+
+import awais.instagrabber.R;
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.databinding.LayoutDmLinkBinding;
+import awais.instagrabber.interfaces.MentionClickListener;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemLink;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemLinkContext;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.utils.TextUtils;
+import awais.instagrabber.utils.Utils;
+
+public class DirectItemLinkViewHolder extends DirectItemViewHolder {
+
+ private final LayoutDmLinkBinding binding;
+
+ public DirectItemLinkViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
+ final LayoutDmLinkBinding binding,
+ final ProfileModel currentUser,
+ final DirectThread thread,
+ final MentionClickListener mentionClickListener,
+ final View.OnClickListener onClickListener) {
+ super(baseBinding, currentUser, thread, onClickListener);
+ this.binding = binding;
+ final int margin = itemView.getResources().getDimensionPixelSize(R.dimen.dm_message_item_margin);
+ final int width = Utils.displayMetrics.widthPixels - margin - Utils.convertDpToPx(8);
+ final ViewGroup.LayoutParams layoutParams = binding.preview.getLayoutParams();
+ layoutParams.width = width;
+ binding.preview.requestLayout();
+ setItemView(binding.getRoot());
+ }
+
+ @Override
+ public void bindItem(final DirectItem directItemModel, final MessageDirection messageDirection) {
+ final DirectItemLink link = directItemModel.getLink();
+ final DirectItemLinkContext linkContext = link.getLinkContext();
+ final String linkImageUrl = linkContext.getLinkImageUrl();
+ if (TextUtils.isEmpty(linkImageUrl)) {
+ binding.preview.setVisibility(View.GONE);
+ } else {
+ binding.preview.setVisibility(View.VISIBLE);
+ binding.preview.setImageURI(linkImageUrl);
+ }
+ if (TextUtils.isEmpty(linkContext.getLinkTitle())) {
+ binding.title.setVisibility(View.GONE);
+ } else {
+ binding.title.setVisibility(View.VISIBLE);
+ binding.title.setText(linkContext.getLinkTitle());
+ }
+ if (TextUtils.isEmpty(linkContext.getLinkSummary())) {
+ binding.summary.setVisibility(View.GONE);
+ } else {
+ binding.summary.setVisibility(View.VISIBLE);
+ binding.summary.setText(linkContext.getLinkSummary());
+ }
+ if (TextUtils.isEmpty(linkContext.getLinkUrl())) {
+ binding.url.setVisibility(View.GONE);
+ } else {
+ binding.url.setVisibility(View.VISIBLE);
+ binding.url.setText(linkContext.getLinkUrl());
+ }
+ binding.text.setText(link.getText());
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java
new file mode 100644
index 00000000..9f34f9d5
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java
@@ -0,0 +1,139 @@
+package awais.instagrabber.adapters.viewholder.directmessages;
+
+import android.content.res.Resources;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.core.util.Pair;
+
+import com.facebook.drawee.drawable.ScalingUtils;
+import com.facebook.drawee.generic.GenericDraweeHierarchy;
+import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
+import com.facebook.drawee.generic.RoundingParams;
+
+import awais.instagrabber.R;
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.databinding.LayoutDmMediaShareBinding;
+import awais.instagrabber.interfaces.MentionClickListener;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.models.enums.DirectItemType;
+import awais.instagrabber.models.enums.MediaItemType;
+import awais.instagrabber.repositories.responses.directmessages.Caption;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemClip;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemFelixShare;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemMedia;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.repositories.responses.directmessages.DirectUser;
+import awais.instagrabber.utils.NumberUtils;
+import awais.instagrabber.utils.ResponseBodyUtils;
+import awais.instagrabber.utils.Utils;
+
+public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
+
+ private final LayoutDmMediaShareBinding binding;
+ private final int maxHeight;
+ private final int maxWidth;
+ private final int dmRadius;
+ private final int dmRadiusSmall;
+ // private final RoundingParams roundingParams;
+
+ public DirectItemMediaShareViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
+ @NonNull final LayoutDmMediaShareBinding binding,
+ final ProfileModel currentUser,
+ final DirectThread thread,
+ final MentionClickListener mentionClickListener,
+ final View.OnClickListener onClickListener) {
+ super(baseBinding, currentUser, thread, onClickListener);
+ this.binding = binding;
+ final Resources resources = itemView.getResources();
+ maxHeight = resources.getDimensionPixelSize(R.dimen.dm_media_img_max_height);
+ final int margin = resources.getDimensionPixelSize(R.dimen.dm_message_item_margin);
+ maxWidth = Utils.displayMetrics.widthPixels - margin - Utils.convertDpToPx(8);
+ dmRadius = resources.getDimensionPixelSize(R.dimen.dm_message_card_radius);
+ dmRadiusSmall = resources.getDimensionPixelSize(R.dimen.dm_message_card_radius_small);
+ setItemView(binding.getRoot());
+ }
+
+ @Override
+ public void bindItem(final DirectItem item, final MessageDirection messageDirection) {
+ removeBg();
+ final RoundingParams roundingParams = messageDirection == MessageDirection.INCOMING
+ ? RoundingParams.fromCornersRadii(dmRadiusSmall, dmRadius, dmRadius, dmRadius)
+ : RoundingParams.fromCornersRadii(dmRadius, dmRadiusSmall, dmRadius, dmRadius);
+ final GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(itemView.getResources())
+ .setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
+ .setRoundingParams(roundingParams)
+ .build();
+ binding.mediaPreview.setHierarchy(hierarchy);
+ binding.topBg.setBackgroundResource(messageDirection == MessageDirection.INCOMING
+ ? R.drawable.bg_media_share_top_incoming
+ : R.drawable.bg_media_share_top_outgoing);
+ DirectItemMedia media = null;
+ if (item.getItemType() == DirectItemType.MEDIA_SHARE) {
+ media = item.getMediaShare();
+ } else if (item.getItemType() == DirectItemType.CLIP) {
+ final DirectItemClip clip = item.getClip();
+ if (clip == null) return;
+ media = clip.getClip();
+ } else if (item.getItemType() == DirectItemType.FELIX_SHARE) {
+ final DirectItemFelixShare felixShare = item.getFelixShare();
+ if (felixShare == null) return;
+ media = felixShare.getVideo();
+ }
+ if (media == null) return;
+ final DirectUser user = media.getUser();
+ if (user != null) {
+ binding.username.setVisibility(View.VISIBLE);
+ binding.profilePic.setVisibility(View.VISIBLE);
+ binding.username.setText(user.getUsername());
+ binding.profilePic.setImageURI(user.getProfilePicUrl());
+ } else {
+ binding.username.setVisibility(View.GONE);
+ binding.profilePic.setVisibility(View.GONE);
+ }
+ final String title = media.getTitle();
+ if (!TextUtils.isEmpty(title)) {
+ binding.title.setVisibility(View.VISIBLE);
+ binding.title.setText(title);
+ } else {
+ binding.title.setVisibility(View.GONE);
+ }
+ final Caption caption = media.getCaption();
+ if (caption != null) {
+ binding.caption.setVisibility(View.VISIBLE);
+ binding.caption.setText(caption.getText());
+ binding.caption.setEllipsize(TextUtils.TruncateAt.END);
+ binding.caption.setMaxLines(2);
+ } else {
+ binding.caption.setVisibility(View.GONE);
+ }
+ final MediaItemType mediaType = media.getMediaType();
+ if (mediaType == MediaItemType.MEDIA_TYPE_SLIDER) {
+ media = media.getCarouselMedia().get(0);
+ }
+ final Pair widthHeight = NumberUtils.calculateWidthHeight(
+ media.getOriginalHeight(),
+ media.getOriginalWidth(),
+ maxHeight,
+ maxWidth
+ );
+ final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams();
+ layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
+ layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
+ binding.mediaPreview.requestLayout();
+ final String url = ResponseBodyUtils.getThumbUrl(media.getImageVersions2());
+ binding.mediaPreview.setImageURI(url);
+ final boolean showTypeIcon = mediaType == MediaItemType.MEDIA_TYPE_VIDEO || mediaType == MediaItemType.MEDIA_TYPE_SLIDER;
+ if (!showTypeIcon) {
+ binding.typeIcon.setVisibility(View.GONE);
+ return;
+ }
+ binding.typeIcon.setVisibility(View.VISIBLE);
+ binding.typeIcon.setImageResource(mediaType == MediaItemType.MEDIA_TYPE_VIDEO
+ ? R.drawable.ic_video_24
+ : R.drawable.ic_checkbox_multiple_blank_stroke);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaViewHolder.java
new file mode 100644
index 00000000..fb7df861
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaViewHolder.java
@@ -0,0 +1,86 @@
+package awais.instagrabber.adapters.viewholder.directmessages;
+
+import android.content.res.Resources;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.core.util.Pair;
+
+import com.facebook.drawee.drawable.ScalingUtils;
+import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
+import com.facebook.drawee.generic.RoundingParams;
+
+import awais.instagrabber.R;
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.databinding.LayoutDmMediaBinding;
+import awais.instagrabber.interfaces.MentionClickListener;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.models.enums.MediaItemType;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemMedia;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.repositories.responses.directmessages.ImageVersions2;
+import awais.instagrabber.utils.NumberUtils;
+import awais.instagrabber.utils.ResponseBodyUtils;
+import awais.instagrabber.utils.Utils;
+
+public class DirectItemMediaViewHolder extends DirectItemViewHolder {
+
+ private final LayoutDmMediaBinding binding;
+ private final int maxHeight;
+ private final int maxWidth;
+ private final RoundingParams incomingRoundingParams;
+ private final RoundingParams outgoingRoundingParams;
+
+ public DirectItemMediaViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
+ @NonNull final LayoutDmMediaBinding binding,
+ final ProfileModel currentUser,
+ final DirectThread thread,
+ final MentionClickListener mentionClickListener,
+ final View.OnClickListener onClickListener) {
+ super(baseBinding, currentUser, thread, onClickListener);
+ this.binding = binding;
+ final Resources resources = itemView.getResources();
+ maxHeight = resources.getDimensionPixelSize(R.dimen.dm_media_img_max_height);
+ final int margin = resources.getDimensionPixelSize(R.dimen.dm_message_item_margin);
+ maxWidth = Utils.displayMetrics.widthPixels - margin - Utils.convertDpToPx(8);
+ final int dmRadius = resources.getDimensionPixelSize(R.dimen.dm_message_card_radius);
+ final int dmRadiusSmall = resources.getDimensionPixelSize(R.dimen.dm_message_card_radius_small);
+ incomingRoundingParams = RoundingParams.fromCornersRadii(dmRadiusSmall, dmRadius, dmRadius, dmRadius);
+ outgoingRoundingParams = RoundingParams.fromCornersRadii(dmRadius, dmRadiusSmall, dmRadius, dmRadius);
+ setItemView(binding.getRoot());
+ removeBg();
+ }
+
+ @Override
+ public void bindItem(final DirectItem directItemModel, final MessageDirection messageDirection) {
+ final RoundingParams roundingParams = messageDirection == MessageDirection.INCOMING ? incomingRoundingParams : outgoingRoundingParams;
+ binding.mediaPreview.setHierarchy(new GenericDraweeHierarchyBuilder(itemView.getResources())
+ .setRoundingParams(roundingParams)
+ .setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
+ .build());
+ final DirectItemMedia media = directItemModel.getMedia();
+ final MediaItemType modelMediaType = media.getMediaType();
+ binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER
+ ? View.VISIBLE
+ : View.GONE);
+ final Pair widthHeight = NumberUtils.calculateWidthHeight(
+ media.getOriginalHeight(),
+ media.getOriginalWidth(),
+ maxHeight,
+ maxWidth
+ );
+ final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams();
+ final int width = widthHeight.first != null ? widthHeight.first : 0;
+ layoutParams.width = width;
+ layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
+ binding.mediaPreview.requestLayout();
+ binding.bgTime.getLayoutParams().width = width;
+ binding.bgTime.requestLayout();
+ final ImageVersions2 imageVersions2 = media.getImageVersions2();
+ if (imageVersions2 == null) return;
+ final String thumbUrl = ResponseBodyUtils.getThumbUrl(imageVersions2);
+ binding.mediaPreview.setImageURI(thumbUrl);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemPlaceholderViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemPlaceholderViewHolder.java
new file mode 100644
index 00000000..b6a293a7
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemPlaceholderViewHolder.java
@@ -0,0 +1,34 @@
+package awais.instagrabber.adapters.viewholder.directmessages;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.databinding.LayoutDmTextBinding;
+import awais.instagrabber.interfaces.MentionClickListener;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+
+public class DirectItemPlaceholderViewHolder extends DirectItemViewHolder {
+
+ private final LayoutDmTextBinding binding;
+
+ public DirectItemPlaceholderViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
+ final LayoutDmTextBinding binding,
+ final ProfileModel currentUser,
+ final DirectThread thread,
+ final MentionClickListener mentionClickListener,
+ final View.OnClickListener onClickListener) {
+ super(baseBinding, currentUser, thread, onClickListener);
+ this.binding = binding;
+ setItemView(binding.getRoot());
+ }
+
+ @Override
+ public void bindItem(final DirectItem directItemModel, final MessageDirection messageDirection) {
+ final String text = String.format("%s: %s", directItemModel.getPlaceholder().getTitle(), directItemModel.getPlaceholder().getMessage());
+ binding.tvMessage.setText(text);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemProfileViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemProfileViewHolder.java
new file mode 100644
index 00000000..e29b8837
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemProfileViewHolder.java
@@ -0,0 +1,115 @@
+package awais.instagrabber.adapters.viewholder.directmessages;
+
+import android.content.res.Resources;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
+import com.facebook.drawee.generic.RoundingParams;
+import com.facebook.drawee.view.SimpleDraweeView;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+import awais.instagrabber.R;
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.databinding.LayoutDmProfileBinding;
+import awais.instagrabber.interfaces.MentionClickListener;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.models.enums.DirectItemType;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemLocation;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemMedia;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.repositories.responses.directmessages.DirectUser;
+import awais.instagrabber.repositories.responses.directmessages.ImageVersions2;
+import awais.instagrabber.utils.ResponseBodyUtils;
+
+public class DirectItemProfileViewHolder extends DirectItemViewHolder {
+
+ private final LayoutDmProfileBinding binding;
+ private final ImmutableList previewViews;
+
+ public DirectItemProfileViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
+ @NonNull final LayoutDmProfileBinding binding,
+ final ProfileModel currentUser,
+ final DirectThread thread,
+ final MentionClickListener mentionClickListener,
+ final View.OnClickListener onClickListener) {
+ super(baseBinding, currentUser, thread, onClickListener);
+ this.binding = binding;
+ setItemView(binding.getRoot());
+ previewViews = ImmutableList.of(
+ binding.preview1,
+ binding.preview2,
+ binding.preview3,
+ binding.preview4,
+ binding.preview5,
+ binding.preview6
+ );
+ final Resources resources = itemView.getResources();
+ final int dmRadius = resources.getDimensionPixelSize(R.dimen.dm_message_card_radius);
+ binding.preview4.setHierarchy(new GenericDraweeHierarchyBuilder(resources)
+ .setRoundingParams(RoundingParams.fromCornersRadii(0, 0, 0, dmRadius))
+ .build());
+ binding.preview6.setHierarchy(new GenericDraweeHierarchyBuilder(resources)
+ .setRoundingParams(RoundingParams.fromCornersRadii(0, 0, dmRadius, 0))
+ .build());
+ }
+
+ @Override
+ public void bindItem(@NonNull final DirectItem item,
+ final MessageDirection messageDirection) {
+ removeBg();
+ binding.getRoot().setBackgroundResource(messageDirection == MessageDirection.INCOMING
+ ? R.drawable.bg_speech_bubble_incoming
+ : R.drawable.bg_speech_bubble_outgoing);
+ if (item.getItemType() == DirectItemType.PROFILE) {
+ setProfile(item);
+ } else if (item.getItemType() == DirectItemType.LOCATION) {
+ setLocation(item);
+ } else {
+ return;
+ }
+ for (final SimpleDraweeView previewView : previewViews) {
+ previewView.setImageURI((String) null);
+ }
+ final List previewMedias = item.getPreviewMedias();
+ if (previewMedias.size() <= 0) {
+ binding.firstRow.setVisibility(View.GONE);
+ binding.secondRow.setVisibility(View.GONE);
+ return;
+ }
+ if (previewMedias.size() <= 3) {
+ binding.firstRow.setVisibility(View.VISIBLE);
+ binding.secondRow.setVisibility(View.GONE);
+ }
+ for (int i = 0; i < previewMedias.size(); i++) {
+ final DirectItemMedia previewMedia = previewMedias.get(i);
+ if (previewMedia == null) continue;
+ final ImageVersions2 imageVersions2 = previewMedia.getImageVersions2();
+ final String url = ResponseBodyUtils.getThumbUrl(imageVersions2);
+ if (url == null) continue;
+ previewViews.get(i).setImageURI(url);
+ }
+ }
+
+ private void setProfile(@NonNull final DirectItem item) {
+ final DirectUser profile = item.getProfile();
+ if (profile == null) return;
+ binding.profilePic.setImageURI(profile.getProfilePicUrl());
+ binding.username.setText(profile.getUsername());
+ binding.fullName.setText(profile.getFullName());
+ binding.isVerified.setVisibility(profile.isVerified() ? View.VISIBLE : View.GONE);
+ }
+
+ private void setLocation(@NonNull final DirectItem item) {
+ final DirectItemLocation location = item.getLocation();
+ if (location == null) return;
+ binding.profilePic.setVisibility(View.GONE);
+ binding.username.setText(location.getName());
+ binding.fullName.setText(location.getAddress());
+ binding.isVerified.setVisibility(View.GONE);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java
new file mode 100644
index 00000000..f150a515
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java
@@ -0,0 +1,197 @@
+package awais.instagrabber.adapters.viewholder.directmessages;
+
+import android.content.res.Resources;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.core.util.Pair;
+
+import com.facebook.drawee.drawable.ScalingUtils;
+import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
+import com.facebook.drawee.generic.RoundingParams;
+
+import awais.instagrabber.R;
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.databinding.LayoutDmRavenMediaBinding;
+import awais.instagrabber.interfaces.MentionClickListener;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.models.enums.MediaItemType;
+import awais.instagrabber.models.enums.RavenMediaViewMode;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemMedia;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.repositories.responses.directmessages.ImageVersions2;
+import awais.instagrabber.utils.NumberUtils;
+import awais.instagrabber.utils.ResponseBodyUtils;
+import awais.instagrabber.utils.Utils;
+
+public class DirectItemRavenMediaViewHolder extends DirectItemViewHolder {
+
+ private final LayoutDmRavenMediaBinding binding;
+ private final int maxHeight;
+ private final int maxWidth;
+ private final int dmRadius;
+ private final int dmRadiusSmall;
+
+ public DirectItemRavenMediaViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
+ @NonNull final LayoutDmRavenMediaBinding binding,
+ final ProfileModel currentUser,
+ final DirectThread thread,
+ final MentionClickListener mentionClickListener,
+ final View.OnClickListener onClickListener) {
+ super(baseBinding, currentUser, thread, onClickListener);
+ this.binding = binding;
+ final Resources resources = itemView.getResources();
+ maxHeight = resources.getDimensionPixelSize(R.dimen.dm_media_img_max_height);
+ final int margin = resources.getDimensionPixelSize(R.dimen.dm_message_item_margin);
+ maxWidth = Utils.displayMetrics.widthPixels - margin - Utils.convertDpToPx(8);
+ dmRadius = resources.getDimensionPixelSize(R.dimen.dm_message_card_radius);
+ dmRadiusSmall = resources.getDimensionPixelSize(R.dimen.dm_message_card_radius_small);
+ setItemView(binding.getRoot());
+ }
+
+ @Override
+ public void bindItem(final DirectItem directItemModel, final MessageDirection messageDirection) {
+ removeBg();
+ final DirectItemVisualMedia visualMedia = directItemModel.getVisualMedia();
+ final DirectItemMedia media = visualMedia.getMedia();
+ if (media == null) return;
+ setExpiryInfo(visualMedia);
+ setPreview(visualMedia, messageDirection);
+ /*final boolean isExpired = visualMedia == null || (mediaModel = visualMedia.getMedia()) == null ||
+ TextUtils.isEmpty(mediaModel.getThumbUrl()) && mediaModel.getPk() < 1;
+
+ RavenExpiringMediaActionSummary mediaActionSummary = null;
+ if (visualMedia != null) {
+ mediaActionSummary = visualMedia.getExpiringMediaActionSummary();
+ }
+ binding.mediaExpiredIcon.setVisibility(isExpired ? View.VISIBLE : View.GONE);
+
+ int textRes = R.string.dms_inbox_raven_media_unknown;
+ if (isExpired) textRes = R.string.dms_inbox_raven_media_expired;
+
+ if (!isExpired) {
+ if (mediaActionSummary != null) {
+ final ActionType expiringMediaType = mediaActionSummary.getType();
+
+ if (expiringMediaType == ActionType.DELIVERED)
+ textRes = R.string.dms_inbox_raven_media_delivered;
+ else if (expiringMediaType == ActionType.SENT)
+ textRes = R.string.dms_inbox_raven_media_sent;
+ else if (expiringMediaType == ActionType.OPENED)
+ textRes = R.string.dms_inbox_raven_media_opened;
+ else if (expiringMediaType == ActionType.REPLAYED)
+ textRes = R.string.dms_inbox_raven_media_replayed;
+ else if (expiringMediaType == ActionType.SENDING)
+ textRes = R.string.dms_inbox_raven_media_sending;
+ else if (expiringMediaType == ActionType.BLOCKED)
+ textRes = R.string.dms_inbox_raven_media_blocked;
+ else if (expiringMediaType == ActionType.SUGGESTED)
+ textRes = R.string.dms_inbox_raven_media_suggested;
+ else if (expiringMediaType == ActionType.SCREENSHOT)
+ textRes = R.string.dms_inbox_raven_media_screenshot;
+ else if (expiringMediaType == ActionType.CANNOT_DELIVER)
+ textRes = R.string.dms_inbox_raven_media_cant_deliver;
+ }
+
+ final RavenMediaViewMode ravenMediaViewMode = visualMedia.getViewType();
+ if (ravenMediaViewMode == RavenMediaViewMode.PERMANENT || ravenMediaViewMode == RavenMediaViewMode.REPLAYABLE) {
+ final MediaItemType mediaType = mediaModel.getMediaType();
+ textRes = -1;
+ binding.typeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO || mediaType == MediaItemType.MEDIA_TYPE_SLIDER
+ ? View.VISIBLE
+ : View.GONE);
+ final Pair widthHeight = NumberUtils.calculateWidthHeight(
+ mediaModel.getHeight(),
+ mediaModel.getWidth(),
+ maxHeight,
+ maxWidth
+ );
+ final ViewGroup.LayoutParams layoutParams = binding.ivMediaPreview.getLayoutParams();
+ layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
+ layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
+ binding.ivMediaPreview.requestLayout();
+ binding.ivMediaPreview.setImageURI(mediaModel.getThumbUrl());
+ }
+ }
+ if (textRes != -1) {
+ binding.tvMessage.setText(context.getText(textRes));
+ binding.tvMessage.setVisibility(View.VISIBLE);
+ }*/
+ }
+
+ private void setExpiryInfo(final DirectItemVisualMedia visualMedia) {
+ final DirectItemMedia media = visualMedia.getMedia();
+ final RavenMediaViewMode viewMode = visualMedia.getViewMode();
+ if (viewMode != RavenMediaViewMode.PERMANENT) {
+ final MediaItemType mediaType = media.getMediaType();
+ final boolean expired = media.getPk() == null;
+ final String info;
+ switch (mediaType) {
+ case MEDIA_TYPE_IMAGE:
+ if (expired) {
+ info = "Image has expired";
+ break;
+ }
+ info = "Image will expire when seen";
+ break;
+ case MEDIA_TYPE_VIDEO:
+ if (expired) {
+ info = "Video has expired";
+ break;
+ }
+ info = "Video will expire when seen";
+ break;
+ default:
+ if (expired) {
+ info = "Message has expired";
+ break;
+ }
+ info = "Message will expire when seen";
+ break;
+ }
+ binding.expiryInfo.setVisibility(View.VISIBLE);
+ binding.expiryInfo.setText(info);
+ return;
+ }
+ binding.expiryInfo.setVisibility(View.GONE);
+ }
+
+ private void setPreview(final DirectItemVisualMedia visualMedia,
+ final MessageDirection messageDirection) {
+ final DirectItemMedia media = visualMedia.getMedia();
+ final boolean expired = media.getPk() == null;
+ if (expired) {
+ binding.preview.setVisibility(View.GONE);
+ binding.typeIcon.setVisibility(View.GONE);
+ return;
+ }
+ final RoundingParams roundingParams = messageDirection == MessageDirection.INCOMING
+ ? RoundingParams.fromCornersRadii(dmRadiusSmall, dmRadius, dmRadius, dmRadius)
+ : RoundingParams.fromCornersRadii(dmRadius, dmRadiusSmall, dmRadius, dmRadius);
+ binding.preview.setHierarchy(new GenericDraweeHierarchyBuilder(itemView.getResources())
+ .setRoundingParams(roundingParams)
+ .setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
+ .build());
+ final MediaItemType modelMediaType = media.getMediaType();
+ binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER
+ ? View.VISIBLE
+ : View.GONE);
+ final Pair widthHeight = NumberUtils.calculateWidthHeight(
+ media.getOriginalHeight(),
+ media.getOriginalWidth(),
+ maxHeight,
+ maxWidth
+ );
+ final ViewGroup.LayoutParams layoutParams = binding.preview.getLayoutParams();
+ layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
+ layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
+ binding.preview.requestLayout();
+ final ImageVersions2 imageVersions2 = media.getImageVersions2();
+ if (imageVersions2 == null) return;
+ final String thumbUrl = ResponseBodyUtils.getThumbUrl(imageVersions2);
+ binding.preview.setImageURI(thumbUrl);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemReelShareViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemReelShareViewHolder.java
new file mode 100644
index 00000000..62fa1c2c
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemReelShareViewHolder.java
@@ -0,0 +1,180 @@
+package awais.instagrabber.adapters.viewholder.directmessages;
+
+import android.view.Gravity;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
+import com.facebook.drawee.generic.RoundingParams;
+
+import awais.instagrabber.R;
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.databinding.LayoutDmReelShareBinding;
+import awais.instagrabber.interfaces.MentionClickListener;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.models.enums.MediaItemType;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemMedia;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemReelShare;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.repositories.responses.directmessages.DirectUser;
+import awais.instagrabber.repositories.responses.directmessages.ImageVersions2;
+import awais.instagrabber.utils.ResponseBodyUtils;
+import awais.instagrabber.utils.TextUtils;
+import awais.instagrabber.utils.Utils;
+
+public class DirectItemReelShareViewHolder extends DirectItemViewHolder {
+
+ private final LayoutDmReelShareBinding binding;
+ // private final int maxHeight;
+ // private final int maxWidth;
+ private final int dmRadiusSmall;
+ private final int messageMargin;
+
+ public DirectItemReelShareViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
+ @NonNull final LayoutDmReelShareBinding binding,
+ final ProfileModel currentUser,
+ final DirectThread thread,
+ final MentionClickListener mentionClickListener,
+ final View.OnClickListener onClickListener) {
+ super(baseBinding, currentUser, thread, onClickListener);
+ this.binding = binding;
+ dmRadiusSmall = itemView.getResources().getDimensionPixelSize(R.dimen.dm_message_card_radius_small);
+ // binding.tvMessage.setMentionClickListener(mentionClickListener);
+ messageMargin = Utils.convertDpToPx(4);
+ setItemView(binding.getRoot());
+ }
+
+ @Override
+ public void bindItem(final DirectItem item, final MessageDirection messageDirection) {
+ removeBg();
+ final DirectItemReelShare reelShare = item.getReelShare();
+ final String type = reelShare.getType();
+ if (type == null) return;
+ final boolean isSelf = isSelf(item);
+ final DirectItemMedia media = reelShare.getMedia();
+ if (media == null) return;
+ final DirectUser user = media.getUser();
+ if (user == null) return;
+ final boolean expired = media.getMediaType() == null;
+ if (expired) {
+ binding.preview.setVisibility(View.GONE);
+ binding.typeIcon.setVisibility(View.GONE);
+ binding.quoteLine.setVisibility(View.GONE);
+ binding.reaction.setVisibility(View.GONE);
+ } else {
+ binding.preview.setVisibility(View.VISIBLE);
+ binding.typeIcon.setVisibility(View.VISIBLE);
+ binding.quoteLine.setVisibility(View.VISIBLE);
+ binding.reaction.setVisibility(View.VISIBLE);
+ }
+ setGravity(messageDirection, expired);
+ if (type.equals("reply")) {
+ setReply(messageDirection, reelShare, isSelf);
+ }
+ if (type.equals("reaction")) {
+ setReaction(messageDirection, reelShare, isSelf, expired);
+ }
+ if (type.equals("mention")) {
+ setMention(isSelf);
+ }
+ if (!expired) {
+ setPreview(media);
+ }
+ }
+
+ private void setGravity(final MessageDirection messageDirection, final boolean expired) {
+ final boolean isIncoming = messageDirection == MessageDirection.INCOMING;
+ binding.shareInfo.setGravity(isIncoming ? Gravity.START : Gravity.END);
+ if (!expired) {
+ binding.quoteLine.setVisibility(isIncoming ? View.VISIBLE : View.GONE);
+ binding.quoteLineEnd.setVisibility(isIncoming ? View.GONE : View.VISIBLE);
+ }
+ final ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) binding.quoteLine.getLayoutParams();
+ layoutParams.horizontalBias = isIncoming ? 0 : 1;
+ final ConstraintLayout.LayoutParams messageLayoutParams = (ConstraintLayout.LayoutParams) binding.message.getLayoutParams();
+ messageLayoutParams.startToStart = isIncoming ? ConstraintLayout.LayoutParams.PARENT_ID : ConstraintLayout.LayoutParams.UNSET;
+ messageLayoutParams.endToEnd = isIncoming ? ConstraintLayout.LayoutParams.UNSET : ConstraintLayout.LayoutParams.PARENT_ID;
+ messageLayoutParams.setMarginStart(isIncoming ? messageMargin : 0);
+ messageLayoutParams.setMarginEnd(isIncoming ? 0 : messageMargin);
+ final ConstraintLayout.LayoutParams reactionLayoutParams = (ConstraintLayout.LayoutParams) binding.reaction.getLayoutParams();
+ final int previewId = binding.preview.getId();
+ if (isIncoming) {
+ reactionLayoutParams.startToEnd = previewId;
+ reactionLayoutParams.endToEnd = previewId;
+ reactionLayoutParams.startToStart = ConstraintLayout.LayoutParams.UNSET;
+ reactionLayoutParams.endToStart = ConstraintLayout.LayoutParams.UNSET;
+ } else {
+ reactionLayoutParams.startToStart = previewId;
+ reactionLayoutParams.endToStart = previewId;
+ reactionLayoutParams.startToEnd = ConstraintLayout.LayoutParams.UNSET;
+ reactionLayoutParams.endToEnd = ConstraintLayout.LayoutParams.UNSET;
+ }
+ }
+
+ private void setReply(final MessageDirection messageDirection,
+ final DirectItemReelShare reelShare,
+ final boolean isSelf) {
+ final String info = isSelf ? "You replied to their story" : "They replied to your story";
+ binding.shareInfo.setText(info);
+ binding.reaction.setVisibility(View.GONE);
+ final String text = reelShare.getText();
+ if (TextUtils.isEmpty(text)) {
+ binding.message.setVisibility(View.GONE);
+ return;
+ }
+ setMessage(messageDirection, text);
+ }
+
+ private void setReaction(final MessageDirection messageDirection,
+ final DirectItemReelShare reelShare,
+ final boolean isSelf,
+ final boolean expired) {
+ final String info = isSelf ? "You reacted to their story" : "They reacted to your story";
+ binding.shareInfo.setText(info);
+ binding.message.setVisibility(View.GONE);
+ final String text = reelShare.getText();
+ if (TextUtils.isEmpty(text)) {
+ binding.reaction.setVisibility(View.GONE);
+ return;
+ }
+ if (expired) {
+ setMessage(messageDirection, text);
+ return;
+ }
+ binding.reaction.setVisibility(View.VISIBLE);
+ binding.reaction.setText(text);
+ }
+
+ private void setMention(final boolean isSelf) {
+ final String info = isSelf ? "You mentioned them in your story" : "Mentioned you in their story";
+ binding.shareInfo.setText(info);
+ binding.message.setVisibility(View.GONE);
+ binding.reaction.setVisibility(View.GONE);
+ }
+
+ private void setMessage(final MessageDirection messageDirection, final String text) {
+ binding.message.setVisibility(View.VISIBLE);
+ binding.message.setBackgroundResource(messageDirection == MessageDirection.INCOMING
+ ? R.drawable.bg_speech_bubble_incoming
+ : R.drawable.bg_speech_bubble_outgoing);
+ binding.message.setText(text);
+ }
+
+ private void setPreview(final DirectItemMedia media) {
+ final MediaItemType mediaType = media.getMediaType();
+ if (mediaType == null) return;
+ binding.typeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO || mediaType == MediaItemType.MEDIA_TYPE_SLIDER
+ ? View.VISIBLE : View.GONE);
+ final RoundingParams roundingParams = RoundingParams.fromCornersRadii(dmRadiusSmall, dmRadiusSmall, dmRadiusSmall, dmRadiusSmall);
+ binding.preview.setHierarchy(new GenericDraweeHierarchyBuilder(itemView.getResources())
+ .setRoundingParams(roundingParams)
+ .build());
+ final ImageVersions2 imageVersions2 = media.getImageVersions2();
+ if (imageVersions2 == null) return;
+ final String thumbUrl = ResponseBodyUtils.getThumbUrl(imageVersions2);
+ binding.preview.setImageURI(thumbUrl);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemStoryShareViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemStoryShareViewHolder.java
new file mode 100644
index 00000000..5ab88afd
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemStoryShareViewHolder.java
@@ -0,0 +1,112 @@
+package awais.instagrabber.adapters.viewholder.directmessages;
+
+import android.content.res.Resources;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.core.util.Pair;
+
+import com.facebook.drawee.drawable.ScalingUtils;
+import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
+import com.facebook.drawee.generic.RoundingParams;
+
+import awais.instagrabber.R;
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.databinding.LayoutDmStoryShareBinding;
+import awais.instagrabber.interfaces.MentionClickListener;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.models.enums.MediaItemType;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemMedia;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.repositories.responses.directmessages.ImageVersions2;
+import awais.instagrabber.utils.NumberUtils;
+import awais.instagrabber.utils.ResponseBodyUtils;
+import awais.instagrabber.utils.TextUtils;
+import awais.instagrabber.utils.Utils;
+
+public class DirectItemStoryShareViewHolder extends DirectItemViewHolder {
+
+ private final LayoutDmStoryShareBinding binding;
+ private final int maxHeight;
+ private final int maxWidth;
+ private final int dmRadius;
+ private final int dmRadiusSmall;
+
+ public DirectItemStoryShareViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
+ @NonNull final LayoutDmStoryShareBinding binding,
+ final ProfileModel currentUser,
+ final DirectThread thread,
+ final MentionClickListener mentionClickListener,
+ final View.OnClickListener onClickListener) {
+ super(baseBinding, currentUser, thread, onClickListener);
+ this.binding = binding;
+ maxHeight = itemView.getResources().getDimensionPixelSize(R.dimen.dm_media_img_max_height);
+ final Resources resources = itemView.getResources();
+ final int margin = resources.getDimensionPixelSize(R.dimen.dm_message_item_margin);
+ maxWidth = Utils.displayMetrics.widthPixels - margin - Utils.convertDpToPx(8);
+ dmRadius = resources.getDimensionPixelSize(R.dimen.dm_message_card_radius);
+ dmRadiusSmall = resources.getDimensionPixelSize(R.dimen.dm_message_card_radius_small);
+ setItemView(binding.getRoot());
+ }
+
+ @Override
+ public void bindItem(final DirectItem item, final MessageDirection messageDirection) {
+ removeBg();
+ String format = "@%s's story";
+ final String reelType = item.getStoryShare().getReelType();
+ if (reelType == null || item.getStoryShare().getMedia() == null) {
+ setExpiredStoryInfo(item);
+ return;
+ }
+ if (reelType.equals("highlight_reel")) {
+ format = "@%s's story highlight";
+ }
+ final String info = String.format(format, item.getStoryShare().getMedia().getUser().getUsername());
+ binding.shareInfo.setText(info);
+ binding.text.setVisibility(View.GONE);
+ binding.ivMediaPreview.setController(null);
+ final DirectItemStoryShare storyShare = item.getStoryShare();
+ if (storyShare == null) return;
+ final String text = storyShare.getText();
+ if (!TextUtils.isEmpty(text)) {
+ binding.text.setText(text);
+ binding.text.setVisibility(View.VISIBLE);
+ return;
+ }
+ final DirectItemMedia storyShareMedia = storyShare.getMedia();
+ final MediaItemType mediaType = storyShareMedia.getMediaType();
+ binding.typeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ? View.VISIBLE : View.GONE);
+ final RoundingParams roundingParams = messageDirection == MessageDirection.INCOMING
+ ? RoundingParams.fromCornersRadii(dmRadiusSmall, dmRadius, dmRadius, dmRadius)
+ : RoundingParams.fromCornersRadii(dmRadius, dmRadiusSmall, dmRadius, dmRadius);
+ binding.ivMediaPreview.setHierarchy(new GenericDraweeHierarchyBuilder(itemView.getResources())
+ .setRoundingParams(roundingParams)
+ .setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
+ .build());
+ final Pair widthHeight = NumberUtils.calculateWidthHeight(
+ storyShareMedia.getOriginalHeight(),
+ storyShareMedia.getOriginalWidth(),
+ maxHeight,
+ maxWidth
+ );
+ final ViewGroup.LayoutParams layoutParams = binding.ivMediaPreview.getLayoutParams();
+ layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
+ layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
+ binding.ivMediaPreview.requestLayout();
+ final ImageVersions2 imageVersions2 = storyShareMedia.getImageVersions2();
+ if (imageVersions2 == null) return;
+ final String thumbUrl = ResponseBodyUtils.getThumbUrl(imageVersions2);
+ binding.ivMediaPreview.setImageURI(thumbUrl);
+ }
+
+ private void setExpiredStoryInfo(final DirectItem item) {
+ binding.shareInfo.setText(item.getStoryShare().getTitle());
+ binding.text.setVisibility(View.VISIBLE);
+ binding.text.setText(item.getStoryShare().getMessage());
+ binding.ivMediaPreview.setVisibility(View.GONE);
+ binding.typeIcon.setVisibility(View.GONE);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemTextViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemTextViewHolder.java
new file mode 100644
index 00000000..0c2c8462
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemTextViewHolder.java
@@ -0,0 +1,44 @@
+package awais.instagrabber.adapters.viewholder.directmessages;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.databinding.LayoutDmTextBinding;
+import awais.instagrabber.interfaces.MentionClickListener;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+
+public class DirectItemTextViewHolder extends DirectItemViewHolder {
+
+ private final LayoutDmTextBinding binding;
+
+ public DirectItemTextViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
+ @NonNull final LayoutDmTextBinding binding,
+ final ProfileModel currentUser,
+ final DirectThread thread,
+ final View.OnClickListener onClickListener,
+ final MentionClickListener mentionClickListener) {
+ super(baseBinding, currentUser, thread, onClickListener);
+ this.binding = binding;
+ // this.binding.tvMessage.setMentionClickListener(mentionClickListener);
+ setItemView(binding.getRoot());
+ }
+
+ @Override
+ public void bindItem(final DirectItem directItemModel, final MessageDirection messageDirection) {
+ // final Context context = itemView.getContext();
+ final String text = directItemModel.getText();
+ if (text == null) return;
+ binding.tvMessage.setText(text);
+ // text = TextUtils.getSpannableUrl(text.toString()); // for urls
+ // if (TextUtils.hasMentions(text)) text = TextUtils.getMentionText(text); // for mentions
+ // if (text instanceof Spanned)
+ // binding.tvMessage.setText(text, TextView.BufferType.SPANNABLE);
+ // else if (text == "") {
+ // binding.tvMessage.setText(context.getText(R.string.dms_inbox_raven_message_unknown));
+ // } else binding.tvMessage.setText(text);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemVideoCallEventViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemVideoCallEventViewHolder.java
new file mode 100644
index 00000000..d9d567da
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemVideoCallEventViewHolder.java
@@ -0,0 +1,65 @@
+package awais.instagrabber.adapters.viewholder.directmessages;
+
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.style.ClickableSpan;
+import android.text.style.ForegroundColorSpan;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+import awais.instagrabber.R;
+import awais.instagrabber.databinding.LayoutDmActionLogBinding;
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.interfaces.MentionClickListener;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemActionLog;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemVideoCallEvent;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.utils.TextUtils;
+
+public class DirectItemVideoCallEventViewHolder extends DirectItemViewHolder {
+
+ private final LayoutDmActionLogBinding binding;
+
+ public DirectItemVideoCallEventViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
+ final LayoutDmActionLogBinding binding,
+ final ProfileModel currentUser,
+ final DirectThread thread,
+ final MentionClickListener mentionClickListener,
+ final View.OnClickListener onClickListener) {
+ super(baseBinding, currentUser, thread, onClickListener);
+ this.binding = binding;
+ setItemView(binding.getRoot());
+ }
+
+ @Override
+ public void bindItem(final DirectItem directItemModel, final MessageDirection messageDirection) {
+ final DirectItemVideoCallEvent videoCallEvent = directItemModel.getVideoCallEvent();
+ final String text = videoCallEvent.getDescription();
+ final SpannableStringBuilder sb = new SpannableStringBuilder(text);
+ final List textAttributes = videoCallEvent.getTextAttributes();
+ if (textAttributes != null && !textAttributes.isEmpty()) {
+ for (final DirectItemActionLog.TextRange textAttribute : textAttributes) {
+ if (!TextUtils.isEmpty(textAttribute.getColor())) {
+ final ForegroundColorSpan colorSpan = new ForegroundColorSpan(itemView.getResources().getColor(R.color.deep_orange_400));
+ sb.setSpan(colorSpan, textAttribute.getStart(), textAttribute.getEnd(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ if (!TextUtils.isEmpty(textAttribute.getIntent())) {
+ final ClickableSpan clickableSpan = new ClickableSpan() {
+ @Override
+ public void onClick(@NonNull final View widget) {
+
+ }
+ };
+ sb.setSpan(clickableSpan, textAttribute.getStart(), textAttribute.getEnd(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ }
+ }
+ binding.tvMessage.setMaxLines(1);
+ binding.tvMessage.setText(sb);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java
new file mode 100644
index 00000000..207e0bf0
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java
@@ -0,0 +1,322 @@
+package awais.instagrabber.adapters.viewholder.directmessages;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.text.format.DateFormat;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.core.widget.ImageViewCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import awais.instagrabber.R;
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.models.enums.DirectItemType;
+import awais.instagrabber.models.enums.MediaItemType;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemEmojiReaction;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemMedia;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemReactions;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.repositories.responses.directmessages.DirectUser;
+import awais.instagrabber.utils.ResponseBodyUtils;
+import awais.instagrabber.utils.Utils;
+
+public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
+ private static final String TAG = DirectItemViewHolder.class.getSimpleName();
+
+ private final LayoutDmBaseBinding binding;
+ private final DirectUser currentUser;
+ private final DirectThread thread;
+ private final int margin;
+ private final int dmRadius;
+ private final int messageInfoPaddingSmall;
+ private final int dmRadiusSmall;
+ private final List userIds;
+
+ public DirectItemViewHolder(@NonNull final LayoutDmBaseBinding binding,
+ @NonNull final ProfileModel currentUser,
+ final DirectThread thread,
+ @NonNull final View.OnClickListener onClickListener) {
+ super(binding.getRoot());
+ this.binding = binding;
+ this.currentUser = DirectUser.fromProfileModel(currentUser);
+ this.thread = thread;
+ userIds = thread.getUsers()
+ .stream()
+ .map(DirectUser::getPk)
+ .collect(Collectors.toList());
+ binding.ivProfilePic.setVisibility(thread.isGroup() ? View.VISIBLE : View.GONE);
+ binding.ivProfilePic.setOnClickListener(thread.isGroup() ? onClickListener : null);
+ // binding.messageCard.setOnClickListener(onClickListener);
+ margin = itemView.getResources().getDimensionPixelSize(R.dimen.dm_message_item_margin);
+ final Resources resources = itemView.getResources();
+ dmRadius = resources.getDimensionPixelSize(R.dimen.dm_message_card_radius);
+ dmRadiusSmall = resources.getDimensionPixelSize(R.dimen.dm_message_card_radius_small);
+ messageInfoPaddingSmall = Utils.convertDpToPx(4);
+ }
+
+ public void bind(final DirectItem item) {
+ final MessageDirection messageDirection = isSelf(item) ? MessageDirection.OUTGOING : MessageDirection.INCOMING;
+ final FrameLayout.LayoutParams containerLayoutParams = (FrameLayout.LayoutParams) binding.container.getLayoutParams();
+ final DirectItemType itemType = item.getItemType();
+ binding.messageInfo.setVisibility(View.VISIBLE);
+ binding.deliveryStatus.setVisibility(messageDirection == MessageDirection.OUTGOING ? View.VISIBLE : View.GONE);
+ if (itemType != DirectItemType.ACTION_LOG && itemType != DirectItemType.VIDEO_CALL_EVENT) {
+ containerLayoutParams.setMarginStart(messageDirection == MessageDirection.OUTGOING ? margin : 0);
+ containerLayoutParams.setMarginEnd(messageDirection == MessageDirection.INCOMING ? margin : 0);
+ containerLayoutParams.gravity = messageDirection == MessageDirection.INCOMING ? Gravity.START : Gravity.END;
+ binding.background.setBackgroundResource(messageDirection == MessageDirection.INCOMING ? R.drawable.bg_speech_bubble_incoming
+ : R.drawable.bg_speech_bubble_outgoing);
+ binding.ivProfilePic.setVisibility(messageDirection == MessageDirection.INCOMING && thread.isGroup() ? View.VISIBLE : View.GONE);
+ binding.tvUsername.setVisibility(messageDirection == MessageDirection.INCOMING && thread.isGroup() ? View.VISIBLE : View.GONE);
+ if (messageDirection == MessageDirection.INCOMING && thread.isGroup()) {
+ final DirectUser user = getUser(item.getUserId(), thread.getUsers());
+ if (user != null) {
+ binding.tvUsername.setText(user.getUsername());
+ binding.ivProfilePic.setImageURI(user.getProfilePicUrl());
+ }
+ }
+ } else {
+ binding.ivProfilePic.setVisibility(View.GONE);
+ binding.tvUsername.setVisibility(View.GONE);
+ containerLayoutParams.gravity = Gravity.CENTER;
+ if (itemType == DirectItemType.ACTION_LOG) {
+ binding.messageInfo.setVisibility(View.GONE);
+ }
+ }
+ if (itemType == DirectItemType.REEL_SHARE) {
+ containerLayoutParams.setMarginStart(0);
+ containerLayoutParams.setMarginEnd(0);
+ }
+ if (itemType == DirectItemType.TEXT || itemType == DirectItemType.LINK) {
+ binding.messageInfo.setPadding(0,
+ 0,
+ dmRadius,
+ dmRadiusSmall);
+ } else {
+ binding.messageInfo.setPadding(0,
+ 0,
+ messageInfoPaddingSmall,
+ dmRadiusSmall);
+ }
+ binding.messageTime.setText(DateFormat.getTimeFormat(itemView.getContext()).format(item.getDate()));
+ if (messageDirection == MessageDirection.OUTGOING) {
+ if (item.isPending()) {
+ binding.deliveryStatus.setImageResource(R.drawable.ic_check_24);
+ } else {
+ final boolean read = ResponseBodyUtils.isRead(item, thread.getLastSeenAt(), userIds, null);
+ binding.deliveryStatus.setImageResource(R.drawable.ic_check_all_24);
+ ImageViewCompat.setImageTintList(
+ binding.deliveryStatus,
+ ColorStateList.valueOf(itemView.getResources().getColor(read ? R.color.blue_500 : R.color.grey_500))
+ );
+ }
+ }
+ if (item.getRepliedToMessage() != null) {
+ setReply(item, messageDirection, thread.getUsers());
+ } else {
+ binding.quoteLine.setVisibility(View.GONE);
+ binding.replyContainer.setVisibility(View.GONE);
+ binding.replyInfo.setVisibility(View.GONE);
+ }
+ setReactions(item, thread.getUsers());
+ bindItem(item, messageDirection);
+ }
+
+ private void setReply(final DirectItem item,
+ final MessageDirection messageDirection,
+ final List users) {
+ final DirectItem replied = item.getRepliedToMessage();
+ final DirectItemType itemType = replied.getItemType();
+ String text = null;
+ String url = null;
+ switch (itemType) {
+ case TEXT:
+ text = replied.getText();
+ break;
+ case LINK:
+ text = replied.getLink().getText();
+ break;
+ case PLACEHOLDER:
+ text = replied.getPlaceholder().getMessage();
+ break;
+ case MEDIA:
+ url = ResponseBodyUtils.getThumbUrl(replied.getMedia().getImageVersions2());
+ break;
+ case RAVEN_MEDIA:
+ url = ResponseBodyUtils.getThumbUrl(replied.getVisualMedia().getMedia().getImageVersions2());
+ break;
+ case VOICE_MEDIA:
+ text = "Voice message";
+ break;
+ case MEDIA_SHARE:
+ DirectItemMedia mediaShare = replied.getMediaShare();
+ if (mediaShare.getMediaType() == MediaItemType.MEDIA_TYPE_SLIDER) {
+ mediaShare = mediaShare.getCarouselMedia().get(0);
+ }
+ url = ResponseBodyUtils.getThumbUrl(mediaShare.getImageVersions2());
+ break;
+ case REEL_SHARE:
+ text = replied.getReelShare().getText();
+ break;
+ // Below types cannot be replied to
+ // case LIKE:
+ // text = "โค๏ธ";
+ // break;
+ // case PROFILE:
+ // text = "@" + replied.getProfile().getUsername();
+ // break;
+ // case CLIP:
+ // url = ResponseBodyUtils.getThumbUrl(replied.getClip().getClip().getImageVersions2());
+ // break;
+ // case FELIX_SHARE:
+ // url = ResponseBodyUtils.getThumbUrl(replied.getFelixShare().getVideo().getImageVersions2());
+ // break;
+ // case STORY_SHARE:
+ // final DirectItemMedia media = replied.getStoryShare().getMedia();
+ // if (media == null) break;
+ // url = ResponseBodyUtils.getThumbUrl(media.getImageVersions2());
+ // break;
+ }
+ if (text == null && url == null) {
+ binding.quoteLine.setVisibility(View.GONE);
+ binding.replyContainer.setVisibility(View.GONE);
+ binding.replyInfo.setVisibility(View.GONE);
+ return;
+ }
+ setReplyGravity(messageDirection);
+ final String info = setReplyInfo(item, replied, users);
+ binding.replyInfo.setVisibility(View.VISIBLE);
+ binding.replyInfo.setText(info);
+ binding.quoteLine.setVisibility(View.VISIBLE);
+ binding.replyContainer.setVisibility(View.VISIBLE);
+ if (url != null) {
+ binding.replyText.setVisibility(View.GONE);
+ binding.replyImage.setVisibility(View.VISIBLE);
+ binding.replyImage.setImageURI(url);
+ return;
+ }
+ binding.replyImage.setVisibility(View.GONE);
+ final Drawable background = binding.replyText.getBackground().mutate();
+ final Resources resources = itemView.getResources();
+ background.setTint(replied.getUserId() != currentUser.getPk()
+ ? resources.getColor(R.color.grey_600)
+ : resources.getColor(R.color.deep_purple_400));
+ binding.replyText.setBackgroundDrawable(background);
+ binding.replyText.setVisibility(View.VISIBLE);
+ binding.replyText.setText(text);
+ }
+
+ private String setReplyInfo(final DirectItem item,
+ final DirectItem replied,
+ final List users) {
+ final long repliedToUserId = replied.getUserId();
+ if (repliedToUserId == item.getUserId() && item.getUserId() == currentUser.getPk()) {
+ // User replied to own message
+ return "You replied to yourself";
+ }
+ if (repliedToUserId == item.getUserId()) {
+ // opposite user replied to their own message
+ return "Replied to themself";
+ }
+ final DirectUser user = getUser(repliedToUserId, users);
+ final String repliedToUsername = user != null ? user.getUsername() : "";
+ if (item.getUserId() == currentUser.getPk()) {
+ return !thread.isGroup() ? "You replied" : String.format("You replied to %s", repliedToUsername);
+ }
+ if (repliedToUserId == currentUser.getPk()) {
+ return "Replied to you";
+ }
+ return String.format("Replied to %s", repliedToUsername);
+ }
+
+ private void setReplyGravity(final MessageDirection messageDirection) {
+ final boolean isIncoming = messageDirection == MessageDirection.INCOMING;
+ final ConstraintLayout.LayoutParams quoteLineLayoutParams = (ConstraintLayout.LayoutParams) binding.quoteLine.getLayoutParams();
+ final ConstraintLayout.LayoutParams replyContainerLayoutParams = (ConstraintLayout.LayoutParams) binding.replyContainer.getLayoutParams();
+ final ConstraintLayout.LayoutParams replyInfoLayoutParams = (ConstraintLayout.LayoutParams) binding.replyInfo.getLayoutParams();
+ final int profilePicId = binding.ivProfilePic.getId();
+ final int replyContainerId = binding.replyContainer.getId();
+ final int quoteLineId = binding.quoteLine.getId();
+ quoteLineLayoutParams.startToEnd = isIncoming ? profilePicId : replyContainerId;
+ quoteLineLayoutParams.endToStart = isIncoming ? replyContainerId : ConstraintLayout.LayoutParams.UNSET;
+ quoteLineLayoutParams.endToEnd = isIncoming ? ConstraintLayout.LayoutParams.UNSET : ConstraintLayout.LayoutParams.PARENT_ID;
+ replyContainerLayoutParams.startToEnd = isIncoming ? quoteLineId : profilePicId;
+ replyContainerLayoutParams.endToEnd = isIncoming ? ConstraintLayout.LayoutParams.PARENT_ID : ConstraintLayout.LayoutParams.UNSET;
+ replyContainerLayoutParams.endToStart = isIncoming ? ConstraintLayout.LayoutParams.UNSET : quoteLineId;
+ replyInfoLayoutParams.startToEnd = isIncoming ? quoteLineId : ConstraintLayout.LayoutParams.UNSET;
+ replyInfoLayoutParams.endToStart = isIncoming ? ConstraintLayout.LayoutParams.UNSET : quoteLineId;
+ }
+
+ private void setReactions(final DirectItem item, final List users) {
+ final DirectItemReactions reactions = item.getReactions();
+ final List emojis = reactions != null ? reactions.getEmojis() : null;
+ if (emojis == null || emojis.isEmpty()) {
+ binding.reactions.setVisibility(View.GONE);
+ return;
+ }
+ binding.reactions.setVisibility(View.VISIBLE);
+ final String emojisJoined = emojis.stream()
+ .map(DirectItemEmojiReaction::getEmoji)
+ .collect(Collectors.joining());
+ final String text = String.format(Locale.ENGLISH, "%s %d", emojisJoined, emojis.size());
+ binding.emojis.setText(text);
+ // final List reactedUsers = emojis.stream()
+ // .map(DirectItemEmojiReaction::getSenderId)
+ // .distinct()
+ // .map(userId -> getUser(userId, users))
+ // .collect(Collectors.toList());
+ // for (final DirectUser user : reactedUsers) {
+ // if (user == null) continue;
+ // final ProfilePicView profilePicView = new ProfilePicView(itemView.getContext());
+ // profilePicView.setSize(ProfilePicView.Size.TINY);
+ // profilePicView.setImageURI(user.getProfilePicUrl());
+ // binding.reactions.addView(profilePicView);
+ // }
+ }
+
+ protected boolean isSelf(final DirectItem directItem) {
+ return directItem.getUserId() == currentUser.getPk();
+ }
+
+ public void setItemView(final View view) {
+ this.binding.message.addView(view);
+ }
+
+ public abstract void bindItem(final DirectItem directItemModel, final MessageDirection messageDirection);
+
+ @Nullable
+ protected DirectUser getUser(final long userId, final List users) {
+ if (userId == currentUser.getPk()) {
+ return currentUser;
+ }
+ if (users == null) return null;
+ for (final DirectUser user : users) {
+ if (userId != user.getPk()) continue;
+ return user;
+ }
+ return null;
+ }
+
+ protected void removeBg() {
+ binding.background.setBackground(null);
+ }
+
+ public void cleanup() {}
+
+ public enum MessageDirection {
+ INCOMING,
+ OUTGOING
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemVoiceMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemVoiceMediaViewHolder.java
new file mode 100644
index 00000000..ab2b88e3
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemVoiceMediaViewHolder.java
@@ -0,0 +1,189 @@
+package awais.instagrabber.adapters.viewholder.directmessages;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.source.ProgressiveMediaSource;
+import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
+import com.google.common.primitives.Floats;
+
+import java.util.List;
+
+import awais.instagrabber.R;
+import awais.instagrabber.databinding.LayoutDmBaseBinding;
+import awais.instagrabber.databinding.LayoutDmVoiceMediaBinding;
+import awais.instagrabber.interfaces.MentionClickListener;
+import awais.instagrabber.models.ProfileModel;
+import awais.instagrabber.repositories.responses.directmessages.Audio;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemMedia;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemVoiceMedia;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.utils.TextUtils;
+import awais.instagrabber.utils.Utils;
+
+import static com.google.android.exoplayer2.C.TIME_UNSET;
+
+public class DirectItemVoiceMediaViewHolder extends DirectItemViewHolder {
+ private static final String TAG = "DirectItemVoiceMediaVH";
+
+ private final LayoutDmVoiceMediaBinding binding;
+ private final DefaultDataSourceFactory dataSourceFactory;
+ private SimpleExoPlayer player;
+ private Handler handler;
+ private Runnable positionChecker;
+ private Player.EventListener listener;
+
+ public DirectItemVoiceMediaViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
+ @NonNull final LayoutDmVoiceMediaBinding binding,
+ final ProfileModel currentUser,
+ final DirectThread thread,
+ final MentionClickListener mentionClickListener,
+ final View.OnClickListener onClickListener) {
+ super(baseBinding, currentUser, thread, onClickListener);
+ this.binding = binding;
+ this.dataSourceFactory = new DefaultDataSourceFactory(binding.getRoot().getContext(), "instagram");
+ setItemView(binding.getRoot());
+ final int margin = itemView.getResources().getDimensionPixelSize(R.dimen.dm_message_item_margin);
+ binding.waveformSeekBar.getLayoutParams().width = Utils.displayMetrics.widthPixels - margin - Utils.convertDpToPx(56);
+ }
+
+ @Override
+ public void bindItem(final DirectItem directItemModel, final MessageDirection messageDirection) {
+ removeBg();
+ final DirectItemVoiceMedia voiceMedia = directItemModel.getVoiceMedia();
+ if (voiceMedia == null) return;
+ final DirectItemMedia media = voiceMedia.getMedia();
+ if (media == null) return;
+ final Audio audio = media.getAudio();
+ if (audio == null) return;
+ final List waveformData = audio.getWaveformData();
+ binding.waveformSeekBar.setSample(Floats.toArray(waveformData));
+ binding.waveformSeekBar.setEnabled(false);
+ final String text = String.format("%s/%s", TextUtils.millisToTimeString(0), TextUtils.millisToTimeString(audio.getDuration()));
+ binding.duration.setText(text);
+ final AudioItemState audioItemState = new AudioItemState();
+ player = new SimpleExoPlayer.Builder(itemView.getContext()).build();
+ player.setVolume(1);
+ player.setPlayWhenReady(true);
+ player.setRepeatMode(Player.REPEAT_MODE_OFF);
+ handler = new Handler();
+ final long initialDelay = 0;
+ final long recurringDelay = 60;
+ positionChecker = new Runnable() {
+ @Override
+ public void run() {
+ if (handler != null) {
+ handler.removeCallbacks(this);
+ }
+ if (player == null) return;
+ final long currentPosition = player.getCurrentPosition();
+ final long duration = player.getDuration();
+ // Log.d(TAG, "currentPosition: " + currentPosition + ", duration: " + duration);
+ if (duration == TIME_UNSET) return;
+ // final float progress = ((float) currentPosition / duration /* * 100 */);
+ final int progress = (int) ((float) currentPosition / duration * 100);
+ // Log.d(TAG, "progress: " + progress);
+ final String text = String.format("%s/%s", TextUtils.millisToTimeString(currentPosition), TextUtils.millisToTimeString(duration));
+ binding.duration.setText(text);
+ binding.waveformSeekBar.setProgress(progress);
+ if (handler != null) {
+ handler.postDelayed(this, recurringDelay);
+ }
+ }
+ };
+ player.addListener(listener = new Player.EventListener() {
+ @Override
+ public void onPlaybackStateChanged(final int state) {
+ if (!audioItemState.isPrepared() && state == Player.STATE_READY) {
+ binding.playPause.setIconResource(R.drawable.ic_round_pause_24);
+ audioItemState.setPrepared(true);
+ binding.playPause.setVisibility(View.VISIBLE);
+ binding.progressBar.setVisibility(View.GONE);
+ if (handler != null) {
+ handler.postDelayed(positionChecker, initialDelay);
+ }
+ return;
+ }
+ if (state == Player.STATE_ENDED) {
+ // binding.waveformSeekBar.setProgressInPercentage(0);
+ binding.waveformSeekBar.setProgress(0);
+ binding.playPause.setIconResource(R.drawable.ic_round_play_arrow_24);
+ if (handler != null) {
+ handler.removeCallbacks(positionChecker);
+ }
+ }
+ }
+
+ @Override
+ public void onPlayerError(final ExoPlaybackException error) {
+ Log.e(TAG, "onPlayerError: ", error);
+ }
+ });
+ final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(dataSourceFactory);
+ final MediaItem mediaItem = MediaItem.fromUri(audio.getAudioSrc());
+ final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(mediaItem);
+ player.setMediaSource(mediaSource);
+ binding.playPause.setOnClickListener(v -> {
+ if (player == null) return;
+ if (!audioItemState.isPrepared()) {
+ player.prepare();
+ binding.playPause.setVisibility(View.GONE);
+ binding.progressBar.setVisibility(View.VISIBLE);
+ return;
+ }
+ if (player.isPlaying()) {
+ binding.playPause.setIconResource(R.drawable.ic_round_play_arrow_24);
+ player.pause();
+ return;
+ }
+ binding.playPause.setIconResource(R.drawable.ic_round_pause_24);
+ if (player.getPlaybackState() == Player.STATE_ENDED) {
+ player.seekTo(0);
+ if (handler != null) {
+ handler.postDelayed(positionChecker, initialDelay);
+ }
+ }
+ binding.waveformSeekBar.setEnabled(true);
+ player.play();
+ });
+ }
+
+ @Override
+ public void cleanup() {
+ if (handler != null && positionChecker != null) {
+ handler.removeCallbacks(positionChecker);
+ handler = null;
+ positionChecker = null;
+ }
+ if (player != null) {
+ player.release();
+ if (listener != null) {
+ player.removeListener(listener);
+ }
+ player = null;
+ }
+ }
+
+ private static class AudioItemState {
+ private boolean prepared;
+
+ private AudioItemState() {}
+
+ public boolean isPrepared() {
+ return prepared;
+ }
+
+ public void setPrepared(final boolean prepared) {
+ this.prepared = prepared;
+ }
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageActionLogViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageActionLogViewHolder.java
deleted file mode 100644
index 72cb31d4..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageActionLogViewHolder.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package awais.instagrabber.adapters.viewholder.directmessages;
-
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.core.text.HtmlCompat;
-
-import awais.instagrabber.databinding.LayoutDmBaseBinding;
-import awais.instagrabber.databinding.LayoutDmTextBinding;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-
-import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_COMPACT;
-
-public class DirectMessageActionLogViewHolder extends DirectMessageItemViewHolder {
-
- private final LayoutDmTextBinding binding;
-
- public DirectMessageActionLogViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
- @NonNull final LayoutDmTextBinding binding,
- final View.OnClickListener onClickListener) {
- super(baseBinding, onClickListener);
- this.binding = binding;
- setItemView(binding.getRoot());
- }
-
- @Override
- public void bindItem(final DirectItemModel directItemModel) {
- final String text = directItemModel.getActionLogModel().getDescription();
- binding.tvMessage.setText(HtmlCompat.fromHtml("" + text + "", FROM_HTML_MODE_COMPACT));
- binding.tvMessage.setVisibility(View.VISIBLE);
- }
-}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageAnimatedMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageAnimatedMediaViewHolder.java
deleted file mode 100644
index 65ed59af..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageAnimatedMediaViewHolder.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package awais.instagrabber.adapters.viewholder.directmessages;
-
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.core.util.Pair;
-
-import com.facebook.drawee.backends.pipeline.Fresco;
-
-import awais.instagrabber.R;
-import awais.instagrabber.databinding.LayoutDmAnimatedMediaBinding;
-import awais.instagrabber.databinding.LayoutDmBaseBinding;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-import awais.instagrabber.utils.NumberUtils;
-import awais.instagrabber.utils.Utils;
-
-public class DirectMessageAnimatedMediaViewHolder extends DirectMessageItemViewHolder {
-
- private final LayoutDmAnimatedMediaBinding binding;
- private final int maxHeight;
- private final int maxWidth;
-
- public DirectMessageAnimatedMediaViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
- @NonNull final LayoutDmAnimatedMediaBinding binding,
- final View.OnClickListener onClickListener) {
- super(baseBinding, onClickListener);
- this.binding = binding;
- maxHeight = itemView.getResources().getDimensionPixelSize(R.dimen.dm_media_img_max_height);
- maxWidth = Utils.displayMetrics.widthPixels - Utils.convertDpToPx(64) - getItemMargin();
- setItemView(binding.getRoot());
- setupForAnimatedMedia();
- }
-
- @Override
- public void bindItem(final DirectItemModel directItemModel) {
- final DirectItemModel.DirectItemAnimatedMediaModel animatedMediaModel = directItemModel.getAnimatedMediaModel();
- final String url = animatedMediaModel.getWebpUrl();
- final Pair widthHeight = NumberUtils.calculateWidthHeight(
- animatedMediaModel.getHeight(),
- animatedMediaModel.getWidth(),
- maxHeight,
- maxWidth
- );
- binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
- final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams();
- final int width = widthHeight.first != null ? widthHeight.first : 0;
- final int height = widthHeight.second != null ? widthHeight.second : 0;
- layoutParams.width = width;
- layoutParams.height = height;
- binding.ivAnimatedMessage.requestLayout();
- binding.ivAnimatedMessage.setController(Fresco.newDraweeControllerBuilder()
- .setUri(url)
- .setAutoPlayAnimations(true)
- .build());
- }
-}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageDefaultViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageDefaultViewHolder.java
deleted file mode 100644
index 5a8ff31a..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageDefaultViewHolder.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package awais.instagrabber.adapters.viewholder.directmessages;
-
-import android.content.Context;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-
-import awais.instagrabber.R;
-import awais.instagrabber.databinding.LayoutDmBaseBinding;
-import awais.instagrabber.databinding.LayoutDmTextBinding;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-
-public class DirectMessageDefaultViewHolder extends DirectMessageItemViewHolder {
-
- private final LayoutDmTextBinding binding;
-
- public DirectMessageDefaultViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
- @NonNull final LayoutDmTextBinding binding,
- final View.OnClickListener onClickListener) {
- super(baseBinding, onClickListener);
- this.binding = binding;
- setItemView(binding.getRoot());
- }
-
- @Override
- public void bindItem(final DirectItemModel directItemModel) {
- final Context context = itemView.getContext();
- binding.tvMessage.setText(context.getText(R.string.dms_inbox_raven_message_unknown));
- }
-}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageItemViewHolder.java
deleted file mode 100644
index 3f3011c0..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageItemViewHolder.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package awais.instagrabber.adapters.viewholder.directmessages;
-
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.RecyclerView;
-
-import java.util.List;
-
-import awais.instagrabber.databinding.LayoutDmBaseBinding;
-import awais.instagrabber.models.ProfileModel;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-import awais.instagrabber.utils.Constants;
-import awais.instagrabber.utils.CookieUtils;
-import awais.instagrabber.utils.TextUtils;
-import awais.instagrabber.utils.Utils;
-
-public abstract class DirectMessageItemViewHolder extends RecyclerView.ViewHolder {
- private static final int MESSAGE_INCOMING = 69;
- private static final int MESSAGE_OUTGOING = 420;
-
- private final ProfileModel myProfileHolder = ProfileModel.getDefaultProfileModel(
- CookieUtils.getUserIdFromCookie(Utils.settingsHelper.getString(Constants.COOKIE)));
- private final LayoutDmBaseBinding binding;
- private final int itemMargin;
-
- public DirectMessageItemViewHolder(@NonNull final LayoutDmBaseBinding binding, @NonNull final View.OnClickListener onClickListener) {
- super(binding.getRoot());
- this.binding = binding;
- binding.ivProfilePic.setOnClickListener(onClickListener);
- binding.messageCard.setOnClickListener(onClickListener);
- // final String strDmYou = binding.getRoot().getContext().getString(R.string.direct_messages_you);
- itemMargin = Utils.displayMetrics.widthPixels / 5;
- }
-
- public void bind(final DirectItemModel directItemModel, final List users, final List leftUsers) {
- final ProfileModel user = getUser(directItemModel.getUserId(), users, leftUsers);
- final int type = user == myProfileHolder ? MESSAGE_OUTGOING : MESSAGE_INCOMING;
-
- final RecyclerView.LayoutParams itemViewLayoutParams = (RecyclerView.LayoutParams) itemView.getLayoutParams();
- itemViewLayoutParams.setMargins(type == MESSAGE_OUTGOING ? itemMargin : 0, 0,
- type == MESSAGE_INCOMING ? itemMargin : 0, 0);
-
- final ViewGroup messageCardParent = (ViewGroup) binding.messageCard.getParent();
- binding.contentContainer.setGravity(type == MESSAGE_INCOMING ? Gravity.START : Gravity.END);
-
- CharSequence text = "?";
- if (user != null && user != myProfileHolder) {
- text = user.getUsername();
- } else if (user == myProfileHolder) text = "";
- text = (TextUtils.isEmpty(text) ? "" : text + " - ") + directItemModel.getDateTime();
- binding.tvUsername.setText(text);
- binding.tvUsername.setGravity(type == MESSAGE_INCOMING ? Gravity.START : Gravity.END);
- binding.ivProfilePic.setVisibility(type == MESSAGE_INCOMING ? View.VISIBLE : View.GONE);
- binding.ivProfilePic.setTag(user);
- binding.likedContainer.setVisibility(directItemModel.isLiked() ? View.VISIBLE : View.GONE);
- messageCardParent.setTag(directItemModel);
- binding.messageCard.setTag(directItemModel);
-
- if (type == MESSAGE_INCOMING && user != null) {
- binding.ivProfilePic.setImageURI(user.getSdProfilePic());
- }
-
- bindItem(directItemModel);
- }
-
- public void setItemView(final View view) {
- this.binding.messageCard.addView(view);
- }
-
- public int getItemMargin() {
- return itemMargin;
- }
-
- public abstract void bindItem(final DirectItemModel directItemModel);
-
- @Nullable
- private ProfileModel getUser(final long userId, final List users, final List leftUsers) {
- if (users != null) {
- ProfileModel result = myProfileHolder;
- for (final ProfileModel user : users) {
- if (Long.toString(userId).equals(user.getId())) result = user;
- }
- if (leftUsers != null)
- for (final ProfileModel leftUser : leftUsers) {
- if (Long.toString(userId).equals(leftUser.getId())) result = leftUser;
- }
- return result;
- }
- return null;
- }
-
- protected void setupForAnimatedMedia() {
- binding.messageCard.setCardElevation(0);
- binding.messageCard.setCardBackgroundColor(itemView.getResources().getColor(android.R.color.transparent));
- }
-}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageLinkViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageLinkViewHolder.java
deleted file mode 100644
index 776c9355..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageLinkViewHolder.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package awais.instagrabber.adapters.viewholder.directmessages;
-
-import android.view.View;
-
-import androidx.annotation.NonNull;
-
-import awais.instagrabber.databinding.LayoutDmBaseBinding;
-import awais.instagrabber.databinding.LayoutDmLinkBinding;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-import awais.instagrabber.utils.TextUtils;
-
-public class DirectMessageLinkViewHolder extends DirectMessageItemViewHolder {
-
- private final LayoutDmLinkBinding binding;
-
- public DirectMessageLinkViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
- @NonNull final LayoutDmLinkBinding binding,
- final View.OnClickListener onClickListener) {
- super(baseBinding, onClickListener);
- this.binding = binding;
- setItemView(binding.getRoot());
- }
-
- @Override
- public void bindItem(final DirectItemModel directItemModel) {
- final DirectItemModel.DirectItemLinkModel link = directItemModel.getLinkModel();
- final DirectItemModel.DirectItemLinkContext linkContext = link.getLinkContext();
- final String linkImageUrl = linkContext.getLinkImageUrl();
- if (TextUtils.isEmpty(linkImageUrl)) {
- binding.ivLinkPreview.setVisibility(View.GONE);
- } else {
- binding.ivLinkPreview.setImageURI(linkImageUrl);
- }
- if (TextUtils.isEmpty(linkContext.getLinkTitle())) {
- binding.tvLinkTitle.setVisibility(View.GONE);
- } else {
- binding.tvLinkTitle.setText(linkContext.getLinkTitle());
- }
- if (TextUtils.isEmpty(linkContext.getLinkSummary())) {
- binding.tvLinkSummary.setVisibility(View.GONE);
- } else {
- binding.tvLinkSummary.setText(linkContext.getLinkSummary());
- }
- binding.tvMessage.setText(TextUtils.getSpannableUrl(link.getText()));
- }
-}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageMediaShareViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageMediaShareViewHolder.java
deleted file mode 100644
index 02a79cff..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageMediaShareViewHolder.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package awais.instagrabber.adapters.viewholder.directmessages;
-
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.core.text.HtmlCompat;
-import androidx.core.util.Pair;
-
-import awais.instagrabber.R;
-import awais.instagrabber.databinding.LayoutDmBaseBinding;
-import awais.instagrabber.databinding.LayoutDmMediaShareBinding;
-import awais.instagrabber.models.ProfileModel;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemMediaModel;
-import awais.instagrabber.models.enums.MediaItemType;
-import awais.instagrabber.utils.NumberUtils;
-import awais.instagrabber.utils.Utils;
-
-import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_COMPACT;
-
-public class DirectMessageMediaShareViewHolder extends DirectMessageItemViewHolder {
-
- private final LayoutDmMediaShareBinding binding;
- private final int maxHeight;
- private final int maxWidth;
-
- public DirectMessageMediaShareViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
- @NonNull final LayoutDmMediaShareBinding binding,
- final View.OnClickListener onClickListener) {
- super(baseBinding, onClickListener);
- this.binding = binding;
- maxHeight = itemView.getResources().getDimensionPixelSize(R.dimen.dm_media_img_max_height);
- maxWidth = (int) (Utils.displayMetrics.widthPixels * 0.8);
- setItemView(binding.getRoot());
- }
-
- @Override
- public void bindItem(final DirectItemModel directItemModel) {
- final Context context = itemView.getContext();
- final DirectItemMediaModel mediaModel = directItemModel.getMediaModel();
- final ProfileModel modelUser = mediaModel.getUser();
- if (modelUser != null) {
- binding.tvMessage.setText(HtmlCompat.fromHtml(
- "" + context.getString(R.string.dms_inbox_media_shared_from, modelUser.getUsername()) + "",
- FROM_HTML_MODE_COMPACT));
- }
- final Pair widthHeight = NumberUtils.calculateWidthHeight(
- mediaModel.getHeight(),
- mediaModel.getWidth(),
- maxHeight,
- maxWidth
- );
- final ViewGroup.LayoutParams layoutParams = binding.ivMediaPreview.getLayoutParams();
- layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
- layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
- binding.ivMediaPreview.requestLayout();
- binding.ivMediaPreview.setImageURI(mediaModel.getThumbUrl());
- final MediaItemType modelMediaType = mediaModel.getMediaType();
- binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO
- || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
- }
-}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageMediaViewHolder.java
deleted file mode 100644
index 91c692c5..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageMediaViewHolder.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package awais.instagrabber.adapters.viewholder.directmessages;
-
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.core.util.Pair;
-
-import awais.instagrabber.R;
-import awais.instagrabber.databinding.LayoutDmBaseBinding;
-import awais.instagrabber.databinding.LayoutDmMediaBinding;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-import awais.instagrabber.models.enums.MediaItemType;
-import awais.instagrabber.utils.NumberUtils;
-import awais.instagrabber.utils.Utils;
-
-public class DirectMessageMediaViewHolder extends DirectMessageItemViewHolder {
-
- private final LayoutDmMediaBinding binding;
- private final int maxHeight;
- private final int maxWidth;
-
- public DirectMessageMediaViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
- @NonNull final LayoutDmMediaBinding binding,
- final View.OnClickListener onClickListener) {
- super(baseBinding, onClickListener);
- this.binding = binding;
- maxHeight = itemView.getResources().getDimensionPixelSize(R.dimen.dm_media_img_max_height);
- maxWidth = (int) (Utils.displayMetrics.widthPixels - Utils.convertDpToPx(64) - getItemMargin());
- setItemView(binding.getRoot());
- }
-
- @Override
- public void bindItem(final DirectItemModel directItemModel) {
- final DirectItemModel.DirectItemMediaModel mediaModel = directItemModel.getMediaModel();
- final Pair widthHeight = NumberUtils.calculateWidthHeight(
- mediaModel.getHeight(),
- mediaModel.getWidth(),
- maxHeight,
- maxWidth
- );
- final ViewGroup.LayoutParams layoutParams = binding.ivMediaPreview.getLayoutParams();
- layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
- layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
- binding.ivMediaPreview.requestLayout();
- binding.ivMediaPreview.setImageURI(mediaModel.getThumbUrl());
- final MediaItemType modelMediaType = mediaModel.getMediaType();
- binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER
- ? View.VISIBLE
- : View.GONE);
- }
-}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessagePlaceholderViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessagePlaceholderViewHolder.java
deleted file mode 100644
index 78eec36b..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessagePlaceholderViewHolder.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package awais.instagrabber.adapters.viewholder.directmessages;
-
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.core.text.HtmlCompat;
-
-import awais.instagrabber.databinding.LayoutDmBaseBinding;
-import awais.instagrabber.databinding.LayoutDmTextBinding;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-
-import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_COMPACT;
-
-public class DirectMessagePlaceholderViewHolder extends DirectMessageItemViewHolder {
-
- private final LayoutDmTextBinding binding;
-
- public DirectMessagePlaceholderViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
- @NonNull final LayoutDmTextBinding binding,
- final View.OnClickListener onClickListener) {
- super(baseBinding, onClickListener);
- this.binding = binding;
- setItemView(binding.getRoot());
- }
-
- @Override
- public void bindItem(final DirectItemModel directItemModel) {
- binding.tvMessage.setText(HtmlCompat.fromHtml(directItemModel.getText().toString(), FROM_HTML_MODE_COMPACT));
- }
-}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageProfileViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageProfileViewHolder.java
deleted file mode 100644
index 64f4d70b..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageProfileViewHolder.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package awais.instagrabber.adapters.viewholder.directmessages;
-
-import android.view.View;
-
-import androidx.annotation.NonNull;
-
-import awais.instagrabber.databinding.LayoutDmBaseBinding;
-import awais.instagrabber.databinding.LayoutDmProfileBinding;
-import awais.instagrabber.models.ProfileModel;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-
-public class DirectMessageProfileViewHolder extends DirectMessageItemViewHolder {
-
- private final LayoutDmProfileBinding binding;
-
- public DirectMessageProfileViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
- @NonNull final LayoutDmProfileBinding binding,
- final View.OnClickListener onClickListener) {
- super(baseBinding, onClickListener);
- this.binding = binding;
- binding.btnOpenProfile.setOnClickListener(onClickListener);
- setItemView(binding.getRoot());
- }
-
- @Override
- public void bindItem(final DirectItemModel directItemModel) {
- final ProfileModel profileModel = directItemModel.getProfileModel();
- if (profileModel == null) return;
- binding.profileInfo.setImageURI(profileModel.getSdProfilePic());
- binding.btnOpenProfile.setTag(profileModel);
- binding.tvFullName.setText(profileModel.getName());
- binding.profileInfoText.setText(profileModel.getUsername());
- binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
- }
-}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageRavenMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageRavenMediaViewHolder.java
deleted file mode 100644
index 4e1c9120..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageRavenMediaViewHolder.java
+++ /dev/null
@@ -1,104 +0,0 @@
-package awais.instagrabber.adapters.viewholder.directmessages;
-
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.core.util.Pair;
-
-import awais.instagrabber.R;
-import awais.instagrabber.databinding.LayoutDmBaseBinding;
-import awais.instagrabber.databinding.LayoutDmRavenMediaBinding;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-import awais.instagrabber.models.enums.MediaItemType;
-import awais.instagrabber.models.enums.RavenExpiringMediaType;
-import awais.instagrabber.models.enums.RavenMediaViewType;
-import awais.instagrabber.utils.NumberUtils;
-import awais.instagrabber.utils.TextUtils;
-import awais.instagrabber.utils.Utils;
-
-public class DirectMessageRavenMediaViewHolder extends DirectMessageItemViewHolder {
-
- private final LayoutDmRavenMediaBinding binding;
- private final int maxHeight;
- private final int maxWidth;
-
- public DirectMessageRavenMediaViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
- @NonNull final LayoutDmRavenMediaBinding binding,
- final View.OnClickListener onClickListener) {
- super(baseBinding, onClickListener);
- this.binding = binding;
- maxHeight = itemView.getResources().getDimensionPixelSize(R.dimen.dm_media_img_max_height);
- maxWidth = (int) (Utils.displayMetrics.widthPixels * 0.8);
- binding.tvMessage.setVisibility(View.GONE);
- setItemView(binding.getRoot());
- }
-
- @Override
- public void bindItem(final DirectItemModel directItemModel) {
- final Context context = itemView.getContext();
- final DirectItemModel.DirectItemRavenMediaModel ravenMediaModel = directItemModel.getRavenMediaModel();
- DirectItemModel.DirectItemMediaModel mediaModel = directItemModel.getMediaModel();
- final boolean isExpired = ravenMediaModel == null || (mediaModel = ravenMediaModel.getMedia()) == null ||
- TextUtils.isEmpty(mediaModel.getThumbUrl()) && mediaModel.getPk() < 1;
-
- DirectItemModel.RavenExpiringMediaActionSummaryModel mediaActionSummary = null;
- if (ravenMediaModel != null) {
- mediaActionSummary = ravenMediaModel.getExpiringMediaActionSummary();
- }
- binding.mediaExpiredIcon.setVisibility(isExpired ? View.VISIBLE : View.GONE);
-
- int textRes = R.string.dms_inbox_raven_media_unknown;
- if (isExpired) textRes = R.string.dms_inbox_raven_media_expired;
-
- if (!isExpired) {
- if (mediaActionSummary != null) {
- final RavenExpiringMediaType expiringMediaType = mediaActionSummary.getType();
-
- if (expiringMediaType == RavenExpiringMediaType.RAVEN_DELIVERED)
- textRes = R.string.dms_inbox_raven_media_delivered;
- else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SENT)
- textRes = R.string.dms_inbox_raven_media_sent;
- else if (expiringMediaType == RavenExpiringMediaType.RAVEN_OPENED)
- textRes = R.string.dms_inbox_raven_media_opened;
- else if (expiringMediaType == RavenExpiringMediaType.RAVEN_REPLAYED)
- textRes = R.string.dms_inbox_raven_media_replayed;
- else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SENDING)
- textRes = R.string.dms_inbox_raven_media_sending;
- else if (expiringMediaType == RavenExpiringMediaType.RAVEN_BLOCKED)
- textRes = R.string.dms_inbox_raven_media_blocked;
- else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SUGGESTED)
- textRes = R.string.dms_inbox_raven_media_suggested;
- else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SCREENSHOT)
- textRes = R.string.dms_inbox_raven_media_screenshot;
- else if (expiringMediaType == RavenExpiringMediaType.RAVEN_CANNOT_DELIVER)
- textRes = R.string.dms_inbox_raven_media_cant_deliver;
- }
-
- final RavenMediaViewType ravenMediaViewType = ravenMediaModel.getViewType();
- if (ravenMediaViewType == RavenMediaViewType.PERMANENT || ravenMediaViewType == RavenMediaViewType.REPLAYABLE) {
- final MediaItemType mediaType = mediaModel.getMediaType();
- textRes = -1;
- binding.typeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO || mediaType == MediaItemType.MEDIA_TYPE_SLIDER
- ? View.VISIBLE
- : View.GONE);
- final Pair widthHeight = NumberUtils.calculateWidthHeight(
- mediaModel.getHeight(),
- mediaModel.getWidth(),
- maxHeight,
- maxWidth
- );
- final ViewGroup.LayoutParams layoutParams = binding.ivMediaPreview.getLayoutParams();
- layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
- layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
- binding.ivMediaPreview.requestLayout();
- binding.ivMediaPreview.setImageURI(mediaModel.getThumbUrl());
- }
- }
- if (textRes != -1) {
- binding.tvMessage.setText(context.getText(textRes));
- binding.tvMessage.setVisibility(View.VISIBLE);
- }
- }
-}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageReelShareViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageReelShareViewHolder.java
deleted file mode 100644
index 88c60121..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageReelShareViewHolder.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package awais.instagrabber.adapters.viewholder.directmessages;
-
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.core.util.Pair;
-
-import awais.instagrabber.R;
-import awais.instagrabber.databinding.LayoutDmBaseBinding;
-import awais.instagrabber.databinding.LayoutDmRavenMediaBinding;
-import awais.instagrabber.interfaces.MentionClickListener;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-import awais.instagrabber.models.enums.MediaItemType;
-import awais.instagrabber.utils.NumberUtils;
-import awais.instagrabber.utils.TextUtils;
-import awais.instagrabber.utils.Utils;
-
-public class DirectMessageReelShareViewHolder extends DirectMessageItemViewHolder {
-
- private final LayoutDmRavenMediaBinding binding;
- private final int maxHeight;
- private final int maxWidth;
-
- public DirectMessageReelShareViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
- @NonNull final LayoutDmRavenMediaBinding binding,
- final View.OnClickListener onClickListener,
- final MentionClickListener mentionClickListener) {
- super(baseBinding, onClickListener);
- this.binding = binding;
- maxHeight = itemView.getResources().getDimensionPixelSize(R.dimen.dm_media_img_max_height);
- maxWidth = (int) (Utils.displayMetrics.widthPixels * 0.8);
- binding.tvMessage.setMentionClickListener(mentionClickListener);
- setItemView(binding.getRoot());
- }
-
- @Override
- public void bindItem(final DirectItemModel directItemModel) {
- final DirectItemModel.DirectItemReelShareModel reelShare = directItemModel.getReelShare();
- CharSequence text = reelShare.getText();
- if (TextUtils.isEmpty(text)) {
- binding.tvMessage.setVisibility(View.GONE);
- } else {
- if (TextUtils.hasMentions(text)) text = TextUtils.getMentionText(text); // for mentions
- binding.tvMessage.setText(text);
- }
- final DirectItemModel.DirectItemMediaModel reelShareMedia = reelShare.getMedia();
- final MediaItemType mediaType = reelShareMedia.getMediaType();
- if (mediaType == null) {
- binding.mediaExpiredIcon.setVisibility(View.VISIBLE);
- } else {
- binding.typeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
- mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
- final Pair widthHeight = NumberUtils.calculateWidthHeight(
- reelShareMedia.getHeight(),
- reelShareMedia.getWidth(),
- maxHeight,
- maxWidth
- );
- final ViewGroup.LayoutParams layoutParams = binding.ivMediaPreview.getLayoutParams();
- layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
- layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
- binding.ivMediaPreview.requestLayout();
- binding.ivMediaPreview.setImageURI(reelShareMedia.getThumbUrl());
- }
- }
-}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageStoryShareViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageStoryShareViewHolder.java
deleted file mode 100644
index cb19c71d..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageStoryShareViewHolder.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package awais.instagrabber.adapters.viewholder.directmessages;
-
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.core.text.HtmlCompat;
-import androidx.core.util.Pair;
-
-import awais.instagrabber.R;
-import awais.instagrabber.databinding.LayoutDmBaseBinding;
-import awais.instagrabber.databinding.LayoutDmStoryShareBinding;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-import awais.instagrabber.models.enums.MediaItemType;
-import awais.instagrabber.utils.NumberUtils;
-import awais.instagrabber.utils.TextUtils;
-import awais.instagrabber.utils.Utils;
-
-import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_COMPACT;
-
-public class DirectMessageStoryShareViewHolder extends DirectMessageItemViewHolder {
-
- private final LayoutDmStoryShareBinding binding;
- private final int maxHeight;
- private final int maxWidth;
-
- public DirectMessageStoryShareViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
- @NonNull final LayoutDmStoryShareBinding binding,
- final View.OnClickListener onClickListener) {
- super(baseBinding, onClickListener);
- this.binding = binding;
- maxHeight = itemView.getResources().getDimensionPixelSize(R.dimen.dm_media_img_max_height);
- maxWidth = (int) (Utils.displayMetrics.widthPixels * 0.8);
- binding.tvMessage.setVisibility(View.GONE);
- setItemView(binding.getRoot());
- }
-
- @Override
- public void bindItem(final DirectItemModel directItemModel) {
- final DirectItemModel.DirectItemReelShareModel reelShare = directItemModel.getReelShare();
- if (reelShare == null) {
- binding.tvMessage.setText(HtmlCompat.fromHtml(directItemModel.getText().toString(), FROM_HTML_MODE_COMPACT));
- binding.tvMessage.setVisibility(View.VISIBLE);
- } else {
- final String text = reelShare.getText();
- if (!TextUtils.isEmpty(text)) {
- binding.tvMessage.setText(text);
- binding.tvMessage.setVisibility(View.VISIBLE);
- } else {
- final DirectItemModel.DirectItemMediaModel reelShareMedia = reelShare.getMedia();
- final MediaItemType mediaType = reelShareMedia.getMediaType();
- binding.typeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ? View.VISIBLE : View.GONE);
- final Pair widthHeight = NumberUtils.calculateWidthHeight(
- reelShareMedia.getHeight(),
- reelShareMedia.getWidth(),
- maxHeight,
- maxWidth
- );
- final ViewGroup.LayoutParams layoutParams = binding.ivMediaPreview.getLayoutParams();
- layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
- layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
- binding.ivMediaPreview.requestLayout();
- binding.ivMediaPreview.setImageURI(reelShareMedia.getThumbUrl());
- }
- }
- }
-}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageTextViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageTextViewHolder.java
deleted file mode 100644
index e8d0bb72..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageTextViewHolder.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package awais.instagrabber.adapters.viewholder.directmessages;
-
-import android.content.Context;
-import android.text.Spanned;
-import android.view.View;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-
-import awais.instagrabber.R;
-import awais.instagrabber.databinding.LayoutDmBaseBinding;
-import awais.instagrabber.databinding.LayoutDmTextBinding;
-import awais.instagrabber.interfaces.MentionClickListener;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-import awais.instagrabber.utils.TextUtils;
-
-public class DirectMessageTextViewHolder extends DirectMessageItemViewHolder {
-
- private final LayoutDmTextBinding binding;
-
- public DirectMessageTextViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
- @NonNull final LayoutDmTextBinding binding,
- final View.OnClickListener onClickListener,
- final MentionClickListener mentionClickListener) {
- super(baseBinding, onClickListener);
- this.binding = binding;
- this.binding.tvMessage.setMentionClickListener(mentionClickListener);
- setItemView(binding.getRoot());
- }
-
- @Override
- public void bindItem(final DirectItemModel directItemModel) {
- final Context context = itemView.getContext();
- CharSequence text = directItemModel.getText();
- text = TextUtils.getSpannableUrl(text.toString()); // for urls
- if (TextUtils.hasMentions(text)) text = TextUtils.getMentionText(text); // for mentions
- if (text instanceof Spanned)
- binding.tvMessage.setText(text, TextView.BufferType.SPANNABLE);
- else if (text == "") {
- binding.tvMessage.setText(context.getText(R.string.dms_inbox_raven_message_unknown));
- } else binding.tvMessage.setText(text);
- }
-}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageVideoCallEventViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageVideoCallEventViewHolder.java
deleted file mode 100644
index e8f9fa68..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageVideoCallEventViewHolder.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package awais.instagrabber.adapters.viewholder.directmessages;
-
-import android.view.View;
-
-import androidx.annotation.NonNull;
-
-import awais.instagrabber.databinding.LayoutDmBaseBinding;
-import awais.instagrabber.databinding.LayoutDmTextBinding;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-
-public class DirectMessageVideoCallEventViewHolder extends DirectMessageItemViewHolder {
-
- private final LayoutDmTextBinding binding;
-
- public DirectMessageVideoCallEventViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
- @NonNull final LayoutDmTextBinding binding,
- final View.OnClickListener onClickListener) {
- super(baseBinding, onClickListener);
- this.binding = binding;
- setItemView(binding.getRoot());
- }
-
- @Override
- public void bindItem(final DirectItemModel directItemModel) {
- // todo add call event info
- binding.tvMessage.setVisibility(View.VISIBLE);
- binding.tvMessage.setBackgroundColor(0xFF_1F90E6);
- }
-}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageVoiceMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageVoiceMediaViewHolder.java
deleted file mode 100644
index bbee55fa..00000000
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageVoiceMediaViewHolder.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package awais.instagrabber.adapters.viewholder.directmessages;
-
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-
-import androidx.annotation.NonNull;
-
-import awais.instagrabber.databinding.LayoutDmBaseBinding;
-import awais.instagrabber.databinding.LayoutDmVoiceMediaBinding;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-import awais.instagrabber.utils.NumberUtils;
-
-public class DirectMessageVoiceMediaViewHolder extends DirectMessageItemViewHolder {
-
- private final LayoutDmVoiceMediaBinding binding;
-
- private DirectItemModel.DirectItemVoiceMediaModel prevVoiceModel;
- private ImageView prevPlayIcon;
-
- public DirectMessageVoiceMediaViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
- @NonNull final LayoutDmVoiceMediaBinding binding,
- final View.OnClickListener onClickListener) {
- super(baseBinding, onClickListener);
- this.binding = binding;
-
- // todo pause / resume
- // todo release prev audio, start new voice
- binding.btnPlayVoice.setOnClickListener(v -> {
- final Object tag = v.getTag();
- final ImageView playIcon = (ImageView) ((ViewGroup) v).getChildAt(0);
- final DirectItemModel.DirectItemVoiceMediaModel voiceMediaModel = (DirectItemModel.DirectItemVoiceMediaModel) tag;
- final boolean voicePlaying = voiceMediaModel.isPlaying();
- voiceMediaModel.setPlaying(!voicePlaying);
-
- if (voiceMediaModel == prevVoiceModel) {
- // todo pause / resume
- } else {
- // todo release prev audio, start new voice
- if (prevVoiceModel != null) prevVoiceModel.setPlaying(false);
- if (prevPlayIcon != null)
- prevPlayIcon.setImageResource(android.R.drawable.ic_media_play);
- }
-
- if (voicePlaying) {
- playIcon.setImageResource(android.R.drawable.ic_media_play);
- } else {
- playIcon.setImageResource(android.R.drawable.ic_media_pause);
- }
- prevVoiceModel = voiceMediaModel;
- prevPlayIcon = playIcon;
- });
- setItemView(binding.getRoot());
- }
-
- @Override
- public void bindItem(final DirectItemModel directItemModel) {
- final DirectItemModel.DirectItemVoiceMediaModel voiceMediaModel = directItemModel.getVoiceMediaModel();
- if (voiceMediaModel != null) {
- final int[] waveformData = voiceMediaModel.getWaveformData();
- if (waveformData != null) binding.waveformSeekBar.setSample(waveformData);
-
- final long durationMs = voiceMediaModel.getDurationMs();
- binding.tvVoiceDuration.setText(NumberUtils.millisToString(durationMs));
- binding.waveformSeekBar.setProgress(voiceMediaModel.getProgress());
- binding.waveformSeekBar.setProgressChangeListener((waveformSeekBar, progress, fromUser) -> {
- // todo progress audio player
- voiceMediaModel.setProgress(progress);
- if (fromUser)
- binding.tvVoiceDuration.setText(NumberUtils.millisToString(durationMs * progress / 100));
- });
- binding.btnPlayVoice.setTag(voiceMediaModel);
- } else {
- binding.waveformSeekBar.setProgress(0);
- }
- }
-}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedVideoViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedVideoViewHolder.java
index e82e9893..c6ddb9a0 100644
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedVideoViewHolder.java
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedVideoViewHolder.java
@@ -61,7 +61,6 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
// Log.d(TAG, "Binding post: " + feedModel.getPostId());
this.feedModel = feedModel;
binding.itemFeedBottom.tvVideoViews.setText(String.valueOf(feedModel.getViewCount()));
- // showOrHideDetails(false);
final float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
final VideoPlayerViewHelper.VideoPlayerCallback videoPlayerCallback = new VideoPlayerCallbackAdapter() {
@@ -72,21 +71,14 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
@Override
public void onPlayerViewLoaded() {
- // binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
final ViewGroup.LayoutParams layoutParams = binding.videoPost.playerView.getLayoutParams();
final int requiredWidth = Utils.displayMetrics.widthPixels;
final int resultingHeight = NumberUtils.getResultingHeight(requiredWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
layoutParams.width = requiredWidth;
layoutParams.height = resultingHeight;
binding.videoPost.playerView.requestLayout();
- setMuteIcon(vol == 0f && Utils.sessionVolumeFull ? 1f : vol);
}
};
- // final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
- // final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
- // final Uri uri = Uri.parse(feedModel.getDisplayUrl());
- // final MediaItem mediaItem = MediaItem.fromUri(uri);
- // final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(mediaItem);
final float aspectRatio = (float) feedModel.getImageWidth() / feedModel.getImageHeight();
final VideoPlayerViewHelper videoPlayerViewHelper = new VideoPlayerViewHelper(binding.getRoot().getContext(),
binding.videoPost,
@@ -104,17 +96,6 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
binding.videoPost.thumbnail.requestLayout();
}
});
- // binding.itemFeedBottom.btnMute.setOnClickListener(v -> {
- // final float newVol = videoPlayerViewHelper.toggleMute();
- // setMuteIcon(newVol);
- // Utils.sessionVolumeFull = newVol == 1f;
- // });
- // binding.videoPost.playerView.setOnClickListener(v -> videoPlayerViewHelper.togglePlayback());
- }
-
-
- private void setMuteIcon(final float vol) {
- // binding.itemFeedBottom.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24 : R.drawable.ic_volume_off_24);
}
public FeedModel getCurrentFeedModel() {
diff --git a/app/src/main/java/awais/instagrabber/animations/CubicBezierInterpolator.java b/app/src/main/java/awais/instagrabber/animations/CubicBezierInterpolator.java
new file mode 100644
index 00000000..3c137d74
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/animations/CubicBezierInterpolator.java
@@ -0,0 +1,74 @@
+package awais.instagrabber.animations;
+
+import android.graphics.PointF;
+import android.view.animation.Interpolator;
+
+public class CubicBezierInterpolator implements Interpolator {
+
+ public static final CubicBezierInterpolator DEFAULT = new CubicBezierInterpolator(0.25, 0.1, 0.25, 1);
+ public static final CubicBezierInterpolator EASE_OUT = new CubicBezierInterpolator(0, 0, .58, 1);
+ public static final CubicBezierInterpolator EASE_OUT_QUINT = new CubicBezierInterpolator(.23, 1, .32, 1);
+ public static final CubicBezierInterpolator EASE_IN = new CubicBezierInterpolator(.42, 0, 1, 1);
+ public static final CubicBezierInterpolator EASE_BOTH = new CubicBezierInterpolator(.42, 0, .58, 1);
+
+ protected PointF start;
+ protected PointF end;
+ protected PointF a = new PointF();
+ protected PointF b = new PointF();
+ protected PointF c = new PointF();
+
+ public CubicBezierInterpolator(PointF start, PointF end) throws IllegalArgumentException {
+ if (start.x < 0 || start.x > 1) {
+ throw new IllegalArgumentException("startX value must be in the range [0, 1]");
+ }
+ if (end.x < 0 || end.x > 1) {
+ throw new IllegalArgumentException("endX value must be in the range [0, 1]");
+ }
+ this.start = start;
+ this.end = end;
+ }
+
+ public CubicBezierInterpolator(float startX, float startY, float endX, float endY) {
+ this(new PointF(startX, startY), new PointF(endX, endY));
+ }
+
+ public CubicBezierInterpolator(double startX, double startY, double endX, double endY) {
+ this((float) startX, (float) startY, (float) endX, (float) endY);
+ }
+
+ @Override
+ public float getInterpolation(float time) {
+ return getBezierCoordinateY(getXForTime(time));
+ }
+
+ protected float getBezierCoordinateY(float time) {
+ c.y = 3 * start.y;
+ b.y = 3 * (end.y - start.y) - c.y;
+ a.y = 1 - c.y - b.y;
+ return time * (c.y + time * (b.y + time * a.y));
+ }
+
+ protected float getXForTime(float time) {
+ float x = time;
+ float z;
+ for (int i = 1; i < 14; i++) {
+ z = getBezierCoordinateX(x) - time;
+ if (Math.abs(z) < 1e-3) {
+ break;
+ }
+ x -= z / getXDerivate(x);
+ }
+ return x;
+ }
+
+ private float getXDerivate(float t) {
+ return c.x + t * (2 * b.x + 3 * a.x * t);
+ }
+
+ private float getBezierCoordinateX(float time) {
+ c.x = 3 * start.x;
+ b.x = 3 * (end.x - start.x) - c.x;
+ a.x = 1 - c.x - b.x;
+ return time * (c.x + time * (b.x + time * a.x));
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/animations/ScaleAnimation.java b/app/src/main/java/awais/instagrabber/animations/ScaleAnimation.java
new file mode 100644
index 00000000..c4e8193b
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/animations/ScaleAnimation.java
@@ -0,0 +1,45 @@
+package awais.instagrabber.animations;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+
+public class ScaleAnimation {
+
+ private final View view;
+
+ public ScaleAnimation(View view) {
+ this.view = view;
+ }
+
+
+ public void start() {
+ AnimatorSet set = new AnimatorSet();
+ ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 2.0f);
+
+ ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 2.0f);
+ set.setDuration(150);
+ set.setInterpolator(new AccelerateDecelerateInterpolator());
+ set.playTogether(scaleY, scaleX);
+ set.start();
+ }
+
+ public void stop() {
+ AnimatorSet set = new AnimatorSet();
+ ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1.0f);
+ // scaleY.setDuration(250);
+ // scaleY.setInterpolator(new DecelerateInterpolator());
+
+
+ ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1.0f);
+ // scaleX.setDuration(250);
+ // scaleX.setInterpolator(new DecelerateInterpolator());
+
+
+ set.setDuration(150);
+ set.setInterpolator(new AccelerateDecelerateInterpolator());
+ set.playTogether(scaleY, scaleX);
+ set.start();
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/asyncs/ImageUploader.java b/app/src/main/java/awais/instagrabber/asyncs/ImageUploader.java
deleted file mode 100644
index 386cb4de..00000000
--- a/app/src/main/java/awais/instagrabber/asyncs/ImageUploader.java
+++ /dev/null
@@ -1,176 +0,0 @@
-package awais.instagrabber.asyncs;
-
-import android.graphics.Bitmap;
-import android.os.AsyncTask;
-import android.util.Log;
-
-import org.json.JSONObject;
-
-import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
-import awais.instagrabber.models.ImageUploadOptions;
-import awais.instagrabber.utils.NetworkUtils;
-import awais.instagrabber.utils.NumberUtils;
-
-public class ImageUploader extends AsyncTask {
- private static final String TAG = "ImageUploader";
- private static final long LOWER = 1000000000L;
- private static final long UPPER = 9999999999L;
- private OnImageUploadCompleteListener listener;
-
- protected ImageUploadResponse doInBackground(final ImageUploadOptions... imageUploadOptions) {
- if (imageUploadOptions == null || imageUploadOptions.length == 0 || imageUploadOptions[0] == null) {
- return null;
- }
- HttpURLConnection connection = null;
- OutputStream out = null;
- InputStream inputStream = null;
- BufferedReader r = null;
- ByteArrayOutputStream baos = null;
- try {
- final ImageUploadOptions options = imageUploadOptions[0];
- final Bitmap bitmap = options.getBitmap();
- baos = new ByteArrayOutputStream();
- final boolean compressResult = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
- if (!compressResult) {
- Log.e(TAG, "Compress result was false!");
- return null;
- }
- final byte[] bytes = baos.toByteArray();
- final String contentLength = String.valueOf(bytes.length);
- final Map headers = new HashMap<>();
- final String uploadId = String.valueOf(new Date().getTime());
- final long random = NumberUtils.random(LOWER, UPPER + 1);
- final String name = String.format("%s_0_%s", uploadId, random);
- final String waterfallId = options.getWaterfallId() != null ? options.getWaterfallId() : UUID.randomUUID().toString();
- headers.put("X-Entity-Type", "image/jpeg");
- headers.put("Offset", "0");
- headers.put("X_FB_PHOTO_WATERFALL_ID", waterfallId);
- headers.put("X-Instagram-Rupload-Params", new JSONObject(createPhotoRuploadParams(options, uploadId)).toString());
- headers.put("X-Entity-Name", name);
- headers.put("X-Entity-Length", contentLength);
- headers.put("Content-Type", "application/octet-stream");
- headers.put("Content-Length", contentLength);
- headers.put("Accept-Encoding", "gzip");
- final String url = "https://www.instagram.com/rupload_igphoto/" + name + "/";
- connection = (HttpURLConnection) new URL(url).openConnection();
- connection.setRequestMethod("POST");
- connection.setUseCaches(false);
- connection.setDoOutput(true);
- NetworkUtils.setConnectionHeaders(connection, headers);
- out = new BufferedOutputStream(connection.getOutputStream());
- out.write(bytes);
- out.flush();
- final int responseCode = connection.getResponseCode();
- Log.d(TAG, "response: " + responseCode);
- final String responseCodeString = String.valueOf(responseCode);
- final InputStream responseInputStream = responseCodeString.startsWith("4") || responseCodeString.startsWith("5")
- ? connection.getErrorStream() : connection.getInputStream();
- r = new BufferedReader(new InputStreamReader(responseInputStream));
- final StringBuilder builder = new StringBuilder();
- for (String line = r.readLine(); line != null; line = r.readLine()) {
- if (builder.length() != 0) {
- builder.append("\n");
- }
- builder.append(line);
- }
- return new ImageUploadResponse(responseCode, new JSONObject(builder.toString()));
- } catch (Exception ex) {
- Log.e(TAG, "Image upload error:", ex);
- } finally {
- if (r != null) {
- try {
- r.close();
- } catch (IOException ignored) {}
- }
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (IOException ignored) {}
- }
- if (out != null) {
- try {
- out.close();
- } catch (IOException ignored) {}
- }
- if (connection != null) {
- connection.disconnect();
- }
- if (baos != null) {
- try {
- baos.close();
- } catch (IOException ignored) {}
- }
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(final ImageUploadResponse response) {
- if (listener != null) {
- listener.onImageUploadComplete(response);
- }
- }
-
- private Map createPhotoRuploadParams(final ImageUploadOptions options, final String uploadId) {
- final Map retryContext = new HashMap<>();
- retryContext.put("num_step_auto_retry", 0);
- retryContext.put("num_reupload", 0);
- retryContext.put("num_step_manual_retry", 0);
- final String retryContextString = new JSONObject(retryContext).toString();
- final Map params = new HashMap<>();
- params.put("retry_context", retryContextString);
- params.put("media_type", "1");
- params.put("upload_id", uploadId);
- params.put("xsharing_user_ids", "[]");
- final Map imageCompression = new HashMap<>();
- imageCompression.put("lib_name", "moz");
- imageCompression.put("lib_version", "3.1.m");
- imageCompression.put("quality", "80");
- params.put("image_compression", new JSONObject(imageCompression).toString());
- if (options.isSidecar()) {
- params.put("is_sidecar", "1");
- }
- return params;
- }
-
- public void setOnTaskCompleteListener(final OnImageUploadCompleteListener listener) {
- if (listener != null) {
- this.listener = listener;
- }
- }
-
- public interface OnImageUploadCompleteListener {
- void onImageUploadComplete(ImageUploadResponse response);
- }
-
- public static class ImageUploadResponse {
- private int responseCode;
- private JSONObject response;
-
- public ImageUploadResponse(int responseCode, JSONObject response) {
- this.responseCode = responseCode;
- this.response = response;
- }
-
- public int getResponseCode() {
- return responseCode;
- }
-
- public JSONObject getResponse() {
- return response;
- }
- }
-}
diff --git a/app/src/main/java/awais/instagrabber/asyncs/direct_messages/DirectMessageInboxThreadFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/direct_messages/DirectMessageInboxThreadFetcher.java
deleted file mode 100644
index 1fe89f8f..00000000
--- a/app/src/main/java/awais/instagrabber/asyncs/direct_messages/DirectMessageInboxThreadFetcher.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package awais.instagrabber.asyncs.direct_messages;
-
-import android.os.AsyncTask;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import org.json.JSONObject;
-
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.HashMap;
-import java.util.Map;
-
-import awais.instagrabber.BuildConfig;
-import awais.instagrabber.interfaces.FetchListener;
-import awais.instagrabber.models.direct_messages.InboxThreadModel;
-import awais.instagrabber.models.enums.UserInboxDirection;
-import awais.instagrabber.utils.Constants;
-import awais.instagrabber.utils.LocaleUtils;
-import awais.instagrabber.utils.NetworkUtils;
-import awais.instagrabber.utils.ResponseBodyUtils;
-import awais.instagrabber.utils.TextUtils;
-
-import static awais.instagrabber.utils.Utils.logCollector;
-import static awaisomereport.LogCollector.LogFile;
-
-public final class DirectMessageInboxThreadFetcher extends AsyncTask {
- private static final String TAG = "DMInboxThreadFetcher";
-
- private final String id;
- private final String endCursor;
- private final FetchListener fetchListener;
- private final UserInboxDirection direction;
-
- public DirectMessageInboxThreadFetcher(final String id,
- final UserInboxDirection direction,
- final String cursor,
- final FetchListener fetchListener) {
- this.id = id;
- this.direction = direction;
- this.endCursor = cursor;
- this.fetchListener = fetchListener;
- }
-
- @Nullable
- @Override
- protected InboxThreadModel doInBackground(final Void... voids) {
- InboxThreadModel result = null;
- final Map queryParamsMap = new HashMap<>();
- queryParamsMap.put("visual_message_return_type", "unseen");
- if (direction != null) queryParamsMap.put("direction", direction.getValue());
- if (!TextUtils.isEmpty(endCursor)) {
- queryParamsMap.put("cursor", endCursor);
- }
- final String queryString = NetworkUtils.getQueryString(queryParamsMap);
- final String url = "https://i.instagram.com/api/v1/direct_v2/threads/" + id + "/?" + queryString;
- try {
- final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
- conn.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
- conn.setRequestProperty("Accept-Language", LocaleUtils.getCurrentLocale().getLanguage() + ",en-US;q=0.8");
- conn.setUseCaches(false);
-
- if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
- final JSONObject data = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("thread");
- result = ResponseBodyUtils.createInboxThreadModel(data, true);
- }
-
- conn.disconnect();
- } catch (final Exception e) {
- result = null;
- if (logCollector != null)
- logCollector.appendException(e, LogFile.ASYNC_DMS_THREAD, "doInBackground");
- if (BuildConfig.DEBUG) Log.e(TAG, "", e);
- }
- return result;
- }
-
- @Override
- protected void onPreExecute() {
- if (fetchListener != null) fetchListener.doBefore();
- }
-
- @Override
- protected void onPostExecute(final InboxThreadModel inboxThreadModel) {
- if (fetchListener != null) fetchListener.onResult(inboxThreadModel);
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/asyncs/direct_messages/DirectThreadBroadcaster.java b/app/src/main/java/awais/instagrabber/asyncs/direct_messages/DirectThreadBroadcaster.java
index ea185890..56f696e8 100644
--- a/app/src/main/java/awais/instagrabber/asyncs/direct_messages/DirectThreadBroadcaster.java
+++ b/app/src/main/java/awais/instagrabber/asyncs/direct_messages/DirectThreadBroadcaster.java
@@ -1,253 +1,125 @@
-package awais.instagrabber.asyncs.direct_messages;
-
-import android.os.AsyncTask;
-import android.util.Log;
-
-import org.json.JSONObject;
-
-import java.io.BufferedReader;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
-import awais.instagrabber.utils.Constants;
-import awais.instagrabber.utils.CookieUtils;
-import awais.instagrabber.utils.Utils;
-
-import static awais.instagrabber.utils.Utils.settingsHelper;
-
-public class DirectThreadBroadcaster extends AsyncTask {
- private static final String TAG = "DirectThreadBroadcaster";
-
- private final String threadId;
-
- private OnBroadcastCompleteListener listener;
-
- public DirectThreadBroadcaster(String threadId) {
- this.threadId = threadId;
- }
-
- @Override
- protected DirectThreadBroadcastResponse doInBackground(final BroadcastOptions... broadcastOptionsArray) {
- if (broadcastOptionsArray == null || broadcastOptionsArray.length == 0 || broadcastOptionsArray[0] == null) {
- return null;
- }
- final BroadcastOptions broadcastOptions = broadcastOptionsArray[0];
- final String cookie = settingsHelper.getString(Constants.COOKIE);
- final String cc = UUID.randomUUID().toString();
- final Map form = new HashMap<>();
- form.put("_csrftoken", CookieUtils.getCsrfTokenFromCookie(cookie));
- form.put("_uid", CookieUtils.getUserIdFromCookie(cookie));
- form.put("__uuid", settingsHelper.getString(Constants.DEVICE_UUID));
- form.put("client_context", cc);
- form.put("mutation_token", cc);
- form.putAll(broadcastOptions.getFormMap());
- form.put("thread_id", threadId);
- form.put("action", "send_item");
- final String message = new JSONObject(form).toString();
- final String content = Utils.sign(message);
- final String url = "https://i.instagram.com/api/v1/direct_v2/threads/broadcast/" + broadcastOptions.getItemType().getValue() + "/";
- HttpURLConnection connection = null;
- DataOutputStream outputStream = null;
- BufferedReader r = null;
- try {
- connection = (HttpURLConnection) new URL(url).openConnection();
- connection.setRequestMethod("POST");
- connection.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
- connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
- if (content != null) {
- connection.setRequestProperty("Content-Length", "" + content.getBytes().length);
- }
- connection.setUseCaches(false);
- connection.setDoOutput(true);
- outputStream = new DataOutputStream(connection.getOutputStream());
- outputStream.writeBytes(content);
- outputStream.flush();
- final int responseCode = connection.getResponseCode();
- if (responseCode != HttpURLConnection.HTTP_OK) {
- Log.d(TAG, responseCode + ": " + content + ": " + cookie);
- return new DirectThreadBroadcastResponse(responseCode, null);
- }
- r = new BufferedReader(new InputStreamReader(connection.getInputStream()));
- final StringBuilder builder = new StringBuilder();
- for (String line = r.readLine(); line != null; line = r.readLine()) {
- if (builder.length() != 0) {
- builder.append("\n");
- }
- builder.append(line);
- }
- return new DirectThreadBroadcastResponse(responseCode, new JSONObject(builder.toString()));
- } catch (Exception e) {
- Log.e(TAG, "Error", e);
- } finally {
- if (r != null) {
- try {
- r.close();
- } catch (IOException ignored) {
- }
- }
- if (outputStream != null) {
- try {
- outputStream.close();
- } catch (IOException ignored) {
- }
- }
- if (connection != null) {
- connection.disconnect();
- }
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(final DirectThreadBroadcastResponse result) {
- if (listener != null) {
- listener.onTaskComplete(result);
- }
- }
-
- public void setOnTaskCompleteListener(final OnBroadcastCompleteListener listener) {
- if (listener != null) {
- this.listener = listener;
- }
- }
-
- public interface OnBroadcastCompleteListener {
- void onTaskComplete(DirectThreadBroadcastResponse response);
- }
-
- public enum ItemType {
- TEXT("text"),
- REACTION("reaction"),
- REELSHARE("reel_share"),
- IMAGE("configure_photo");
-
- private final String value;
-
- ItemType(final String value) {
- this.value = value;
- }
-
- public String getValue() {
- return value;
- }
- }
-
- public static abstract class BroadcastOptions {
- private final ItemType itemType;
-
- public BroadcastOptions(final ItemType itemType) {
- this.itemType = itemType;
- }
-
- public ItemType getItemType() {
- return itemType;
- }
-
- abstract Map getFormMap();
- }
-
- public static class TextBroadcastOptions extends BroadcastOptions {
- private final String text;
-
- public TextBroadcastOptions(String text) throws UnsupportedEncodingException {
- super(ItemType.TEXT);
- this.text = URLEncoder.encode(text, "UTF-8")
- .replaceAll("\\+", "%20").replaceAll("%21", "!").replaceAll("%27", "'")
- .replaceAll("%28", "(").replaceAll("%29", ")").replaceAll("%7E", "~").replaceAll("%0A", "\n");
- }
-
- @Override
- Map getFormMap() {
- return Collections.singletonMap("text", text);
- }
- }
-
- public static class ReactionBroadcastOptions extends BroadcastOptions {
- private final String itemId;
- private final boolean delete;
-
- public ReactionBroadcastOptions(String itemId, boolean delete) {
- super(ItemType.REACTION);
- this.itemId = itemId;
- this.delete = delete;
- }
-
- @Override
- Map getFormMap() {
- final Map form = new HashMap<>();
- form.put("item_id", itemId);
- form.put("reaction_status", delete ? "deleted" : "created");
- form.put("reaction_type", "like");
- return form;
- }
- }
-
- public static class StoryReplyBroadcastOptions extends BroadcastOptions {
- private final String text, mediaId, reelId;
-
- public StoryReplyBroadcastOptions(String text, String mediaId, String reelId) throws UnsupportedEncodingException {
- super(ItemType.REELSHARE);
- this.text = URLEncoder.encode(text, "UTF-8")
- .replaceAll("\\+", "%20").replaceAll("%21", "!").replaceAll("%27", "'")
- .replaceAll("%28", "(").replaceAll("%29", ")").replaceAll("%7E", "~").replaceAll("%0A", "\n");
- this.mediaId = mediaId;
- this.reelId = reelId; // or user id, usually same
- }
-
- @Override
- Map getFormMap() {
- final Map form = new HashMap<>();
- form.put("text", text);
- form.put("media_id", mediaId);
- form.put("reel_id", reelId);
- form.put("entry", "reel");
- return form;
- }
- }
-
- public static class ImageBroadcastOptions extends BroadcastOptions {
- final boolean allowFullAspectRatio;
- final String uploadId;
-
- public ImageBroadcastOptions(final boolean allowFullAspectRatio, final String uploadId) {
- super(ItemType.IMAGE);
- this.allowFullAspectRatio = allowFullAspectRatio;
- this.uploadId = uploadId;
- }
-
- @Override
- Map getFormMap() {
- final Map form = new HashMap<>();
- form.put("allow_full_aspect_ratio", String.valueOf(allowFullAspectRatio));
- form.put("upload_id", uploadId);
- return form;
- }
- }
-
- public static class DirectThreadBroadcastResponse {
- private int responseCode;
- private JSONObject response;
-
- public DirectThreadBroadcastResponse(int responseCode, JSONObject response) {
- this.responseCode = responseCode;
- this.response = response;
- }
-
- public int getResponseCode() {
- return responseCode;
- }
-
- public JSONObject getResponse() {
- return response;
- }
- }
-}
+// package awais.instagrabber.asyncs.direct_messages;
+//
+// import android.os.AsyncTask;
+// import android.util.Log;
+//
+// import org.json.JSONObject;
+//
+// import java.io.BufferedReader;
+// import java.io.DataOutputStream;
+// import java.io.IOException;
+// import java.io.InputStreamReader;
+// import java.net.HttpURLConnection;
+// import java.net.URL;
+// import java.util.HashMap;
+// import java.util.Map;
+// import java.util.UUID;
+//
+// import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions;
+// import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse;
+// import awais.instagrabber.utils.Constants;
+// import awais.instagrabber.utils.CookieUtils;
+// import awais.instagrabber.utils.Utils;
+//
+// import static awais.instagrabber.utils.Utils.settingsHelper;
+//
+// public class DirectThreadBroadcaster extends AsyncTask {
+// private static final String TAG = "DirectThreadBroadcaster";
+//
+// private final String threadId;
+//
+// private OnBroadcastCompleteListener listener;
+//
+// public DirectThreadBroadcaster(String threadId) {
+// this.threadId = threadId;
+// }
+//
+// @Override
+// protected DirectThreadBroadcastResponse doInBackground(final BroadcastOptions... broadcastOptionsArray) {
+// if (broadcastOptionsArray == null || broadcastOptionsArray.length == 0 || broadcastOptionsArray[0] == null) {
+// return null;
+// }
+// final BroadcastOptions broadcastOptions = broadcastOptionsArray[0];
+// final String cookie = settingsHelper.getString(Constants.COOKIE);
+// final String cc = UUID.randomUUID().toString();
+// final Map form = new HashMap<>();
+// form.put("_csrftoken", CookieUtils.getCsrfTokenFromCookie(cookie));
+// form.put("_uid", CookieUtils.getUserIdFromCookie(cookie));
+// form.put("__uuid", settingsHelper.getString(Constants.DEVICE_UUID));
+// form.put("client_context", cc);
+// form.put("mutation_token", cc);
+// form.putAll(broadcastOptions.getFormMap());
+// form.put("thread_id", threadId);
+// form.put("action", "send_item");
+// final String message = new JSONObject(form).toString();
+// final String content = Utils.sign(message);
+// final String url = "https://i.instagram.com/api/v1/direct_v2/threads/broadcast/" + broadcastOptions.getItemType().getValue() + "/";
+// HttpURLConnection connection = null;
+// DataOutputStream outputStream = null;
+// BufferedReader r = null;
+// try {
+// connection = (HttpURLConnection) new URL(url).openConnection();
+// connection.setRequestMethod("POST");
+// connection.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
+// connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+// if (content != null) {
+// connection.setRequestProperty("Content-Length", "" + content.getBytes().length);
+// }
+// connection.setUseCaches(false);
+// connection.setDoOutput(true);
+// outputStream = new DataOutputStream(connection.getOutputStream());
+// outputStream.writeBytes(content);
+// outputStream.flush();
+// final int responseCode = connection.getResponseCode();
+// if (responseCode != HttpURLConnection.HTTP_OK) {
+// Log.d(TAG, responseCode + ": " + content + ": " + cookie);
+// return new DirectThreadBroadcastResponse(responseCode, null);
+// }
+// r = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+// final StringBuilder builder = new StringBuilder();
+// for (String line = r.readLine(); line != null; line = r.readLine()) {
+// if (builder.length() != 0) {
+// builder.append("\n");
+// }
+// builder.append(line);
+// }
+// return new DirectThreadBroadcastResponse(responseCode, new JSONObject(builder.toString()));
+// } catch (Exception e) {
+// Log.e(TAG, "Error", e);
+// } finally {
+// if (r != null) {
+// try {
+// r.close();
+// } catch (IOException ignored) {
+// }
+// }
+// if (outputStream != null) {
+// try {
+// outputStream.close();
+// } catch (IOException ignored) {
+// }
+// }
+// if (connection != null) {
+// connection.disconnect();
+// }
+// }
+// return null;
+// }
+//
+// @Override
+// protected void onPostExecute(final DirectThreadBroadcastResponse result) {
+// if (listener != null) {
+// listener.onTaskComplete(result);
+// }
+// }
+//
+// public void setOnTaskCompleteListener(final OnBroadcastCompleteListener listener) {
+// if (listener != null) {
+// this.listener = listener;
+// }
+// }
+//
+// public interface OnBroadcastCompleteListener {
+// void onTaskComplete(DirectThreadBroadcastResponse response);
+// }
+// }
diff --git a/app/src/main/java/awais/instagrabber/asyncs/direct_messages/InboxFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/direct_messages/InboxFetcher.java
deleted file mode 100755
index 3f9e98a4..00000000
--- a/app/src/main/java/awais/instagrabber/asyncs/direct_messages/InboxFetcher.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package awais.instagrabber.asyncs.direct_messages;
-
-import android.os.AsyncTask;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-
-import java.io.BufferedReader;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.HttpURLConnection;
-import java.net.URL;
-
-import awais.instagrabber.BuildConfig;
-import awais.instagrabber.interfaces.FetchListener;
-import awais.instagrabber.models.direct_messages.InboxModel;
-import awais.instagrabber.models.direct_messages.InboxThreadModel;
-import awais.instagrabber.utils.Constants;
-import awais.instagrabber.utils.LocaleUtils;
-import awais.instagrabber.utils.NetworkUtils;
-import awais.instagrabber.utils.ResponseBodyUtils;
-import awais.instagrabber.utils.TextUtils;
-
-import static awais.instagrabber.utils.Utils.logCollector;
-import static awaisomereport.LogCollector.LogFile;
-
-public final class InboxFetcher extends AsyncTask {
- private static final String TAG = "InboxFetcher";
-
- private final String endCursor;
- private final FetchListener fetchListener;
-
- public InboxFetcher(final String endCursor, final FetchListener fetchListener) {
- this.endCursor = TextUtils.isEmpty(endCursor) ? "" : "?cursor=" + endCursor;
- this.fetchListener = fetchListener;
- }
-
- @Nullable
- @Override
- protected InboxModel doInBackground(final Void... voids) {
- InboxModel result = null;
-
- final String url = "https://i.instagram.com/api/v1/direct_v2/inbox/" + endCursor;
- try {
- final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
- conn.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
- conn.setRequestProperty("Accept-Language", LocaleUtils.getCurrentLocale().getLanguage() + ",en-US;q=0.8");
- conn.setUseCaches(false);
-
- if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
- final InputStream responseInputStream = conn.getErrorStream();
- final BufferedReader r = new BufferedReader(new InputStreamReader(responseInputStream));
- final StringBuilder builder = new StringBuilder();
- for (String line = r.readLine(); line != null; line = r.readLine()) {
- if (builder.length() != 0) {
- builder.append("\n");
- }
- builder.append(line);
- }
- Log.e(TAG, "Error response: " + conn.getResponseCode() + ", " + builder.toString());
- r.close();
- conn.disconnect();
- return null;
- }
- JSONObject data = new JSONObject(NetworkUtils.readFromConnection(conn));
-
- final long seqId = data.optLong("seq_id");
- final int pendingRequestsCount = data.optInt("pending_requests_total");
- final boolean hasPendingTopRequests = data.optBoolean("has_pending_top_requests");
-
- data = data.getJSONObject("inbox");
-
- final boolean blendedInboxEnabled = data.optBoolean("blended_inbox_enabled");
- final boolean hasOlder = data.optBoolean("has_older");
- final int unseenCount = data.optInt("unseen_count");
- final long unseenCountTimestamp = data.optLong("unseen_count_ts");
- final String oldestCursor = data.optString("oldest_cursor");
-
- InboxThreadModel[] inboxThreadModels = null;
-
- final JSONArray threadsArray = data.optJSONArray("threads");
- if (threadsArray != null) {
- final int threadsLen = threadsArray.length();
- inboxThreadModels = new InboxThreadModel[threadsLen];
-
- for (int i = 0; i < threadsLen; ++i)
- inboxThreadModels[i] = ResponseBodyUtils.createInboxThreadModel(threadsArray.getJSONObject(i), false);
- }
-
- result = new InboxModel(hasOlder, hasPendingTopRequests,
- blendedInboxEnabled, unseenCount, pendingRequestsCount,
- seqId, unseenCountTimestamp, oldestCursor, inboxThreadModels);
-
- conn.disconnect();
- } catch (final Exception e) {
- result = null;
- if (logCollector != null)
- logCollector.appendException(e, LogFile.ASYNC_DMS, "doInBackground");
- if (BuildConfig.DEBUG) Log.e(TAG, "", e);
- }
-
- return result;
- }
-
- @Override
- protected void onPreExecute() {
- if (fetchListener != null) fetchListener.doBefore();
- }
-
- @Override
- protected void onPostExecute(final InboxModel inboxModel) {
- if (fetchListener != null) fetchListener.onResult(inboxModel);
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/broadcasts/DMRefreshBroadcastReceiver.java b/app/src/main/java/awais/instagrabber/broadcasts/DMRefreshBroadcastReceiver.java
new file mode 100644
index 00000000..3d2151a6
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/broadcasts/DMRefreshBroadcastReceiver.java
@@ -0,0 +1,27 @@
+package awais.instagrabber.broadcasts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class DMRefreshBroadcastReceiver extends BroadcastReceiver {
+ public static final String ACTION_REFRESH_DM = "action_refresh_dm";
+ private final OnDMRefreshCallback callback;
+
+ public DMRefreshBroadcastReceiver(final OnDMRefreshCallback callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (callback == null) return;
+ final String action = intent.getAction();
+ if (action == null) return;
+ if (!action.equals(ACTION_REFRESH_DM)) return;
+ callback.onReceive();
+ }
+
+ public interface OnDMRefreshCallback {
+ void onReceive();
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/ChatMessageLayout.java b/app/src/main/java/awais/instagrabber/customviews/ChatMessageLayout.java
new file mode 100644
index 00000000..6e92be08
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/ChatMessageLayout.java
@@ -0,0 +1,155 @@
+package awais.instagrabber.customviews;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import awais.instagrabber.R;
+
+public class ChatMessageLayout extends FrameLayout {
+
+ private FrameLayout viewPartMain;
+ private View viewPartInfo;
+ private TypedArray a;
+
+ private int viewPartInfoWidth;
+ private int viewPartInfoHeight;
+
+ // private boolean withGroupHeader = false;
+
+ public ChatMessageLayout(@NonNull final Context context) {
+ super(context);
+ }
+
+ public ChatMessageLayout(@NonNull final Context context, @Nullable final AttributeSet attrs) {
+ super(context, attrs);
+ a = context.obtainStyledAttributes(attrs, R.styleable.ChatMessageLayout, 0, 0);
+ }
+
+ public ChatMessageLayout(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ a = context.obtainStyledAttributes(attrs, R.styleable.ChatMessageLayout, defStyleAttr, 0);
+ }
+
+ public ChatMessageLayout(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ a = context.obtainStyledAttributes(attrs, R.styleable.ChatMessageLayout, defStyleAttr, defStyleRes);
+ }
+
+ // public void setWithGroupHeader(boolean withGroupHeader) {
+ // this.withGroupHeader = withGroupHeader;
+ // }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ try {
+ viewPartMain = findViewById(a.getResourceId(R.styleable.ChatMessageLayout_viewPartMain, -1));
+ viewPartInfo = findViewById(a.getResourceId(R.styleable.ChatMessageLayout_viewPartInfo, -1));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSize;
+ // heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (viewPartMain == null || viewPartInfo == null || widthSize <= 0) {
+ return;
+ }
+
+ final View firstChild = viewPartMain.getChildAt(0);
+ if (firstChild == null) return;
+
+ final int firstChildId = firstChild.getId();
+ if (firstChildId == R.id.reel_share_container) return;
+
+ int availableWidth = widthSize - getPaddingLeft() - getPaddingRight();
+ // int availableHeight = heightSize - getPaddingTop() - getPaddingBottom();
+
+ final LayoutParams viewPartMainLayoutParams = (LayoutParams) viewPartMain.getLayoutParams();
+ final int viewPartMainWidth = viewPartMain.getMeasuredWidth() + viewPartMainLayoutParams.leftMargin + viewPartMainLayoutParams.rightMargin;
+ final int viewPartMainHeight = viewPartMain.getMeasuredHeight() + viewPartMainLayoutParams.topMargin + viewPartMainLayoutParams.bottomMargin;
+
+ final LayoutParams viewPartInfoLayoutParams = (LayoutParams) viewPartInfo.getLayoutParams();
+ viewPartInfoWidth = viewPartInfo.getMeasuredWidth() + viewPartInfoLayoutParams.leftMargin + viewPartInfoLayoutParams.rightMargin;
+ viewPartInfoHeight = viewPartInfo.getMeasuredHeight() + viewPartInfoLayoutParams.topMargin + viewPartInfoLayoutParams.bottomMargin;
+
+ widthSize = getPaddingLeft() + getPaddingRight();
+ heightSize = getPaddingTop() + getPaddingBottom();
+ if (firstChildId == R.id.media_container) {
+ widthSize += viewPartMainWidth;
+ heightSize += viewPartMainHeight;
+ } else if (firstChildId == R.id.raven_media_container || firstChildId == R.id.profile_container || firstChildId == R.id.voice_media) {
+ widthSize += viewPartMainWidth;
+ heightSize += viewPartMainHeight + viewPartInfoHeight;
+ } else {
+ int viewPartMainLineCount = 1;
+ float viewPartMainLastLineWidth = 0;
+ if (firstChild instanceof TextView) {
+ viewPartMainLineCount = ((TextView) firstChild).getLineCount();
+ viewPartMainLastLineWidth = viewPartMainLineCount > 0
+ ? ((TextView) firstChild).getLayout().getLineWidth(viewPartMainLineCount - 1)
+ : 0;
+ }
+
+ if (viewPartMainLineCount > 1 && !(viewPartMainLastLineWidth + viewPartInfoWidth > viewPartMain.getMeasuredWidth())) {
+ widthSize += viewPartMainWidth;
+ heightSize += viewPartMainHeight;
+ } else if (viewPartMainLineCount > 1 && (viewPartMainLastLineWidth + viewPartInfoWidth > availableWidth)) {
+ widthSize += viewPartMainWidth;
+ heightSize += viewPartMainHeight + viewPartInfoHeight;
+ } else if (viewPartMainLineCount == 1 && (viewPartMainWidth + viewPartInfoWidth > availableWidth)) {
+ widthSize += viewPartMain.getMeasuredWidth();
+ heightSize += viewPartMainHeight + viewPartInfoHeight;
+ } else {
+ heightSize += viewPartMainHeight;
+ widthSize += viewPartMainWidth + viewPartInfoWidth;
+ }
+ }
+ setMeasuredDimension(widthSize, heightSize);
+ super.onMeasure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
+
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (viewPartMain == null || viewPartInfo == null) {
+ return;
+ }
+ // if (withGroupHeader) {
+ // viewPartMain.layout(
+ // getPaddingLeft(),
+ // getPaddingTop() - Utils.convertDpToPx(4),
+ // viewPartMain.getWidth() + getPaddingLeft(),
+ // viewPartMain.getHeight() + getPaddingTop());
+ //
+ // } else {
+ viewPartMain.layout(
+ getPaddingLeft(),
+ getPaddingTop(),
+ viewPartMain.getWidth() + getPaddingLeft(),
+ viewPartMain.getHeight() + getPaddingTop());
+
+ // }
+ viewPartInfo.layout(
+ right - left - viewPartInfoWidth - getPaddingRight(),
+ bottom - top - getPaddingBottom() - viewPartInfoHeight,
+ right - left - getPaddingRight(),
+ bottom - top - getPaddingBottom());
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/KeyNotifyingEmojiEditText.java b/app/src/main/java/awais/instagrabber/customviews/KeyNotifyingEmojiEditText.java
new file mode 100644
index 00000000..e64a4d3f
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/KeyNotifyingEmojiEditText.java
@@ -0,0 +1,44 @@
+package awais.instagrabber.customviews;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+
+import androidx.emoji.widget.EmojiEditText;
+
+public class KeyNotifyingEmojiEditText extends EmojiEditText {
+ private OnKeyEventListener onKeyEventListener;
+
+ public KeyNotifyingEmojiEditText(final Context context) {
+ super(context);
+ }
+
+ public KeyNotifyingEmojiEditText(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public KeyNotifyingEmojiEditText(final Context context, final AttributeSet attrs, final int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public KeyNotifyingEmojiEditText(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public boolean onKeyPreIme(final int keyCode, final KeyEvent event) {
+ if (onKeyEventListener != null) {
+ final boolean listenerResult = onKeyEventListener.onKeyPreIme(keyCode, event);
+ if (listenerResult) return true;
+ }
+ return super.onKeyPreIme(keyCode, event);
+ }
+
+ public void setOnKeyEventListener(final OnKeyEventListener onKeyEventListener) {
+ this.onKeyEventListener = onKeyEventListener;
+ }
+
+ public interface OnKeyEventListener {
+ boolean onKeyPreIme(int keyCode, KeyEvent keyEvent);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/PopupDialog.java b/app/src/main/java/awais/instagrabber/customviews/PopupDialog.java
new file mode 100644
index 00000000..6e328dc2
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/PopupDialog.java
@@ -0,0 +1,55 @@
+package awais.instagrabber.customviews;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.IBinder;
+import android.view.Gravity;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+
+import awais.instagrabber.utils.Utils;
+
+/**
+ * https://stackoverflow.com/a/15766097/1436766
+ */
+public class PopupDialog extends Dialog {
+ private final Context context;
+
+ public PopupDialog(Context context) {
+ super(context);
+ this.context = context;
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ }
+
+ public void showAtLocation(final IBinder token, final int gravity, int x, int y) {
+ final Window window = getWindow();
+ if (window == null) return;
+ WindowManager.LayoutParams layoutParams = window.getAttributes();
+ layoutParams.gravity = gravity;
+ layoutParams.x = x;
+ layoutParams.y = y;
+ // layoutParams.token = token;
+ show();
+ }
+
+ public void showAsDropDown(View view) {
+ float density = Utils.displayMetrics.density;
+ final Window window = getWindow();
+ if (window == null) return;
+ WindowManager.LayoutParams layoutParams = window.getAttributes();
+ int[] location = new int[2];
+ view.getLocationInWindow(location);
+ layoutParams.gravity = Gravity.TOP | Gravity.START;
+ layoutParams.x = location[0] + (int) (view.getWidth() / density);
+ layoutParams.y = location[1] + (int) (view.getHeight() / density);
+ show();
+ }
+
+ public void setBackgroundDrawable(final Drawable drawable) {
+ final Window window = getWindow();
+ if (window == null) return;
+ window.setBackgroundDrawable(drawable);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java b/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java
index e5771537..53f222e0 100644
--- a/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java
+++ b/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java
@@ -28,7 +28,7 @@ import java.util.List;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.customviews.helpers.PostFetcher;
-import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtBottom;
+import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
@@ -51,7 +51,7 @@ public class PostsRecyclerView extends RecyclerView {
private FeedViewModel feedViewModel;
private boolean initCalled = false;
private GridSpacingItemDecoration gridSpacingItemDecoration;
- private RecyclerLazyLoaderAtBottom lazyLoader;
+ private RecyclerLazyLoaderAtEdge lazyLoader;
private FeedAdapterV2.FeedItemCallback feedItemCallback;
private boolean shouldScrollToTop;
private FeedAdapterV2.SelectionModeCallback selectionModeCallback;
@@ -194,7 +194,7 @@ public class PostsRecyclerView extends RecyclerView {
}
setHasFixedSize(true);
setNestedScrollingEnabled(true);
- lazyLoader = new RecyclerLazyLoaderAtBottom(layoutManager, (page) -> {
+ lazyLoader = new RecyclerLazyLoaderAtEdge(layoutManager, (page) -> {
if (postFetcher.hasMore()) {
postFetcher.fetch();
dispatchFetchStatus();
diff --git a/app/src/main/java/awais/instagrabber/customviews/ProfilePicView.java b/app/src/main/java/awais/instagrabber/customviews/ProfilePicView.java
index cd156d59..2bc480a9 100644
--- a/app/src/main/java/awais/instagrabber/customviews/ProfilePicView.java
+++ b/app/src/main/java/awais/instagrabber/customviews/ProfilePicView.java
@@ -47,6 +47,7 @@ public final class ProfilePicView extends CircularImageView {
final int defStyleAttr) {
super(context, attrs, defStyleAttr);
parseAttrs(context, attrs);
+ updateLayout();
}
private void parseAttrs(final Context context, final AttributeSet attrs) {
@@ -92,6 +93,11 @@ public final class ProfilePicView extends CircularImageView {
// requestLayout();
}
+ public void setSize(final Size size) {
+ this.size = size;
+ updateLayout();
+ }
+
public void setStoriesBorder() {
// private final int borderSize = 8;
final int color = Color.GREEN;
diff --git a/app/src/main/java/awais/instagrabber/customviews/RecordButton.java b/app/src/main/java/awais/instagrabber/customviews/RecordButton.java
new file mode 100644
index 00000000..91877f59
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/RecordButton.java
@@ -0,0 +1,116 @@
+package awais.instagrabber.customviews;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.google.android.material.button.MaterialButton;
+
+import awais.instagrabber.animations.ScaleAnimation;
+
+/**
+ * Created by Devlomi on 13/12/2017.
+ */
+
+public class RecordButton extends MaterialButton implements View.OnTouchListener, View.OnClickListener, View.OnLongClickListener {
+
+ private ScaleAnimation scaleAnimation;
+ private RecordView recordView;
+ private boolean listenForRecord = true;
+ private OnRecordClickListener onRecordClickListener;
+ private OnRecordLongClickListener onRecordLongClickListener;
+
+ public RecordButton(Context context) {
+ super(context);
+ init(context, null);
+ }
+
+ public RecordButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs);
+ }
+
+ public RecordButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context, attrs);
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private void init(Context context, AttributeSet attrs) {
+ scaleAnimation = new ScaleAnimation(this);
+ this.setOnTouchListener(this);
+ this.setOnClickListener(this);
+ this.setOnLongClickListener(this);
+ }
+
+ public void setRecordView(RecordView recordView) {
+ this.recordView = recordView;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (isListenForRecord()) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ recordView.onActionDown((RecordButton) v, event);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ recordView.onActionMove((RecordButton) v, event, false);
+ break;
+ case MotionEvent.ACTION_UP:
+ recordView.onActionUp((RecordButton) v);
+ break;
+ }
+ }
+ return isListenForRecord();
+ }
+
+ protected void startScale() {
+ scaleAnimation.start();
+ }
+
+ public void stopScale() {
+ scaleAnimation.stop();
+ }
+
+ public void setListenForRecord(boolean listenForRecord) {
+ this.listenForRecord = listenForRecord;
+ }
+
+ public boolean isListenForRecord() {
+ return listenForRecord;
+ }
+
+ public void setOnRecordClickListener(OnRecordClickListener onRecordClickListener) {
+ this.onRecordClickListener = onRecordClickListener;
+ }
+
+ public void setOnRecordLongClickListener(OnRecordLongClickListener onRecordLongClickListener) {
+ this.onRecordLongClickListener = onRecordLongClickListener;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (onRecordClickListener != null) {
+ onRecordClickListener.onClick(v);
+ }
+ }
+
+ @Override
+ public boolean onLongClick(final View v) {
+ if (onRecordLongClickListener != null) {
+ return onRecordLongClickListener.onLongClick(v);
+ }
+ return false;
+ }
+
+ public interface OnRecordClickListener {
+ void onClick(View v);
+ }
+
+ public interface OnRecordLongClickListener {
+ boolean onLongClick(View v);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/RecordView.java b/app/src/main/java/awais/instagrabber/customviews/RecordView.java
new file mode 100644
index 00000000..f04cd643
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/RecordView.java
@@ -0,0 +1,352 @@
+package awais.instagrabber.customviews;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.media.MediaPlayer;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.widget.RelativeLayout;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Nullable;
+import androidx.appcompat.content.res.AppCompatResources;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.core.graphics.drawable.DrawableCompat;
+
+import java.io.IOException;
+
+import awais.instagrabber.R;
+import awais.instagrabber.customviews.helpers.RecordViewAnimationHelper;
+import awais.instagrabber.databinding.RecordViewLayoutBinding;
+import awais.instagrabber.utils.Utils;
+
+/**
+ * Created by Devlomi on 24/08/2017.
+ */
+
+public class RecordView extends RelativeLayout {
+ private static final String TAG = RecordView.class.getSimpleName();
+
+ public static final int DEFAULT_CANCEL_BOUNDS = 8; //8dp
+ // private ImageView smallBlinkingMic;
+ // private ImageView basketImg;
+ // private Chronometer counterTime;
+ // private TextView slideToCancel;
+ // private LinearLayout slideToCancelLayout;
+ private float initialX;
+ private float basketInitialY;
+ private float difX = 0;
+ private float cancelBounds = DEFAULT_CANCEL_BOUNDS;
+ private long startTime;
+ private final Context context;
+ private OnRecordListener onRecordListener;
+ private boolean isSwiped;
+ private boolean isLessThanMinAllowed = false;
+ private boolean isSoundEnabled = true;
+ private int RECORD_START = R.raw.record_start;
+ private int RECORD_FINISHED = R.raw.record_finished;
+ private int RECORD_ERROR = R.raw.record_error;
+ private RecordViewAnimationHelper recordViewAnimationHelper;
+ private RecordViewLayoutBinding binding;
+ private int minMillis = 1000;
+
+
+ public RecordView(Context context) {
+ super(context);
+ this.context = context;
+ init(context, null, -1, -1);
+ }
+
+
+ public RecordView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ this.context = context;
+ init(context, attrs, -1, -1);
+ }
+
+ public RecordView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ this.context = context;
+ init(context, attrs, defStyleAttr, -1);
+ }
+
+ private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ binding = RecordViewLayoutBinding.inflate(LayoutInflater.from(context), this, false);
+ addView(binding.getRoot());
+ hideViews(true);
+ if (attrs != null && defStyleAttr == -1 && defStyleRes == -1) {
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RecordView, defStyleAttr, defStyleRes);
+ int slideArrowResource = typedArray.getResourceId(R.styleable.RecordView_slide_to_cancel_arrow, -1);
+ String slideToCancelText = typedArray.getString(R.styleable.RecordView_slide_to_cancel_text);
+ int slideToCancelTextColor = typedArray.getResourceId(R.styleable.RecordView_slide_to_cancel_text_color, -1);
+ int slideMarginRight = (int) typedArray.getDimension(R.styleable.RecordView_slide_to_cancel_margin_right, 30);
+ int counterTimeColor = typedArray.getResourceId(R.styleable.RecordView_counter_time_color, -1);
+ int arrowColor = typedArray.getResourceId(R.styleable.RecordView_slide_to_cancel_arrow_color, -1);
+ int cancelBounds = typedArray.getDimensionPixelSize(R.styleable.RecordView_slide_to_cancel_bounds, -1);
+ if (cancelBounds != -1) {
+ setCancelBounds(cancelBounds, false);//don't convert it to pixels since it's already in pixels
+ }
+ if (slideToCancelText != null) {
+ setSlideToCancelText(slideToCancelText);
+ }
+ if (slideToCancelTextColor != -1) {
+ setSlideToCancelTextColor(getResources().getColor(slideToCancelTextColor));
+ }
+ if (slideArrowResource != -1) {
+ setSlideArrowDrawable(slideArrowResource);
+ }
+ if (arrowColor != -1) {
+ setSlideToCancelArrowColor(getResources().getColor(arrowColor));
+ }
+ if (counterTimeColor != -1) {
+ setCounterTimeColor(getResources().getColor(counterTimeColor));
+ }
+ setMarginRight(slideMarginRight, true);
+ typedArray.recycle();
+ }
+ recordViewAnimationHelper = new RecordViewAnimationHelper(context, binding.basketImg, binding.glowingMic);
+ }
+
+ private void hideViews(boolean hideSmallMic) {
+ binding.slideToCancel.setVisibility(GONE);
+ binding.basketImg.setVisibility(GONE);
+ binding.counterTv.setVisibility(GONE);
+ if (hideSmallMic) {
+ binding.glowingMic.setVisibility(GONE);
+ }
+ }
+
+ private void showViews() {
+ binding.slideToCancel.setVisibility(VISIBLE);
+ binding.glowingMic.setVisibility(VISIBLE);
+ binding.counterTv.setVisibility(VISIBLE);
+ }
+
+ private boolean isLessThanMin(long time) {
+ return time <= minMillis;
+ }
+
+ private void playSound(int soundRes) {
+ if (!isSoundEnabled) return;
+ if (soundRes == 0) return;
+ try {
+ final MediaPlayer player = new MediaPlayer();
+ AssetFileDescriptor afd = context.getResources().openRawResourceFd(soundRes);
+ if (afd == null) return;
+ player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+ afd.close();
+ player.prepare();
+ player.start();
+ player.setOnCompletionListener(MediaPlayer::release);
+ player.setLooping(false);
+ } catch (IOException e) {
+ Log.e(TAG, "playSound", e);
+ }
+ }
+
+ protected void onActionDown(RecordButton recordBtn, MotionEvent motionEvent) {
+ if (onRecordListener != null) {
+ onRecordListener.onStart();
+ }
+ recordViewAnimationHelper.setStartRecorded(true);
+ recordViewAnimationHelper.resetBasketAnimation();
+ recordViewAnimationHelper.resetSmallMic();
+ recordBtn.startScale();
+ // slideToCancelLayout.startShimmerAnimation();
+
+ initialX = recordBtn.getX();
+ basketInitialY = binding.basketImg.getY() + 90;
+ // playSound(RECORD_START);
+ showViews();
+
+ recordViewAnimationHelper.animateSmallMicAlpha();
+ binding.counterTv.setBase(SystemClock.elapsedRealtime());
+ startTime = System.currentTimeMillis();
+ binding.counterTv.start();
+ isSwiped = false;
+ }
+
+ protected void onActionMove(RecordButton recordBtn, MotionEvent motionEvent, final boolean forceCancel) {
+ long time = System.currentTimeMillis() - startTime;
+ if (isSwiped) return;
+ //Swipe To Cancel
+ if (forceCancel || (binding.slideToCancel.getX() != 0 && binding.slideToCancel.getX() <= binding.counterTv.getRight() + cancelBounds)) {
+ //if the time was less than one second then do not start basket animation
+ if (isLessThanMin(time)) {
+ hideViews(true);
+ recordViewAnimationHelper.clearAlphaAnimation(false);
+ if (onRecordListener != null) {
+ onRecordListener.onLessThanMin();
+ }
+ recordViewAnimationHelper.onAnimationEnd();
+ } else {
+ hideViews(false);
+ recordViewAnimationHelper.animateBasket(basketInitialY);
+ }
+ recordViewAnimationHelper.moveRecordButtonAndSlideToCancelBack(recordBtn, binding.slideToCancel, initialX, difX);
+ binding.counterTv.stop();
+ // slideToCancelLayout.stopShimmerAnimation();
+ isSwiped = true;
+ recordViewAnimationHelper.setStartRecorded(false);
+ if (onRecordListener != null) {
+ onRecordListener.onCancel();
+ }
+ return;
+ }
+ //if statement is to Prevent Swiping out of bounds
+ if (!(motionEvent.getRawX() < initialX)) return;
+ recordBtn.animate()
+ .x(motionEvent.getRawX())
+ .setDuration(0)
+ .start();
+ if (difX == 0) {
+ difX = (initialX - binding.slideToCancel.getX());
+ }
+ binding.slideToCancel.animate()
+ .x(motionEvent.getRawX() - difX)
+ .setDuration(0)
+ .start();
+ }
+
+ protected void onActionUp(RecordButton recordBtn) {
+ final long elapsedTime = System.currentTimeMillis() - startTime;
+ if (!isLessThanMinAllowed && isLessThanMin(elapsedTime) && !isSwiped) {
+ if (onRecordListener != null) {
+ onRecordListener.onLessThanMin();
+ }
+ recordViewAnimationHelper.setStartRecorded(false);
+ // playSound(RECORD_ERROR);
+ } else {
+ if (onRecordListener != null && !isSwiped) {
+ onRecordListener.onFinish(elapsedTime);
+ }
+ recordViewAnimationHelper.setStartRecorded(false);
+ if (!isSwiped) {
+ // playSound(RECORD_FINISHED);
+ }
+ }
+ //if user has swiped then do not hide SmallMic since it will be hidden after swipe Animation
+ hideViews(!isSwiped);
+ if (!isSwiped) {
+ recordViewAnimationHelper.clearAlphaAnimation(true);
+ }
+ recordViewAnimationHelper.moveRecordButtonAndSlideToCancelBack(recordBtn, binding.slideToCancel, initialX, difX);
+ binding.counterTv.stop();
+ // slideToCancelLayout.stopShimmerAnimation();
+ }
+
+ private void setMarginRight(int marginRight, boolean convertToDp) {
+ ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) binding.slideToCancel.getLayoutParams();
+ if (convertToDp) {
+ layoutParams.rightMargin = Utils.convertDpToPx(marginRight);
+ } else {
+ layoutParams.rightMargin = marginRight;
+ }
+ binding.slideToCancel.setLayoutParams(layoutParams);
+ }
+
+ private void setSlideArrowDrawable(@DrawableRes final int slideArrowResource) {
+ Drawable slideArrow = AppCompatResources.getDrawable(getContext(), slideArrowResource);
+ // Log.d(TAG, "setSlideArrowDrawable: slideArrow: " + slideArrow);
+ if (slideArrow == null) return;
+ slideArrow.setBounds(0, 0, slideArrow.getIntrinsicWidth(), slideArrow.getIntrinsicHeight());
+ binding.slideToCancel.setCompoundDrawablesRelative(slideArrow, null, null, null);
+ }
+
+ public void setOnRecordListener(OnRecordListener onRecordListener) {
+ this.onRecordListener = onRecordListener;
+ }
+
+ public void setOnBasketAnimationEndListener(OnBasketAnimationEnd onBasketAnimationEndListener) {
+ recordViewAnimationHelper.setOnBasketAnimationEndListener(onBasketAnimationEndListener);
+ }
+
+ public void setSoundEnabled(boolean isEnabled) {
+ isSoundEnabled = isEnabled;
+ }
+
+ public void setLessThanMinAllowed(boolean isAllowed) {
+ isLessThanMinAllowed = isAllowed;
+ }
+
+ public void setSlideToCancelText(String text) {
+ binding.slideToCancel.setText(text);
+ }
+
+ public void setSlideToCancelTextColor(int color) {
+ binding.slideToCancel.setTextColor(color);
+ }
+
+ public void setSmallMicColor(int color) {
+ binding.glowingMic.setColorFilter(color);
+ }
+
+ public void setSmallMicIcon(int icon) {
+ binding.glowingMic.setImageResource(icon);
+ }
+
+ public void setSlideMarginRight(int marginRight) {
+ setMarginRight(marginRight, true);
+ }
+
+ public void setCustomSounds(int startSound, int finishedSound, int errorSound) {
+ //0 means do not play sound
+ RECORD_START = startSound;
+ RECORD_FINISHED = finishedSound;
+ RECORD_ERROR = errorSound;
+ }
+
+ public float getCancelBounds() {
+ return cancelBounds;
+ }
+
+ public void setCancelBounds(float cancelBounds) {
+ setCancelBounds(cancelBounds, true);
+ }
+
+ //set Chronometer color
+ public void setCounterTimeColor(@ColorInt int color) {
+ binding.counterTv.setTextColor(color);
+ }
+
+ public void setSlideToCancelArrowColor(@ColorInt int color) {
+ Drawable drawable = binding.slideToCancel.getCompoundDrawablesRelative()[0];
+ drawable = DrawableCompat.wrap(drawable);
+ DrawableCompat.setTint(drawable.mutate(), color);
+ binding.slideToCancel.setCompoundDrawablesRelative(drawable, null, null, null);
+ }
+
+ private void setCancelBounds(float cancelBounds, boolean convertDpToPixel) {
+ this.cancelBounds = convertDpToPixel ? Utils.convertDpToPx(cancelBounds) : cancelBounds;
+ }
+
+ public void setMinMillis(final int minMillis) {
+ this.minMillis = minMillis;
+ }
+
+ public void cancelRecording(final RecordButton recordBtn) {
+ onActionMove(recordBtn, null, true);
+ }
+
+ public interface OnBasketAnimationEnd {
+ void onAnimationEnd();
+ }
+
+ public interface OnRecordListener {
+ void onStart();
+
+ void onCancel();
+
+ void onFinish(long recordTime);
+
+ void onLessThanMin();
+ }
+}
+
+
diff --git a/app/src/main/java/awais/instagrabber/customviews/SquareImageView.java b/app/src/main/java/awais/instagrabber/customviews/SquareImageView.java
new file mode 100644
index 00000000..c46fd817
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/SquareImageView.java
@@ -0,0 +1,26 @@
+package awais.instagrabber.customviews;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.appcompat.widget.AppCompatImageView;
+
+public class SquareImageView extends AppCompatImageView {
+ public SquareImageView(final Context context) {
+ super(context);
+ }
+
+ public SquareImageView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public SquareImageView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ //noinspection SuspiciousNameCombination
+ super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/Tooltip.java b/app/src/main/java/awais/instagrabber/customviews/Tooltip.java
new file mode 100644
index 00000000..91a07e42
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/Tooltip.java
@@ -0,0 +1,118 @@
+package awais.instagrabber.customviews;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
+
+import androidx.appcompat.widget.AppCompatTextView;
+
+import awais.instagrabber.utils.AppExecutors;
+import awais.instagrabber.utils.Utils;
+import awais.instagrabber.utils.ViewUtils;
+
+
+public class Tooltip extends AppCompatTextView {
+
+ private View anchor;
+ private ViewPropertyAnimator animator;
+ private boolean showing;
+
+ private final AppExecutors appExecutors;
+ private final Runnable dismissRunnable = () -> {
+ animator = animate().alpha(0).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setVisibility(View.GONE);
+ }
+ }).setDuration(300);
+ animator.start();
+ };
+
+ public Tooltip(Context context, ViewGroup parentView, int backgroundColor, int textColor) {
+ super(context);
+ setBackgroundDrawable(ViewUtils.createRoundRectDrawable(Utils.convertDpToPx(3), backgroundColor));
+ setTextColor(textColor);
+ setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
+ setPadding(Utils.convertDpToPx(8), Utils.convertDpToPx(7), Utils.convertDpToPx(8), Utils.convertDpToPx(7));
+ setGravity(Gravity.CENTER_VERTICAL);
+ parentView.addView(this, ViewUtils.createFrame(
+ ViewUtils.WRAP_CONTENT, ViewUtils.WRAP_CONTENT, Gravity.START | Gravity.TOP, 5, 0, 5, 3));
+ setVisibility(GONE);
+ appExecutors = AppExecutors.getInstance();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ updateTooltipPosition();
+ }
+
+ private void updateTooltipPosition() {
+ if (anchor == null) {
+ return;
+ }
+ int top = 0;
+ int left = 0;
+
+ View containerView = (View) getParent();
+ View view = anchor;
+
+ while (view != containerView) {
+ top += view.getTop();
+ left += view.getLeft();
+ view = (View) view.getParent();
+ }
+ int x = left + anchor.getWidth() / 2 - getMeasuredWidth() / 2;
+ if (x < 0) {
+ x = 0;
+ } else if (x + getMeasuredWidth() > containerView.getMeasuredWidth()) {
+ x = containerView.getMeasuredWidth() - getMeasuredWidth() - Utils.convertDpToPx(16);
+ }
+ setTranslationX(x);
+
+ int y = top - getMeasuredHeight();
+ setTranslationY(y);
+ }
+
+ public void show(View anchor) {
+ if (anchor == null) {
+ return;
+ }
+ this.anchor = anchor;
+ updateTooltipPosition();
+ showing = true;
+
+ appExecutors.mainThread().cancel(dismissRunnable);
+ appExecutors.mainThread().execute(dismissRunnable, 2000);
+ if (animator != null) {
+ animator.setListener(null);
+ animator.cancel();
+ animator = null;
+ }
+ if (getVisibility() != VISIBLE) {
+ setAlpha(0f);
+ setVisibility(VISIBLE);
+ animator = animate().setDuration(300).alpha(1f).setListener(null);
+ animator.start();
+ }
+ }
+
+ public void hide() {
+ if (showing) {
+ if (animator != null) {
+ animator.setListener(null);
+ animator.cancel();
+ animator = null;
+ }
+
+ appExecutors.mainThread().cancel(dismissRunnable);
+ dismissRunnable.run();
+ }
+ showing = false;
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/emoji/Emoji.java b/app/src/main/java/awais/instagrabber/customviews/emoji/Emoji.java
new file mode 100644
index 00000000..d90c4d8e
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/emoji/Emoji.java
@@ -0,0 +1,65 @@
+package awais.instagrabber.customviews.emoji;
+
+import androidx.annotation.NonNull;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+public class Emoji {
+ private final String unicode;
+ private final String name;
+ private final List variants;
+ private GoogleCompatEmojiDrawable drawable;
+
+ public Emoji(final String unicode, final String name) {
+ this.unicode = unicode;
+ this.name = name;
+ this.variants = new LinkedList<>();
+ }
+
+ public String getUnicode() {
+ return unicode;
+ }
+
+ public void addVariant(final Emoji emoji) {
+ variants.add(emoji);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List getVariants() {
+ return variants;
+ }
+
+ public GoogleCompatEmojiDrawable getDrawable() {
+ if (drawable == null) {
+ drawable = new GoogleCompatEmojiDrawable(unicode);
+ }
+ return drawable;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ final Emoji emoji = (Emoji) o;
+ return Objects.equals(unicode, emoji.unicode);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(unicode);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "Emoji{" +
+ "unicode='" + unicode + '\'' +
+ ", name='" + name + '\'' +
+ '}';
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiCategory.java b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiCategory.java
new file mode 100644
index 00000000..a1ec71a4
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiCategory.java
@@ -0,0 +1,74 @@
+package awais.instagrabber.customviews.emoji;
+
+import androidx.annotation.DrawableRes;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import awais.instagrabber.R;
+
+public class EmojiCategory {
+ private final EmojiCategoryType type;
+ private final Map emojis = new LinkedHashMap<>();
+ @DrawableRes
+ private int drawableRes;
+
+ public EmojiCategory(final EmojiCategoryType type) {
+ this.type = type;
+ switch (type) {
+ case SMILEYS_AND_EMOTION:
+ drawableRes = R.drawable.ic_round_emoji_emotions_24;
+ break;
+ case ANIMALS_AND_NATURE:
+ drawableRes = R.drawable.ic_round_emoji_nature_24;
+ break;
+ case FOOD_AND_DRINK:
+ drawableRes = R.drawable.ic_round_emoji_food_beverage_24;
+ break;
+ case TRAVEL_AND_PLACES:
+ drawableRes = R.drawable.ic_round_emoji_transportation_24;
+ break;
+ case ACTIVITIES:
+ drawableRes = R.drawable.ic_round_emoji_events_24;
+ break;
+ case OBJECTS:
+ drawableRes = R.drawable.ic_round_emoji_objects_24;
+ break;
+ case SYMBOLS:
+ drawableRes = R.drawable.ic_round_emoji_symbols_24;
+ break;
+ case FLAGS:
+ drawableRes = R.drawable.ic_round_emoji_flags_24;
+ break;
+ case OTHERS:
+ drawableRes = R.drawable.ic_round_unknown_24;
+ break;
+ }
+ }
+
+ public EmojiCategoryType getType() {
+ return type;
+ }
+
+ public Map getEmojis() {
+ return emojis;
+ }
+
+ public int getDrawableRes() {
+ return drawableRes;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ final EmojiCategory that = (EmojiCategory) o;
+ return type == that.type;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiCategoryPageViewHolder.java b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiCategoryPageViewHolder.java
new file mode 100644
index 00000000..b875c5a3
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiCategoryPageViewHolder.java
@@ -0,0 +1,44 @@
+package awais.instagrabber.customviews.emoji;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import awais.instagrabber.customviews.emoji.EmojiPicker.OnEmojiClickListener;
+
+public class EmojiCategoryPageViewHolder extends RecyclerView.ViewHolder {
+ // private static final String TAG = EmojiCategoryPageViewHolder.class.getSimpleName();
+
+ private final View rootView;
+ private final OnEmojiClickListener onEmojiClickListener;
+
+ public EmojiCategoryPageViewHolder(@NonNull final View rootView,
+ @NonNull final RecyclerView itemView,
+ final OnEmojiClickListener onEmojiClickListener) {
+ super(itemView);
+ this.rootView = rootView;
+ this.onEmojiClickListener = onEmojiClickListener;
+ }
+
+ public void bind(final EmojiCategory emojiCategory) {
+ final RecyclerView emojiGrid = (RecyclerView) itemView;
+ final EmojiGridAdapter adapter = new EmojiGridAdapter(
+ emojiCategory.getType(),
+ onEmojiClickListener,
+ (position, view, parent) -> {
+ final EmojiVariantPopup emojiVariantPopup = new EmojiVariantPopup(rootView, ((view1, emoji) -> {
+ if (onEmojiClickListener != null) {
+ onEmojiClickListener.onClick(view1, emoji);
+ }
+ final EmojiGridAdapter emojiGridAdapter = (EmojiGridAdapter) emojiGrid.getAdapter();
+ if (emojiGridAdapter == null) return;
+ emojiGridAdapter.notifyItemChanged(position);
+ }));
+ emojiVariantPopup.show(view, parent);
+ return true;
+ }
+ );
+ emojiGrid.setAdapter(adapter);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiCategoryType.java b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiCategoryType.java
new file mode 100644
index 00000000..2bb4536d
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiCategoryType.java
@@ -0,0 +1,43 @@
+package awais.instagrabber.customviews.emoji;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum EmojiCategoryType {
+ SMILEYS_AND_EMOTION("Smileys & Emotion"),
+ // PEOPLE_AND_BODY("People & Body"),
+ ANIMALS_AND_NATURE("Animals & Nature"),
+ FOOD_AND_DRINK("Food & Drink"),
+ TRAVEL_AND_PLACES("Travel & Places"),
+ ACTIVITIES("Activities"),
+ OBJECTS("Objects"),
+ SYMBOLS("Symbols"),
+ FLAGS("Flags"),
+ OTHERS("Others");
+
+ private final String name;
+
+ private static final Map map = new HashMap<>();
+
+ static {
+ for (EmojiCategoryType type : EmojiCategoryType.values()) {
+ map.put(type.name, type);
+ }
+ }
+
+ EmojiCategoryType(final String name) {
+ this.name = name;
+ }
+
+ public static EmojiCategoryType valueOfName(final String name) {
+ final EmojiCategoryType emojiCategoryType = map.get(name);
+ if (emojiCategoryType == null) {
+ return EmojiCategoryType.OTHERS;
+ }
+ return emojiCategoryType;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiGridAdapter.java b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiGridAdapter.java
new file mode 100644
index 00000000..5aff4472
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiGridAdapter.java
@@ -0,0 +1,143 @@
+package awais.instagrabber.customviews.emoji;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.AdapterListUpdateCallback;
+import androidx.recyclerview.widget.AsyncDifferConfig;
+import androidx.recyclerview.widget.AsyncListDiffer;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+
+import awais.instagrabber.customviews.emoji.EmojiPicker.OnEmojiClickListener;
+import awais.instagrabber.databinding.ItemEmojiGridBinding;
+import awais.instagrabber.utils.AppExecutors;
+import awais.instagrabber.utils.emoji.EmojiParser;
+
+public class EmojiGridAdapter extends RecyclerView.Adapter {
+ private static final String TAG = EmojiGridAdapter.class.getSimpleName();
+
+ private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() {
+ @Override
+ public boolean areItemsTheSame(@NonNull final Emoji oldItem, @NonNull final Emoji newItem) {
+ return oldItem.equals(newItem);
+ }
+
+ @Override
+ public boolean areContentsTheSame(@NonNull final Emoji oldItem, @NonNull final Emoji newItem) {
+ return oldItem.equals(newItem);
+ }
+ };
+
+ private final AsyncListDiffer differ;
+ private final OnEmojiLongClickListener onEmojiLongClickListener;
+ private final OnEmojiClickListener onEmojiClickListener;
+ private final EmojiVariantManager emojiVariantManager;
+ private final AppExecutors appExecutors;
+
+ public EmojiGridAdapter(@NonNull final EmojiCategoryType emojiCategoryType,
+ final OnEmojiClickListener onEmojiClickListener,
+ final OnEmojiLongClickListener onEmojiLongClickListener) {
+ this.onEmojiClickListener = onEmojiClickListener;
+ this.onEmojiLongClickListener = onEmojiLongClickListener;
+ differ = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
+ new AsyncDifferConfig.Builder<>(diffCallback).build());
+ final EmojiParser emojiParser = EmojiParser.getInstance();
+ final Map categoryMap = emojiParser.getCategoryMap();
+ emojiVariantManager = EmojiVariantManager.getInstance();
+ appExecutors = AppExecutors.getInstance();
+ setHasStableIds(true);
+ final EmojiCategory emojiCategory = categoryMap.get(emojiCategoryType);
+ if (emojiCategory == null) {
+ differ.submitList(Collections.emptyList());
+ return;
+ }
+ differ.submitList(ImmutableList.copyOf(emojiCategory.getEmojis().values()));
+ }
+
+ @NonNull
+ @Override
+ public EmojiViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
+ final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
+ final ItemEmojiGridBinding binding = ItemEmojiGridBinding.inflate(layoutInflater, parent, false);
+ return new EmojiViewHolder(binding, onEmojiClickListener, onEmojiLongClickListener);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull final EmojiViewHolder holder, final int position) {
+ final Emoji emoji = differ.getCurrentList().get(position);
+ final String variant = emojiVariantManager.getVariant(emoji.getUnicode());
+ if (variant != null) {
+ appExecutors.tasksThread().execute(() -> {
+ final Optional first = emoji.getVariants()
+ .stream()
+ .filter(e -> e.getUnicode().equals(variant))
+ .findFirst();
+ if (!first.isPresent()) return;
+ appExecutors.mainThread().execute(() -> holder.bind(position, first.get(), emoji));
+ });
+ return;
+ }
+ holder.bind(position, emoji, emoji);
+ }
+
+ @Override
+ public long getItemId(final int position) {
+ return differ.getCurrentList().get(position).hashCode();
+ }
+
+ @Override
+ public int getItemViewType(final int position) {
+ return 0;
+ }
+
+ @Override
+ public int getItemCount() {
+ return differ.getCurrentList().size();
+ }
+
+ public static class EmojiViewHolder extends RecyclerView.ViewHolder {
+ private final AppExecutors appExecutors = AppExecutors.getInstance();
+ private final ItemEmojiGridBinding binding;
+ private final OnEmojiClickListener onEmojiClickListener;
+ private final OnEmojiLongClickListener onEmojiLongClickListener;
+
+ public EmojiViewHolder(@NonNull final ItemEmojiGridBinding binding,
+ final OnEmojiClickListener onEmojiClickListener,
+ final OnEmojiLongClickListener onEmojiLongClickListener) {
+ super(binding.getRoot());
+ this.binding = binding;
+ this.onEmojiClickListener = onEmojiClickListener;
+ this.onEmojiLongClickListener = onEmojiLongClickListener;
+ }
+
+ public void bind(final int position, final Emoji emoji, final Emoji parent) {
+ binding.image.setImageDrawable(null);
+ binding.indicator.setVisibility(View.GONE);
+ itemView.setOnLongClickListener(null);
+ itemView.post(() -> {
+ binding.image.setImageDrawable(emoji.getDrawable());
+ final boolean hasVariants = !parent.getVariants().isEmpty();
+ binding.indicator.setVisibility(hasVariants ? View.VISIBLE : View.GONE);
+ if (onEmojiClickListener != null) {
+ itemView.setOnClickListener(v -> onEmojiClickListener.onClick(v, emoji));
+ }
+ if (hasVariants && onEmojiLongClickListener != null) {
+ itemView.setOnLongClickListener(v -> onEmojiLongClickListener.onLongClick(position, v, parent));
+ }
+ });
+ }
+ }
+
+ public interface OnEmojiLongClickListener {
+ boolean onLongClick(int position, View view, Emoji emoji);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiPicker.java b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiPicker.java
new file mode 100644
index 00000000..1c79e77f
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiPicker.java
@@ -0,0 +1,118 @@
+package awais.instagrabber.customviews.emoji;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.widget.AppCompatImageView;
+import androidx.core.content.ContextCompat;
+import androidx.core.widget.ImageViewCompat;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.google.android.material.tabs.TabLayout;
+import com.google.android.material.tabs.TabLayoutMediator;
+
+import java.util.Collection;
+import java.util.List;
+
+import awais.instagrabber.R;
+import awais.instagrabber.utils.emoji.EmojiParser;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+public class EmojiPicker extends LinearLayout {
+ // private static final String TAG = EmojiPicker.class.getSimpleName();
+
+ public EmojiPicker(final Context context) {
+ super(context);
+ setup();
+ }
+
+ public EmojiPicker(final Context context, @Nullable final AttributeSet attrs) {
+ super(context, attrs);
+ setup();
+ }
+
+ public EmojiPicker(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setup();
+ }
+
+ private void setup() {
+ setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ setOrientation(VERTICAL);
+ }
+
+ public void init(@NonNull final View rootView,
+ final OnEmojiClickListener onEmojiClickListener,
+ final OnBackspaceClickListener onBackspaceClickListener) {
+ final TabLayout tabLayout = new TabLayout(getContext());
+ final LayoutParams tabLayoutLayoutParam = new LayoutParams(MATCH_PARENT, WRAP_CONTENT);
+ tabLayout.setLayoutParams(tabLayoutLayoutParam);
+ tabLayout.setSelectedTabIndicatorGravity(TabLayout.INDICATOR_GRAVITY_TOP);
+ // tabLayout.setSelectedTabIndicatorColor(Utils.getThemeAccentColor(getContext()));
+ tabLayout.setSelectedTabIndicatorColor(getResources().getColor(R.color.blue_500));
+
+ final ViewPager2 viewPager2 = new ViewPager2(getContext());
+ final LayoutParams viewPagerLayoutParam = new LayoutParams(MATCH_PARENT, 0);
+ viewPagerLayoutParam.weight = 1;
+ viewPager2.setLayoutParams(viewPagerLayoutParam);
+ viewPager2.setAdapter(new EmojiPickerPageAdapter(rootView, onEmojiClickListener));
+ viewPager2.setOffscreenPageLimit(1);
+
+ final EmojiParser emojiParser = EmojiParser.getInstance();
+ final List categories = emojiParser.getEmojiCategories();
+
+ new TabLayoutMediator(tabLayout, viewPager2, (tab, position) -> {
+ tab.view.setPadding(0, 0, 0, 0);
+ final EmojiCategory emojiCategory = categories.get(position);
+ if (emojiCategory == null) return;
+ final Collection emojis = emojiCategory.getEmojis().values();
+ if (emojis.isEmpty()) return;
+ final AppCompatImageView imageView = getImageView();
+ imageView.setImageResource(emojiCategory.getDrawableRes());
+ tab.setCustomView(imageView);
+ }).attach();
+
+ final TabLayout.Tab tab = tabLayout.newTab();
+ tab.view.setPadding(0, 0, 0, 0);
+ final AppCompatImageView imageView = getImageView();
+ imageView.setImageResource(R.drawable.ic_round_backspace_24);
+ final TypedValue outValue = new TypedValue();
+ getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
+ imageView.setBackgroundResource(outValue.resourceId);
+ imageView.setOnClickListener(v -> {
+ if (onBackspaceClickListener == null) return;
+ onBackspaceClickListener.onClick();
+ });
+ tab.setCustomView(imageView);
+ tab.view.setEnabled(false);
+ tabLayout.addTab(tab);
+ addView(viewPager2);
+ addView(tabLayout);
+ }
+
+ @NonNull
+ private AppCompatImageView getImageView() {
+ final AppCompatImageView imageView = new AppCompatImageView(getContext());
+ imageView.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ ImageViewCompat.setImageTintList(imageView, ContextCompat.getColorStateList(getContext(), R.color.emoji_picker_tab_color));
+ return imageView;
+ }
+
+ public interface OnEmojiClickListener {
+ void onClick(View view, Emoji emoji);
+ }
+
+ public interface OnBackspaceClickListener {
+ void onClick();
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiPickerPageAdapter.java b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiPickerPageAdapter.java
new file mode 100644
index 00000000..d7b7ff03
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiPickerPageAdapter.java
@@ -0,0 +1,78 @@
+package awais.instagrabber.customviews.emoji;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.AdapterListUpdateCallback;
+import androidx.recyclerview.widget.AsyncDifferConfig;
+import androidx.recyclerview.widget.AsyncListDiffer;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import awais.instagrabber.customviews.emoji.EmojiPicker.OnEmojiClickListener;
+import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
+import awais.instagrabber.utils.Utils;
+import awais.instagrabber.utils.emoji.EmojiParser;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+public class EmojiPickerPageAdapter extends RecyclerView.Adapter {
+
+ private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() {
+ @Override
+ public boolean areItemsTheSame(@NonNull final EmojiCategory oldItem, @NonNull final EmojiCategory newItem) {
+ return oldItem.equals(newItem);
+ }
+
+ @Override
+ public boolean areContentsTheSame(@NonNull final EmojiCategory oldItem, @NonNull final EmojiCategory newItem) {
+ return oldItem.equals(newItem);
+ }
+ };
+
+ private final View rootView;
+ private final OnEmojiClickListener onEmojiClickListener;
+ private final AsyncListDiffer differ;
+
+ public EmojiPickerPageAdapter(final View rootView,
+ final OnEmojiClickListener onEmojiClickListener) {
+ this.rootView = rootView;
+ this.onEmojiClickListener = onEmojiClickListener;
+ differ = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
+ new AsyncDifferConfig.Builder<>(diffCallback).build());
+ differ.submitList(EmojiParser.getInstance().getEmojiCategories());
+ setHasStableIds(true);
+ }
+
+ @NonNull
+ @Override
+ public EmojiCategoryPageViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
+ final Context context = parent.getContext();
+ final RecyclerView emojiGrid = new RecyclerView(context);
+ emojiGrid.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ emojiGrid.setLayoutManager(new GridLayoutManager(context, 9));
+ emojiGrid.setHasFixedSize(true);
+ emojiGrid.setClipToPadding(false);
+ emojiGrid.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(8)));
+ return new EmojiCategoryPageViewHolder(rootView, emojiGrid, onEmojiClickListener);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull final EmojiCategoryPageViewHolder holder, final int position) {
+ final EmojiCategory emojiCategory = differ.getCurrentList().get(position);
+ holder.bind(emojiCategory);
+ }
+
+ @Override
+ public long getItemId(final int position) {
+ return differ.getCurrentList().get(position).hashCode();
+ }
+
+ @Override
+ public int getItemCount() {
+ return differ.getCurrentList().size();
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiPopupWindow.java b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiPopupWindow.java
new file mode 100644
index 00000000..76673590
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiPopupWindow.java
@@ -0,0 +1,163 @@
+package awais.instagrabber.customviews.emoji;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager.LayoutParams;
+import android.widget.PopupWindow;
+
+import awais.instagrabber.R;
+import awais.instagrabber.customviews.emoji.EmojiPicker.OnBackspaceClickListener;
+import awais.instagrabber.customviews.emoji.EmojiPicker.OnEmojiClickListener;
+import awais.instagrabber.utils.Utils;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+/**
+ * https://stackoverflow.com/a/33897583/1436766
+ */
+public class EmojiPopupWindow extends PopupWindow {
+
+ private int keyBoardHeight = 0;
+ private Boolean pendingOpen = false;
+ private Boolean isOpened = false;
+ private final View rootView;
+ private final Context context;
+ private final OnEmojiClickListener onEmojiClickListener;
+ private final OnBackspaceClickListener onBackspaceClickListener;
+
+ private OnSoftKeyboardOpenCloseListener onSoftKeyboardOpenCloseListener;
+
+
+ /**
+ * Constructor
+ *
+ * @param rootView The top most layout in your view hierarchy. The difference of this view and the screen height will be used to calculate the keyboard height.
+ */
+ public EmojiPopupWindow(final View rootView,
+ final OnEmojiClickListener onEmojiClickListener,
+ final OnBackspaceClickListener onBackspaceClickListener) {
+ super(rootView.getContext());
+ this.rootView = rootView;
+ this.context = rootView.getContext();
+ this.onEmojiClickListener = onEmojiClickListener;
+ this.onBackspaceClickListener = onBackspaceClickListener;
+ View customView = createCustomView();
+ setContentView(customView);
+ setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+ //default size
+ setSize((int) context.getResources().getDimension(R.dimen.keyboard_height), MATCH_PARENT);
+ }
+
+ /**
+ * Set the listener for the event of keyboard opening or closing.
+ */
+ public void setOnSoftKeyboardOpenCloseListener(OnSoftKeyboardOpenCloseListener listener) {
+ this.onSoftKeyboardOpenCloseListener = listener;
+ }
+
+ /**
+ * Use this function to show the emoji popup.
+ * NOTE: Since, the soft keyboard sizes are variable on different android devices, the
+ * library needs you to open the soft keyboard atleast once before calling this function.
+ * If that is not possible see showAtBottomPending() function.
+ */
+ public void showAtBottom() {
+ showAtLocation(rootView, Gravity.BOTTOM, 0, 0);
+ }
+
+ /**
+ * Use this function when the soft keyboard has not been opened yet. This
+ * will show the emoji popup after the keyboard is up next time.
+ * Generally, you will be calling InputMethodManager.showSoftInput function after
+ * calling this function.
+ */
+ public void showAtBottomPending() {
+ if (isKeyBoardOpen())
+ showAtBottom();
+ else
+ pendingOpen = true;
+ }
+
+ /**
+ * @return Returns true if the soft keyboard is open, false otherwise.
+ */
+ public Boolean isKeyBoardOpen() {
+ return isOpened;
+ }
+
+ /**
+ * Dismiss the popup
+ */
+ @Override
+ public void dismiss() {
+ super.dismiss();
+ }
+
+ /**
+ * Call this function to resize the emoji popup according to your soft keyboard size
+ */
+ public void setSizeForSoftKeyboard() {
+ rootView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+ Rect r = new Rect();
+ rootView.getWindowVisibleDisplayFrame(r);
+
+ int screenHeight = getUsableScreenHeight();
+ int heightDifference = screenHeight - (r.bottom - r.top);
+ int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
+ if (resourceId > 0) {
+ heightDifference -= context.getResources()
+ .getDimensionPixelSize(resourceId);
+ }
+ if (heightDifference > 100) {
+ keyBoardHeight = heightDifference;
+ setSize(MATCH_PARENT, keyBoardHeight);
+ if (!isOpened) {
+ if (onSoftKeyboardOpenCloseListener != null)
+ onSoftKeyboardOpenCloseListener.onKeyboardOpen(keyBoardHeight);
+ }
+ isOpened = true;
+ if (pendingOpen) {
+ showAtBottom();
+ pendingOpen = false;
+ }
+ } else {
+ isOpened = false;
+ if (onSoftKeyboardOpenCloseListener != null)
+ onSoftKeyboardOpenCloseListener.onKeyboardClose();
+ }
+ });
+ }
+
+ private int getUsableScreenHeight() {
+ return Utils.displayMetrics.heightPixels;
+ }
+
+ /**
+ * Manually set the popup window size
+ *
+ * @param width Width of the popup
+ * @param height Height of the popup
+ */
+ public void setSize(int width, int height) {
+ setWidth(width);
+ setHeight(height);
+ }
+
+ private View createCustomView() {
+ final EmojiPicker emojiPicker = new EmojiPicker(context);
+ final LayoutParams layoutParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT);
+ emojiPicker.setLayoutParams(layoutParams);
+ emojiPicker.init(rootView, onEmojiClickListener, onBackspaceClickListener);
+ return emojiPicker;
+ }
+
+
+ public interface OnSoftKeyboardOpenCloseListener {
+ void onKeyboardOpen(int keyBoardHeight);
+
+ void onKeyboardClose();
+ }
+}
+
diff --git a/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiVariantManager.java b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiVariantManager.java
new file mode 100644
index 00000000..95eb3330
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiVariantManager.java
@@ -0,0 +1,66 @@
+package awais.instagrabber.customviews.emoji;
+
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import awais.instagrabber.utils.AppExecutors;
+import awais.instagrabber.utils.TextUtils;
+import awais.instagrabber.utils.Utils;
+
+import static awais.instagrabber.utils.Constants.PREF_EMOJI_VARIANTS;
+
+public class EmojiVariantManager {
+ private static final String TAG = EmojiVariantManager.class.getSimpleName();
+ private static final Object LOCK = new Object();
+
+ private final AppExecutors appExecutors = AppExecutors.getInstance();
+ private final Map selectedVariantMap = new HashMap<>();
+
+ private static EmojiVariantManager instance;
+
+ public static EmojiVariantManager getInstance() {
+ if (instance == null) {
+ synchronized (LOCK) {
+ if (instance == null) {
+ instance = new EmojiVariantManager();
+ }
+ }
+ }
+ return instance;
+ }
+
+ private EmojiVariantManager() {
+ final String variantsJson = Utils.settingsHelper.getString(PREF_EMOJI_VARIANTS);
+ if (TextUtils.isEmpty(variantsJson)) return;
+ try {
+ final JSONObject variantsJSONObject = new JSONObject(variantsJson);
+ final Iterator keys = variantsJSONObject.keys();
+ keys.forEachRemaining(s -> selectedVariantMap.put(s, variantsJSONObject.optString(s)));
+ } catch (JSONException e) {
+ Log.e(TAG, "EmojiVariantManager: ", e);
+ }
+ }
+
+ @Nullable
+ public String getVariant(final String parentUnicode) {
+ return selectedVariantMap.get(parentUnicode);
+ }
+
+ public void setVariant(final String parent, final String variant) {
+ if (parent == null || variant == null) return;
+ selectedVariantMap.put(parent, variant);
+ appExecutors.tasksThread().execute(() -> {
+ final JSONObject jsonObject = new JSONObject(selectedVariantMap);
+ final String json = jsonObject.toString();
+ Utils.settingsHelper.putString(PREF_EMOJI_VARIANTS, json);
+ });
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiVariantPopup.java b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiVariantPopup.java
new file mode 100644
index 00000000..b0de3b6c
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/emoji/EmojiVariantPopup.java
@@ -0,0 +1,159 @@
+package awais.instagrabber.customviews.emoji;
+
+/*
+ * Copyright (C) 2016 - Niklas Baudy, Ruben Gees, Mario ฤaniฤ and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.drawable.BitmapDrawable;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.PopupWindow;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import awais.instagrabber.customviews.emoji.EmojiPicker.OnEmojiClickListener;
+import awais.instagrabber.databinding.ItemEmojiGridBinding;
+import awais.instagrabber.databinding.LayoutEmojiVariantPopupBinding;
+import awais.instagrabber.utils.AppExecutors;
+
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+public final class EmojiVariantPopup {
+ private static final int DO_NOT_UPDATE_FLAG = -1;
+
+ private final View rootView;
+ private final OnEmojiClickListener listener;
+
+ private PopupWindow popupWindow;
+ private View rootImageView;
+ private final EmojiVariantManager emojiVariantManager;
+ private final AppExecutors appExecutors;
+
+ public EmojiVariantPopup(@NonNull final View rootView,
+ final OnEmojiClickListener listener) {
+ this.rootView = rootView;
+ this.listener = listener;
+ emojiVariantManager = EmojiVariantManager.getInstance();
+ appExecutors = AppExecutors.getInstance();
+ }
+
+ public void show(@NonNull final View view, @NonNull final Emoji emoji) {
+ dismiss();
+
+ rootImageView = view;
+
+ final View content = initView(view.getContext(), emoji, view.getWidth());
+
+ popupWindow = new PopupWindow(content, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);
+ popupWindow.setFocusable(true);
+ popupWindow.setOutsideTouchable(true);
+ popupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+ popupWindow.setBackgroundDrawable(new BitmapDrawable(view.getContext().getResources(), (Bitmap) null));
+
+ content.measure(makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
+
+ final Point location = locationOnScreen(view);
+ final Point desiredLocation = new Point(
+ location.x - content.getMeasuredWidth() / 2 + view.getWidth() / 2,
+ location.y - content.getMeasuredHeight()
+ );
+
+ popupWindow.showAtLocation(rootView, Gravity.NO_GRAVITY, desiredLocation.x, desiredLocation.y);
+ rootImageView.getParent().requestDisallowInterceptTouchEvent(true);
+ fixPopupLocation(popupWindow, desiredLocation);
+ }
+
+ public void dismiss() {
+ rootImageView = null;
+
+ if (popupWindow != null) {
+ popupWindow.dismiss();
+ popupWindow = null;
+ }
+ }
+
+ private View initView(@NonNull final Context context, @NonNull final Emoji emoji, final int width) {
+ final LayoutInflater layoutInflater = LayoutInflater.from(context);
+ final LayoutEmojiVariantPopupBinding binding = LayoutEmojiVariantPopupBinding.inflate(layoutInflater, null, false);
+ final List variants = new ArrayList<>(emoji.getVariants());
+ // Add parent at start of list
+ // variants.add(0, emoji);
+ for (final Emoji variant : variants) {
+ final ItemEmojiGridBinding itemBinding = ItemEmojiGridBinding.inflate(layoutInflater, binding.container, false);
+ final ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) itemBinding.image.getLayoutParams();
+ // Use the same size for Emojis as in the picker.
+ layoutParams.width = width;
+ itemBinding.image.setImageDrawable(variant.getDrawable());
+ itemBinding.image.setOnClickListener(view -> {
+ if (listener != null) {
+ if (!variant.getUnicode().equals(emojiVariantManager.getVariant(emoji.getUnicode()))) {
+ emojiVariantManager.setVariant(emoji.getUnicode(), variant.getUnicode());
+ }
+ listener.onClick(view, variant);
+ }
+ dismiss();
+ });
+ binding.container.addView(itemBinding.getRoot());
+ }
+ return binding.getRoot();
+ }
+
+ @NonNull
+ private Point locationOnScreen(@NonNull final View view) {
+ final int[] location = new int[2];
+ view.getLocationOnScreen(location);
+ return new Point(location[0], location[1]);
+ }
+
+ private void fixPopupLocation(@NonNull final PopupWindow popupWindow, @NonNull final Point desiredLocation) {
+ popupWindow.getContentView().post(() -> {
+ final Point actualLocation = locationOnScreen(popupWindow.getContentView());
+
+ if (!(actualLocation.x == desiredLocation.x && actualLocation.y == desiredLocation.y)) {
+ final int differenceX = actualLocation.x - desiredLocation.x;
+ final int differenceY = actualLocation.y - desiredLocation.y;
+
+ final int fixedOffsetX;
+ final int fixedOffsetY;
+
+ if (actualLocation.x > desiredLocation.x) {
+ fixedOffsetX = desiredLocation.x - differenceX;
+ } else {
+ fixedOffsetX = desiredLocation.x + differenceX;
+ }
+
+ if (actualLocation.y > desiredLocation.y) {
+ fixedOffsetY = desiredLocation.y - differenceY;
+ } else {
+ fixedOffsetY = desiredLocation.y + differenceY;
+ }
+
+ popupWindow.update(fixedOffsetX, fixedOffsetY, DO_NOT_UPDATE_FLAG, DO_NOT_UPDATE_FLAG);
+ }
+ });
+ }
+}
+
diff --git a/app/src/main/java/awais/instagrabber/customviews/emoji/GoogleCompatEmojiDrawable.java b/app/src/main/java/awais/instagrabber/customviews/emoji/GoogleCompatEmojiDrawable.java
new file mode 100644
index 00000000..f2bec738
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/emoji/GoogleCompatEmojiDrawable.java
@@ -0,0 +1,99 @@
+package awais.instagrabber.customviews.emoji;
+
+/*
+ * Copyright (C) 2016 - Niklas Baudy, Ruben Gees, Mario ฤaniฤ and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.emoji.text.EmojiCompat;
+import androidx.emoji.text.EmojiSpan;
+
+/**
+ * An emoji drawable backed by a span generated by the Google emoji support library.
+ */
+final class GoogleCompatEmojiDrawable extends Drawable {
+ private static final String TAG = GoogleCompatEmojiDrawable.class.getSimpleName();
+ private static final float TEXT_SIZE_FACTOR = 0.8f;
+ private static final float BASELINE_OFFSET_FACTOR = 0.225f;
+
+ private EmojiSpan emojiSpan;
+ private boolean processed;
+ private CharSequence emojiCharSequence;
+ private final TextPaint textPaint = new TextPaint();
+
+ GoogleCompatEmojiDrawable(@NonNull final String unicode) {
+ emojiCharSequence = unicode;
+ textPaint.setStyle(Paint.Style.FILL);
+ textPaint.setColor(0x0ffffffff);
+ textPaint.setAntiAlias(true);
+ }
+
+ private void process() {
+ emojiCharSequence = EmojiCompat.get().process(emojiCharSequence);
+ if (emojiCharSequence instanceof Spanned) {
+ final Object[] spans = ((Spanned) emojiCharSequence).getSpans(0, emojiCharSequence.length(), EmojiSpan.class);
+ if (spans.length > 0) {
+ emojiSpan = (EmojiSpan) spans[0];
+ }
+ }
+ }
+
+ @Override
+ public void draw(@NonNull final Canvas canvas) {
+ final Rect bounds = getBounds();
+ textPaint.setTextSize(bounds.height() * TEXT_SIZE_FACTOR);
+ final int y = Math.round(bounds.bottom - bounds.height() * BASELINE_OFFSET_FACTOR);
+
+ if (!processed && EmojiCompat.get().getLoadState() != EmojiCompat.LOAD_STATE_LOADING) {
+ processed = true;
+ if (EmojiCompat.get().getLoadState() != EmojiCompat.LOAD_STATE_FAILED) {
+ process();
+ }
+ }
+
+ if (emojiSpan == null) {
+ canvas.drawText(emojiCharSequence, 0, emojiCharSequence.length(), bounds.left, y, textPaint);
+ } else {
+ emojiSpan.draw(canvas, emojiCharSequence, 0, emojiCharSequence.length(), bounds.left, bounds.top, y, bounds.bottom, textPaint);
+ }
+ }
+
+ @Override
+ public void setAlpha(final int alpha) {
+ textPaint.setAlpha(alpha);
+ }
+
+ @Override
+ public void setColorFilter(final ColorFilter colorFilter) {
+ textPaint.setColorFilter(colorFilter);
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.UNKNOWN;
+ }
+}
+
diff --git a/app/src/main/java/awais/instagrabber/customviews/helpers/HeaderItemDecoration.java b/app/src/main/java/awais/instagrabber/customviews/helpers/HeaderItemDecoration.java
new file mode 100644
index 00000000..35e9802f
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/helpers/HeaderItemDecoration.java
@@ -0,0 +1,199 @@
+package awais.instagrabber.customviews.helpers;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.util.Pair;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * Java implementation of this gist by filipkowicz
+ */
+public class HeaderItemDecoration extends RecyclerView.ItemDecoration {
+ private static final String TAG = HeaderItemDecoration.class.getSimpleName();
+
+ private final HeaderItemDecorationCallback callback;
+
+ private boolean layoutReversed = false;
+ private Pair currentHeader;
+
+ public HeaderItemDecoration(@NonNull RecyclerView parent,
+ @NonNull HeaderItemDecorationCallback callback) {
+ this.callback = callback;
+ final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
+ if (layoutManager instanceof LinearLayoutManager) {
+ layoutReversed = ((LinearLayoutManager) layoutManager).getReverseLayout();
+ }
+ //noinspection rawtypes
+ final RecyclerView.Adapter adapter = parent.getAdapter();
+ if (adapter == null) return;
+ adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
+ @Override
+ public void onChanged() {
+ // clear saved header as it can be outdated now
+ Log.d(TAG, "registerAdapterDataObserver");
+ currentHeader = null;
+ }
+ });
+ parent.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ // clear saved layout as it may need layout update
+ Log.d(TAG, "addOnLayoutChangeListener");
+ currentHeader = null;
+ });
+ parent.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() {
+ @Override
+ public boolean onInterceptTouchEvent(@NonNull final RecyclerView rv, @NonNull final MotionEvent e) {
+ if (e.getAction() == MotionEvent.ACTION_DOWN && currentHeader != null) {
+ final RecyclerView.ViewHolder viewHolder = currentHeader.second;
+ if (viewHolder != null && viewHolder.itemView != null) {
+ final int bottom = viewHolder.itemView.getBottom();
+ return e.getY() <= bottom;
+ }
+ }
+ return super.onInterceptTouchEvent(rv, e);
+ }
+ });
+ }
+
+ @Override
+ public void onDrawOver(@NonNull final Canvas c, @NonNull final RecyclerView parent, @NonNull final RecyclerView.State state) {
+ super.onDrawOver(c, parent, state);
+ final View topChild = parent.findChildViewUnder(
+ parent.getPaddingLeft(),
+ parent.getPaddingTop()
+ );
+ if (topChild == null) {
+ return;
+ }
+ final int topChildPosition = parent.getChildAdapterPosition(topChild);
+ if (topChildPosition == RecyclerView.NO_POSITION) {
+ return;
+ }
+ final View headerView = getHeaderViewForItem(topChildPosition, parent);
+ if (headerView == null) {
+ return;
+ }
+ final int contactPoint = headerView.getBottom() + parent.getPaddingTop();
+ final View childInContact = getChildInContact(parent, contactPoint);
+ if (childInContact != null && callback.isHeader(parent.getChildAdapterPosition(childInContact))) {
+ moveHeader(c, headerView, childInContact, parent.getPaddingTop());
+ return;
+ }
+ drawHeader(c, headerView, parent.getPaddingTop());
+ }
+
+ private void drawHeader(@NonNull final Canvas c, @NonNull final View header, final int paddingTop) {
+ c.save();
+ c.translate(0f, paddingTop);
+ header.draw(c);
+ c.restore();
+ }
+
+ private void moveHeader(@NonNull final Canvas c, @NonNull final View currentHeader, @NonNull final View nextHeader, final int paddingTop) {
+ c.save();
+ c.translate(0f, nextHeader.getTop() - currentHeader.getHeight() /*+ paddingTop*/);
+ currentHeader.draw(c);
+ c.restore();
+ }
+
+ @Nullable
+ private View getChildInContact(@NonNull final RecyclerView parent, final int contactPoint) {
+ View childInContact = null;
+ final int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = parent.getChildAt(i);
+ final Rect mBounds = new Rect();
+ parent.getDecoratedBoundsWithMargins(child, mBounds);
+ if (mBounds.bottom > contactPoint) {
+ if (mBounds.top <= contactPoint) {
+ // This child overlaps the contactPoint
+ childInContact = child;
+ break;
+ }
+ }
+ }
+ return childInContact;
+ }
+
+ @Nullable
+ private View getHeaderViewForItem(final int itemPosition, @NonNull final RecyclerView parent) {
+ if (parent.getAdapter() == null) {
+ return null;
+ }
+ final int headerPosition = getHeaderPositionForItem(itemPosition, parent.getAdapter());
+ if (headerPosition == RecyclerView.NO_POSITION) return null;
+ final int headerType = parent.getAdapter().getItemViewType(headerPosition);
+ // if match reuse viewHolder
+ if (currentHeader != null
+ && currentHeader.first == headerPosition
+ && currentHeader.second.getItemViewType() == headerType) {
+ return currentHeader.second.itemView;
+ }
+ final RecyclerView.ViewHolder headerHolder = parent.getAdapter().createViewHolder(parent, headerType);
+ if (headerHolder != null) {
+ //noinspection unchecked
+ parent.getAdapter().onBindViewHolder(headerHolder, headerPosition);
+ fixLayoutSize(parent, headerHolder.itemView);
+ // save for next draw
+ currentHeader = new Pair<>(headerPosition, headerHolder);
+ return headerHolder.itemView;
+ }
+ return null;
+ }
+
+ @SuppressWarnings("rawtypes")
+ private int getHeaderPositionForItem(final int itemPosition, final RecyclerView.Adapter adapter) {
+ int headerPosition = RecyclerView.NO_POSITION;
+ int currentPosition = itemPosition;
+ do {
+ if (callback.isHeader(currentPosition)) {
+ headerPosition = currentPosition;
+ break;
+ }
+ currentPosition += layoutReversed ? 1 : -1;
+ } while (layoutReversed ? currentPosition < adapter.getItemCount() : currentPosition >= 0);
+ return headerPosition;
+ }
+
+ /**
+ * Properly measures and layouts the top sticky header.
+ *
+ * @param parent ViewGroup: RecyclerView in this case.
+ */
+ private void fixLayoutSize(@NonNull final ViewGroup parent, @NonNull final View view) {
+
+ // Specs for parent (RecyclerView)
+ final int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
+ final int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);
+
+ // Specs for children (headers)
+ final int childWidthSpec = ViewGroup.getChildMeasureSpec(
+ widthSpec,
+ parent.getPaddingLeft() + parent.getPaddingRight(),
+ view.getLayoutParams().width
+ );
+ final int childHeightSpec = ViewGroup.getChildMeasureSpec(
+ heightSpec,
+ parent.getPaddingTop() + parent.getPaddingBottom(),
+ view.getLayoutParams().height
+ );
+
+ view.measure(childWidthSpec, childHeightSpec);
+ view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
+ }
+
+ public View getCurrentHeader() {
+ return currentHeader == null ? null : currentHeader.second.itemView;
+ }
+
+ public interface HeaderItemDecorationCallback {
+ boolean isHeader(int itemPosition);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/helpers/HeightProvider.java b/app/src/main/java/awais/instagrabber/customviews/helpers/HeightProvider.java
new file mode 100644
index 00000000..9287c281
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/helpers/HeightProvider.java
@@ -0,0 +1,66 @@
+package awais.instagrabber.customviews.helpers;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.WindowManager.LayoutParams;
+import android.widget.PopupWindow;
+
+public class HeightProvider extends PopupWindow implements OnGlobalLayoutListener {
+ private final Activity mActivity;
+ private final View rootView;
+ private HeightListener listener;
+ private int heightMax;
+
+ public HeightProvider(Activity activity) {
+ super(activity);
+ this.mActivity = activity;
+
+ rootView = new View(activity);
+ setContentView(rootView);
+
+ rootView.getViewTreeObserver().addOnGlobalLayoutListener(this);
+ setBackgroundDrawable(new ColorDrawable(0));
+
+ setWidth(0);
+ setHeight(LayoutParams.MATCH_PARENT);
+
+ setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
+ setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
+ }
+
+ public HeightProvider init() {
+ if (!isShowing()) {
+ final View view = mActivity.getWindow().getDecorView();
+ view.post(() -> showAtLocation(view, Gravity.NO_GRAVITY, 0, 0));
+ }
+ return this;
+ }
+
+ public HeightProvider setHeightListener(HeightListener listener) {
+ this.listener = listener;
+ return this;
+ }
+
+ @Override
+ public void onGlobalLayout() {
+ Rect rect = new Rect();
+ rootView.getWindowVisibleDisplayFrame(rect);
+ if (rect.bottom > heightMax) {
+ heightMax = rect.bottom;
+ }
+
+ int keyboardHeight = heightMax - rect.bottom;
+ if (listener != null) {
+ listener.onHeightChanged(keyboardHeight);
+ }
+ }
+
+ public interface HeightListener {
+ void onHeightChanged(int height);
+ }
+}
+
diff --git a/app/src/main/java/awais/instagrabber/customviews/helpers/RecordViewAnimationHelper.java b/app/src/main/java/awais/instagrabber/customviews/helpers/RecordViewAnimationHelper.java
new file mode 100644
index 00000000..3375c5b2
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/helpers/RecordViewAnimationHelper.java
@@ -0,0 +1,205 @@
+package awais.instagrabber.customviews.helpers;
+
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Handler;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+import android.widget.ImageView;
+
+import androidx.appcompat.widget.AppCompatImageView;
+import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
+import androidx.vectordrawable.graphics.drawable.AnimatorInflaterCompat;
+
+import awais.instagrabber.R;
+import awais.instagrabber.customviews.RecordButton;
+import awais.instagrabber.customviews.RecordView.OnBasketAnimationEnd;
+
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+
+public class RecordViewAnimationHelper {
+ private static final String TAG = RecordViewAnimationHelper.class.getSimpleName();
+ private final Context context;
+ private final AnimatedVectorDrawableCompat animatedVectorDrawable;
+ private final ImageView basketImg;
+ private final ImageView smallBlinkingMic;
+ private AlphaAnimation alphaAnimation;
+ private OnBasketAnimationEnd onBasketAnimationEndListener;
+ private boolean isBasketAnimating;
+ private boolean isStartRecorded = false;
+ private float micX = 0;
+ private float micY = 0;
+ private AnimatorSet micAnimation;
+ private TranslateAnimation translateAnimation1, translateAnimation2;
+ private Handler handler1, handler2;
+
+ public RecordViewAnimationHelper(Context context, AppCompatImageView basketImg, AppCompatImageView smallBlinkingMic) {
+ this.context = context;
+ this.smallBlinkingMic = smallBlinkingMic;
+ this.basketImg = basketImg;
+ animatedVectorDrawable = AnimatedVectorDrawableCompat.create(context, R.drawable.recv_basket_animated);
+ }
+
+ @SuppressLint("RestrictedApi")
+ public void animateBasket(float basketInitialY) {
+ isBasketAnimating = true;
+
+ clearAlphaAnimation(false);
+
+ //save initial x,y values for mic icon
+ if (micX == 0) {
+ micX = smallBlinkingMic.getX();
+ micY = smallBlinkingMic.getY();
+ }
+
+ micAnimation = (AnimatorSet) AnimatorInflaterCompat.loadAnimator(context, R.animator.delete_mic_animation);
+ micAnimation.setTarget(smallBlinkingMic); // set the view you want to animate
+
+ translateAnimation1 = new TranslateAnimation(0, 0, basketInitialY, basketInitialY - 90);
+ translateAnimation1.setDuration(250);
+
+ translateAnimation2 = new TranslateAnimation(0, 0, basketInitialY - 90, basketInitialY);
+ translateAnimation2.setDuration(350);
+
+ micAnimation.start();
+ basketImg.setImageDrawable(animatedVectorDrawable);
+
+ handler1 = new Handler();
+ handler1.postDelayed(() -> {
+ basketImg.setVisibility(VISIBLE);
+ basketImg.startAnimation(translateAnimation1);
+ }, 350);
+
+ translateAnimation1.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {}
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ animatedVectorDrawable.start();
+ handler2 = new Handler();
+ handler2.postDelayed(() -> {
+ basketImg.startAnimation(translateAnimation2);
+ smallBlinkingMic.setVisibility(INVISIBLE);
+ basketImg.setVisibility(INVISIBLE);
+ }, 450);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {}
+ });
+
+ translateAnimation2.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {}
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ basketImg.setVisibility(INVISIBLE);
+ isBasketAnimating = false;
+ //if the user pressed the record button while the animation is running
+ // then do NOT call on Animation end
+ if (onBasketAnimationEndListener != null && !isStartRecorded) {
+ onBasketAnimationEndListener.onAnimationEnd();
+ }
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {}
+ });
+ }
+
+ //if the user started a new Record while the Animation is running
+ // then we want to stop the current animation and revert views back to default state
+ public void resetBasketAnimation() {
+ if (isBasketAnimating) {
+ translateAnimation1.reset();
+ translateAnimation1.cancel();
+ translateAnimation2.reset();
+ translateAnimation2.cancel();
+ micAnimation.cancel();
+ smallBlinkingMic.clearAnimation();
+ basketImg.clearAnimation();
+ if (handler1 != null) {
+ handler1.removeCallbacksAndMessages(null);
+ }
+ if (handler2 != null) {
+ handler2.removeCallbacksAndMessages(null);
+ }
+ basketImg.setVisibility(INVISIBLE);
+ smallBlinkingMic.setX(micX);
+ smallBlinkingMic.setY(micY);
+ smallBlinkingMic.setVisibility(View.GONE);
+ isBasketAnimating = false;
+ }
+ }
+
+ public void clearAlphaAnimation(boolean hideView) {
+ if (alphaAnimation != null) {
+ alphaAnimation.cancel();
+ alphaAnimation.reset();
+ }
+ smallBlinkingMic.clearAnimation();
+ if (hideView) {
+ smallBlinkingMic.setVisibility(View.GONE);
+ }
+ }
+
+ public void animateSmallMicAlpha() {
+ alphaAnimation = new AlphaAnimation(0.0f, 1.0f);
+ alphaAnimation.setDuration(500);
+ alphaAnimation.setRepeatMode(Animation.REVERSE);
+ alphaAnimation.setRepeatCount(Animation.INFINITE);
+ smallBlinkingMic.startAnimation(alphaAnimation);
+ }
+
+ public void moveRecordButtonAndSlideToCancelBack(final RecordButton recordBtn, View slideToCancelLayout, float initialX, float difX) {
+ final ValueAnimator positionAnimator = ValueAnimator.ofFloat(recordBtn.getX(), initialX);
+ positionAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+ positionAnimator.addUpdateListener(animation -> {
+ float x = (Float) animation.getAnimatedValue();
+ recordBtn.setX(x);
+ });
+ recordBtn.stopScale();
+ positionAnimator.setDuration(200);
+ positionAnimator.start();
+
+ // if the move event was not called ,then the difX will still 0 and there is no need to move it back
+ if (difX != 0) {
+ float x = initialX - difX;
+ slideToCancelLayout.animate()
+ .x(x)
+ .setDuration(0)
+ .start();
+ }
+ }
+
+ public void resetSmallMic() {
+ smallBlinkingMic.setAlpha(1.0f);
+ smallBlinkingMic.setScaleX(1.0f);
+ smallBlinkingMic.setScaleY(1.0f);
+ }
+
+ public void setOnBasketAnimationEndListener(OnBasketAnimationEnd onBasketAnimationEndListener) {
+ this.onBasketAnimationEndListener = onBasketAnimationEndListener;
+
+ }
+
+ public void onAnimationEnd() {
+ if (onBasketAnimationEndListener != null) {
+ onBasketAnimationEndListener.onAnimationEnd();
+ }
+ }
+
+ //check if the user started a new Record by pressing the RecordButton
+ public void setStartRecorded(boolean startRecorded) {
+ isStartRecorded = startRecorded;
+ }
+
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/helpers/RecyclerLazyLoaderAtBottom.java b/app/src/main/java/awais/instagrabber/customviews/helpers/RecyclerLazyLoaderAtEdge.java
similarity index 52%
rename from app/src/main/java/awais/instagrabber/customviews/helpers/RecyclerLazyLoaderAtBottom.java
rename to app/src/main/java/awais/instagrabber/customviews/helpers/RecyclerLazyLoaderAtEdge.java
index 0c4168b6..3d5cf30f 100644
--- a/app/src/main/java/awais/instagrabber/customviews/helpers/RecyclerLazyLoaderAtBottom.java
+++ b/app/src/main/java/awais/instagrabber/customviews/helpers/RecyclerLazyLoaderAtEdge.java
@@ -5,18 +5,27 @@ import android.os.Handler;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
-public final class RecyclerLazyLoaderAtBottom extends RecyclerView.OnScrollListener {
+public final class RecyclerLazyLoaderAtEdge extends RecyclerView.OnScrollListener {
- @NonNull
private final RecyclerView.LayoutManager layoutManager;
private final LazyLoadListener lazyLoadListener;
+ private final boolean atTop;
private int currentPage;
private int previousItemCount;
private boolean loading;
- public RecyclerLazyLoaderAtBottom(@NonNull final RecyclerView.LayoutManager layoutManager,
- final LazyLoadListener lazyLoadListener) {
+ public RecyclerLazyLoaderAtEdge(@NonNull final RecyclerView.LayoutManager layoutManager,
+ final LazyLoadListener lazyLoadListener) {
this.layoutManager = layoutManager;
+ this.atTop = false;
+ this.lazyLoadListener = lazyLoadListener;
+ }
+
+ public RecyclerLazyLoaderAtEdge(@NonNull final RecyclerView.LayoutManager layoutManager,
+ final boolean atTop,
+ final LazyLoadListener lazyLoadListener) {
+ this.layoutManager = layoutManager;
+ this.atTop = atTop;
this.lazyLoadListener = lazyLoadListener;
}
@@ -27,11 +36,12 @@ public final class RecyclerLazyLoaderAtBottom extends RecyclerView.OnScrollListe
if (itemCount > previousItemCount) {
loading = false;
}
- if (!recyclerView.canScrollVertically(RecyclerView.SCROLL_AXIS_HORIZONTAL) && newState == RecyclerView.SCROLL_STATE_IDLE) {
- if (!loading && lazyLoadListener != null) {
- loading = true;
- new Handler().postDelayed(() -> lazyLoadListener.onLoadMore(++currentPage), 500);
- }
+ if (!recyclerView.canScrollVertically(atTop ? -1 : 1)
+ && newState == RecyclerView.SCROLL_STATE_IDLE
+ && !loading
+ && lazyLoadListener != null) {
+ loading = true;
+ new Handler().postDelayed(() -> lazyLoadListener.onLoadMore(++currentPage), 300);
}
}
diff --git a/app/src/main/java/awais/instagrabber/customviews/helpers/TextWatcherAdapter.java b/app/src/main/java/awais/instagrabber/customviews/helpers/TextWatcherAdapter.java
new file mode 100644
index 00000000..f989a142
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/helpers/TextWatcherAdapter.java
@@ -0,0 +1,15 @@
+package awais.instagrabber.customviews.helpers;
+
+import android.text.Editable;
+import android.text.TextWatcher;
+
+public class TextWatcherAdapter implements TextWatcher {
+ @Override
+ public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {}
+
+ @Override
+ public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {}
+
+ @Override
+ public void afterTextChanged(final Editable s) {}
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/helpers/VerticalSpaceItemDecoration.java b/app/src/main/java/awais/instagrabber/customviews/helpers/VerticalSpaceItemDecoration.java
new file mode 100644
index 00000000..71f7705b
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/customviews/helpers/VerticalSpaceItemDecoration.java
@@ -0,0 +1,20 @@
+package awais.instagrabber.customviews.helpers;
+
+import android.graphics.Rect;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class VerticalSpaceItemDecoration extends RecyclerView.ItemDecoration {
+ private final int verticalSpaceHeight;
+
+ public VerticalSpaceItemDecoration(int verticalSpaceHeight) {
+ this.verticalSpaceHeight = verticalSpaceHeight;
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
+ outRect.bottom = verticalSpaceHeight;
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/customviews/masoudss_waveform/WaveformSeekBar.java b/app/src/main/java/awais/instagrabber/customviews/masoudss_waveform/WaveformSeekBar.java
index 6132a6e4..9e1cfdb8 100755
--- a/app/src/main/java/awais/instagrabber/customviews/masoudss_waveform/WaveformSeekBar.java
+++ b/app/src/main/java/awais/instagrabber/customviews/masoudss_waveform/WaveformSeekBar.java
@@ -1,6 +1,5 @@
package awais.instagrabber.customviews.masoudss_waveform;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
@@ -15,9 +14,9 @@ import android.view.ViewConfiguration;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
import awais.instagrabber.R;
+import awais.instagrabber.utils.CubicInterpolation;
import awais.instagrabber.utils.Utils;
public final class WaveformSeekBar extends View {
@@ -25,19 +24,21 @@ public final class WaveformSeekBar extends View {
private final Paint mWavePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final RectF mWaveRect = new RectF();
private final Canvas mProgressCanvas = new Canvas();
- private final WaveGravity waveGravity = WaveGravity.BOTTOM;
+ private final WaveGravity waveGravity = WaveGravity.CENTER;
private final int waveBackgroundColor;
private final int waveProgressColor;
private final float waveWidth = Utils.convertDpToPx(3);
private final float waveMinHeight = Utils.convertDpToPx(4);
private final float waveCornerRadius = Utils.convertDpToPx(2);
private final float waveGap = Utils.convertDpToPx(1);
- private int mCanvasWidth = 0;
- private int mCanvasHeight = 0;
+ // private int mCanvasWidth = 0;
+ // private int mCanvasHeight = 0;
private float mTouchDownX = 0F;
- private int[] sample;
+ private float[] sample;
private int progress = 0;
private WaveFormProgressChangeListener progressChangeListener;
+ private int wavesCount;
+ private CubicInterpolation interpolation;
public WaveformSeekBar(final Context context) {
this(context, null);
@@ -49,79 +50,82 @@ public final class WaveformSeekBar extends View {
public WaveformSeekBar(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
- this.waveBackgroundColor = ContextCompat.getColor(context, R.color.text_color_light);
- this.waveProgressColor = ContextCompat.getColor(context, R.color.text_color_dark);
+ this.waveBackgroundColor = context.getResources().getColor(R.color.white);
+ this.waveProgressColor = context.getResources().getColor(R.color.blue_800);
}
- private int getSampleMax() {
- int max = -1;
- if (sample != null) for (final int i : sample) if (i >= max) max = i;
+ private float getSampleMax() {
+ float max = -1f;
+ if (sample != null) {
+ for (final float v : sample) {
+ if (v > max) max = v;
+ }
+ }
return max;
}
- @SuppressLint("DrawAllocation")
@Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
- if (sample != null && sample.length != 0) {
- final int availableWidth = getAvailableWidth();
- final int availableHeight = getAvailableHeight();
+ if (sample == null || sample.length == 0) return;
+ final int availableWidth = getAvailableWidth();
+ final int availableHeight = getAvailableHeight();
- final float step = availableWidth / (waveGap + waveWidth) / sample.length;
+ // final float step = availableWidth / (waveGap + waveWidth) / sample.size();
- float i = 0F;
- float lastWaveRight = (float) getPaddingLeft();
+ int i = 0;
+ float lastWaveRight = (float) getPaddingLeft();
- final int sampleMax = getSampleMax();
- while (i < sample.length) {
- float waveHeight = availableHeight * ((float) sample[(int) i] / sampleMax);
+ final float sampleMax = getSampleMax();
+ while (i < wavesCount) {
+ final float t = lastWaveRight / availableWidth * sample.length;
+ float waveHeight = availableHeight * (interpolation.interpolate(t) / sampleMax);
- if (waveHeight < waveMinHeight)
- waveHeight = waveMinHeight;
+ if (waveHeight < waveMinHeight)
+ waveHeight = waveMinHeight;
- final float top;
- if (waveGravity == WaveGravity.TOP) {
- top = (float) getPaddingTop();
- } else if (waveGravity == WaveGravity.CENTER) {
- top = (float) getPaddingTop() + availableHeight / 2F - waveHeight / 2F;
- } else if (waveGravity == WaveGravity.BOTTOM) {
- top = mCanvasHeight - (float) getPaddingBottom() - waveHeight;
- } else {
- top = 0;
- }
-
- mWaveRect.set(lastWaveRight, top, lastWaveRight + waveWidth, top + waveHeight);
-
- if (mWaveRect.contains(availableWidth * progress / 100F, mWaveRect.centerY())) {
- int bitHeight = (int) mWaveRect.height();
- if (bitHeight <= 0) bitHeight = (int) waveWidth;
-
- final Bitmap bitmap = Bitmap.createBitmap(availableWidth, bitHeight, Bitmap.Config.ARGB_8888);
- mProgressCanvas.setBitmap(bitmap);
-
- float fillWidth = availableWidth * progress / 100F;
-
- mWavePaint.setColor(waveProgressColor);
- mProgressCanvas.drawRect(0F, 0F, fillWidth, mWaveRect.bottom, mWavePaint);
-
- mWavePaint.setColor(waveBackgroundColor);
- mProgressCanvas.drawRect(fillWidth, 0F, (float) availableWidth, mWaveRect.bottom, mWavePaint);
-
- mWavePaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
- } else {
- mWavePaint.setColor(mWaveRect.right <= availableWidth * progress / 100F ? waveProgressColor : waveBackgroundColor);
- mWavePaint.setShader(null);
- }
-
- canvas.drawRoundRect(mWaveRect, waveCornerRadius, waveCornerRadius, mWavePaint);
-
- lastWaveRight = mWaveRect.right + waveGap;
-
- if (lastWaveRight + waveWidth > availableWidth + getPaddingLeft())
- break;
-
- i += 1 / step;
+ final float top;
+ if (waveGravity == WaveGravity.TOP) {
+ top = (float) getPaddingTop();
+ } else if (waveGravity == WaveGravity.CENTER) {
+ top = (float) getPaddingTop() + availableHeight / 2F - waveHeight / 2F;
+ } else if (waveGravity == WaveGravity.BOTTOM) {
+ top = getMeasuredHeight() - (float) getPaddingBottom() - waveHeight;
+ } else {
+ top = 0;
}
+
+ mWaveRect.set(lastWaveRight, top, lastWaveRight + waveWidth, top + waveHeight);
+
+ if (mWaveRect.contains(availableWidth * progress / 100F, mWaveRect.centerY())) {
+ int bitHeight = (int) mWaveRect.height();
+ if (bitHeight <= 0) bitHeight = (int) waveWidth;
+
+ final Bitmap bitmap = Bitmap.createBitmap(availableWidth, bitHeight, Bitmap.Config.ARGB_8888);
+ mProgressCanvas.setBitmap(bitmap);
+
+ float fillWidth = availableWidth * progress / 100F;
+
+ mWavePaint.setColor(waveProgressColor);
+ mProgressCanvas.drawRect(0F, 0F, fillWidth, mWaveRect.bottom, mWavePaint);
+
+ mWavePaint.setColor(waveBackgroundColor);
+ mProgressCanvas.drawRect(fillWidth, 0F, (float) availableWidth, mWaveRect.bottom, mWavePaint);
+
+ mWavePaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
+ } else {
+ mWavePaint.setColor(mWaveRect.right <= availableWidth * progress / 100F ? waveProgressColor : waveBackgroundColor);
+ mWavePaint.setShader(null);
+ }
+
+ canvas.drawRoundRect(mWaveRect, waveCornerRadius, waveCornerRadius, mWavePaint);
+
+ lastWaveRight = mWaveRect.right + waveGap;
+
+ if (lastWaveRight + waveWidth > availableWidth + getPaddingLeft()) {
+ break;
+ }
+ i++;
}
}
@@ -151,12 +155,27 @@ public final class WaveformSeekBar extends View {
}
@Override
- protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- mCanvasWidth = w;
- mCanvasHeight = h;
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (changed) {
+ calculateWaveDimensions();
+ }
}
+ private void calculateWaveDimensions() {
+ if (sample == null || sample.length == 0) return;
+ final int availableWidth = getAvailableWidth();
+ wavesCount = (int) (availableWidth / (waveGap + waveWidth));
+ interpolation = new CubicInterpolation(sample);
+ }
+
+ // @Override
+ // protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) {
+ // super.onSizeChanged(w, h, oldw, oldh);
+ // mCanvasWidth = w;
+ // mCanvasHeight = h;
+ // }
+
@Override
public boolean performClick() {
super.performClick();
@@ -187,26 +206,13 @@ public final class WaveformSeekBar extends View {
}
private int getAvailableWidth() {
- return mCanvasWidth - getPaddingLeft() - getPaddingRight();
+ return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
}
private int getAvailableHeight() {
- return mCanvasHeight - getPaddingTop() - getPaddingBottom();
+ return getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
}
- // public void setSampleFrom(final String path, final boolean ignoreExtension) { // was false
- // try {
- // final SoundParser soundFile = SoundParser.create(path, ignoreExtension);
- // sample = soundFile.frameGains;
- // } catch (final Exception e) {
- // sample = null;
- // }
- // }
- //
- // public void setSampleFrom(@NonNull final File file, final boolean ignoreExtension) { // was false
- // setSampleFrom(file.getAbsolutePath(), ignoreExtension);
- // }
-
public void setProgress(final int progress) {
this.progress = progress;
invalidate();
@@ -216,10 +222,10 @@ public final class WaveformSeekBar extends View {
this.progressChangeListener = progressChangeListener;
}
- public void setSample(final int[] sample) {
- if (sample != this.sample) {
- this.sample = sample;
- invalidate();
- }
+ public void setSample(final float[] sample) {
+ if (sample == this.sample) return;
+ this.sample = sample;
+ calculateWaveDimensions();
+ invalidate();
}
}
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/dialogs/MediaPickerBottomDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/MediaPickerBottomDialogFragment.java
new file mode 100644
index 00000000..d36cb9dd
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/dialogs/MediaPickerBottomDialogFragment.java
@@ -0,0 +1,194 @@
+package awais.instagrabber.dialogs;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.GridLayoutManager;
+
+import com.google.android.material.bottomsheet.BottomSheetDialog;
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import awais.instagrabber.R;
+import awais.instagrabber.adapters.MediaItemsAdapter;
+import awais.instagrabber.databinding.LayoutMediaPickerBinding;
+import awais.instagrabber.utils.MediaController;
+import awais.instagrabber.utils.PermissionUtils;
+import awais.instagrabber.utils.TextUtils;
+import awais.instagrabber.viewmodels.MediaPickerViewModel;
+
+public class MediaPickerBottomDialogFragment extends BottomSheetDialogFragment {
+ private static final String TAG = MediaPickerBottomDialogFragment.class.getSimpleName();
+ private static final int ATTACH_MEDIA_REQUEST_CODE = 100;
+ // private static final int HEIGHT_PIXELS = Utils.displayMetrics.heightPixels;
+
+ private LayoutMediaPickerBinding binding;
+ private MediaPickerViewModel viewModel;
+ private MediaItemsAdapter mediaItemsAdapter;
+ private OnSelectListener onSelectListener;
+ // private int actionBarHeight;
+ // private int statusBarHeight;
+
+ // private final BottomSheetBehavior.BottomSheetCallback bottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() {
+ // @Override
+ // public void onStateChanged(@NonNull final View bottomSheet, final int newState) {
+ //
+ // }
+ //
+ // @Override
+ // public void onSlide(@NonNull final View bottomSheet, final float slideOffset) {
+ // // Log.d(TAG, "onSlide: " + slideOffset);
+ // final float sheetHeight = HEIGHT_PIXELS * slideOffset;
+ // final Context context = getContext();
+ // if (context == null) return;
+ // final float remaining = HEIGHT_PIXELS - sheetHeight - statusBarHeight;
+ // if (remaining <= actionBarHeight) {
+ // final ViewGroup.LayoutParams layoutParams = binding.toolbar.getLayoutParams();
+ // layoutParams.height = (int) (actionBarHeight - remaining);
+ // binding.toolbar.requestLayout();
+ // }
+ // }
+ // };
+
+ public static MediaPickerBottomDialogFragment newInstance() {
+ final Bundle args = new Bundle();
+ final MediaPickerBottomDialogFragment fragment = new MediaPickerBottomDialogFragment();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(@Nullable final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setStyle(DialogFragment.STYLE_NORMAL, R.style.ThemeOverlay_Rounded_BottomSheetDialog);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
+ binding = LayoutMediaPickerBinding.inflate(inflater, container, false);
+ viewModel = new ViewModelProvider(this).get(MediaPickerViewModel.class);
+ // final Context context = getContext();
+ // if (context == null) return binding.getRoot();
+ // actionBarHeight = Utils.getActionBarHeight(context);
+ // statusBarHeight = Utils.getStatusBarHeight(context);
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
+ init();
+ // final Dialog dialog = getDialog();
+ // if (dialog == null) return;
+ // dialog.setOnShowListener(dialog1 -> {
+ // final BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialog;
+ // final View bottomSheetInternal = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
+ // if (bottomSheetInternal == null) return;
+ // final BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheetInternal);
+ // behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
+ // });
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ final Dialog dialog = getDialog();
+ if (dialog == null) return;
+ final BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialog;
+ final View bottomSheetInternal = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
+ if (bottomSheetInternal == null) return;
+ bottomSheetInternal.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
+ bottomSheetInternal.requestLayout();
+ // final BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheetInternal);
+ // behavior.addBottomSheetCallback(bottomSheetCallback);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
+ if (requestCode == ATTACH_MEDIA_REQUEST_CODE) {
+ final Context context = getContext();
+ if (context == null) return;
+ final boolean hasAttachMediaPerms = PermissionUtils.hasAttachMediaPerms(context);
+ if (hasAttachMediaPerms) {
+ viewModel.loadMedia(context);
+ }
+ }
+ }
+
+ private void init() {
+ setupList();
+ setupAlbumPicker();
+ final Context context = getContext();
+ if (context == null) return;
+ if (!PermissionUtils.hasAttachMediaPerms(context)) {
+ PermissionUtils.requestAttachMediaPerms(this, ATTACH_MEDIA_REQUEST_CODE);
+ return;
+ }
+ viewModel.loadMedia(context);
+ }
+
+ private void setupList() {
+ final Context context = getContext();
+ if (context == null) return;
+ binding.mediaList.setLayoutManager(new GridLayoutManager(context, 3));
+ binding.mediaList.setHasFixedSize(true);
+ mediaItemsAdapter = new MediaItemsAdapter(entry -> {
+ if (onSelectListener == null) return;
+ onSelectListener.onSelect(entry);
+ });
+ binding.mediaList.setAdapter(mediaItemsAdapter);
+ }
+
+ private void setupAlbumPicker() {
+ final Context context = getContext();
+ if (context == null) return;
+ final ArrayAdapter albumPickerAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item);
+ albumPickerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ binding.albumPicker.setAdapter(albumPickerAdapter);
+ binding.albumPicker.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(final AdapterView> parent, final View view, final int position, final long id) {
+ final List albumEntries = viewModel.getAllAlbums().getValue();
+ if (albumEntries == null) return;
+ final MediaController.AlbumEntry albumEntry = albumEntries.get(position);
+ mediaItemsAdapter.submitList(albumEntry.photos, () -> binding.mediaList.scrollToPosition(0));
+ }
+
+ @Override
+ public void onNothingSelected(final AdapterView> parent) {
+ mediaItemsAdapter.submitList(Collections.emptyList());
+ }
+ });
+ viewModel.getAllAlbums().observe(getViewLifecycleOwner(), albums -> {
+ albumPickerAdapter.clear();
+ albumPickerAdapter.addAll(albums.stream()
+ .map(albumEntry -> albumEntry.bucketName)
+ .filter(name -> !TextUtils.isEmpty(name))
+ .collect(Collectors.toList()));
+ albumPickerAdapter.notifyDataSetChanged();
+ if (albums.isEmpty()) return;
+ mediaItemsAdapter.submitList(albums.get(0).photos);
+ });
+ }
+
+ public void setOnSelectListener(final OnSelectListener onSelectListener) {
+ this.onSelectListener = onSelectListener;
+ }
+
+ public interface OnSelectListener {
+ void onSelect(MediaController.MediaEntry entry);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java
index 40f04fd3..c0e1e119 100644
--- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java
+++ b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java
@@ -54,7 +54,6 @@ import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -68,7 +67,6 @@ import awais.instagrabber.asyncs.RespondAction;
import awais.instagrabber.asyncs.SeenAction;
import awais.instagrabber.asyncs.VoteAction;
import awais.instagrabber.asyncs.direct_messages.CreateThreadAction;
-import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster;
import awais.instagrabber.customviews.helpers.SwipeGestureListener;
import awais.instagrabber.databinding.FragmentStoryViewerBinding;
import awais.instagrabber.fragments.main.ProfileFragmentDirections;
@@ -177,40 +175,41 @@ public class StoryViewerFragment extends Fragment {
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
final Context context = getContext();
if (context == null) return false;
- switch (item.getItemId()) {
- case R.id.action_download:
- if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED)
- downloadStory();
- else
- ActivityCompat.requestPermissions(requireActivity(), DownloadUtils.PERMS, 8020);
- return true;
- case R.id.action_dms:
- final EditText input = new EditText(context);
- input.setHint(R.string.reply_hint);
- new AlertDialog.Builder(context)
- .setTitle(R.string.reply_story)
- .setView(input)
- .setPositiveButton(R.string.ok, (d, w) -> new CreateThreadAction(cookie, currentStory.getUserId(), threadId -> {
- try {
- final DirectThreadBroadcaster.StoryReplyBroadcastOptions options = new DirectThreadBroadcaster.StoryReplyBroadcastOptions(
- input.getText().toString(),
- currentStory.getStoryMediaId(),
- currentStory.getUserId()
- );
- final DirectThreadBroadcaster broadcast = new DirectThreadBroadcaster(threadId);
- broadcast.setOnTaskCompleteListener(result -> Toast.makeText(
- context,
- result != null ? R.string.answered_story : R.string.downloader_unknown_error,
- Toast.LENGTH_SHORT
- ).show());
- broadcast.execute(options);
- } catch (UnsupportedEncodingException e) {
- Log.e(TAG, "Error", e);
- }
- }).execute())
- .setNegativeButton(R.string.cancel, null)
- .show();
- return true;
+ int itemId = item.getItemId();
+ if (itemId == R.id.action_download) {
+ if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED)
+ downloadStory();
+ else
+ ActivityCompat.requestPermissions(requireActivity(), DownloadUtils.PERMS, 8020);
+ return true;
+ } else if (itemId == R.id.action_dms) {
+ final EditText input = new EditText(context);
+ input.setHint(R.string.reply_hint);
+ new AlertDialog.Builder(context)
+ .setTitle(R.string.reply_story)
+ .setView(input)
+ .setPositiveButton(R.string.ok, (d, w) -> new CreateThreadAction(cookie, currentStory.getUserId(), threadId -> {
+ // try {
+ // final StoryReplyBroadcastOptions options = new StoryReplyBroadcastOptions(
+ // threadId,
+ // input.getText().toString(),
+ // currentStory.getStoryMediaId(),
+ // currentStory.getUserId()
+ // );
+ // final DirectThreadBroadcaster broadcast = new DirectThreadBroadcaster(threadId);
+ // broadcast.setOnTaskCompleteListener(result -> Toast.makeText(
+ // context,
+ // result != null ? R.string.answered_story : R.string.downloader_unknown_error,
+ // Toast.LENGTH_SHORT
+ // ).show());
+ // broadcast.execute(options);
+ // } catch (UnsupportedEncodingException e) {
+ // Log.e(TAG, "Error", e);
+ // }
+ }).execute())
+ .setNegativeButton(R.string.cancel, null)
+ .show();
+ return true;
}
return false;
}
diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java
index 37937d27..39478e56 100644
--- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java
+++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java
@@ -1,7 +1,8 @@
package awais.instagrabber.fragments.directmessages;
import android.content.Context;
-import android.os.AsyncTask;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
@@ -12,78 +13,49 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.lifecycle.MutableLiveData;
-import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
-import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedList;
+import com.google.android.material.badge.BadgeDrawable;
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+
import java.util.List;
-import awais.instagrabber.BuildConfig;
+import awais.instagrabber.R;
+import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.DirectMessageInboxAdapter;
-import awais.instagrabber.asyncs.direct_messages.InboxFetcher;
-import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
+import awais.instagrabber.broadcasts.DMRefreshBroadcastReceiver;
+import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge;
import awais.instagrabber.databinding.FragmentDirectMessagesInboxBinding;
-import awais.instagrabber.interfaces.FetchListener;
-import awais.instagrabber.models.direct_messages.InboxModel;
-import awais.instagrabber.models.direct_messages.InboxThreadModel;
-import awais.instagrabber.utils.TextUtils;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.viewmodels.DirectInboxViewModel;
public class DirectMessageInboxFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "DirectMessagesInboxFrag";
- private FragmentActivity fragmentActivity;
private CoordinatorLayout root;
- private RecyclerView inboxList;
- private RecyclerLazyLoader lazyLoader;
- private LinearLayoutManager layoutManager;
- private String endCursor;
- private AsyncTask currentlyRunning;
- private InboxThreadModelListViewModel listViewModel;
- public static boolean refreshPlease = false;
+ private RecyclerLazyLoaderAtEdge lazyLoader;
+ private DirectInboxViewModel viewModel;
+ // private boolean refreshInbox = false;
private boolean shouldRefresh = true;
-
- private final FetchListener fetchListener = new FetchListener() {
- @Override
- public void doBefore() {
- binding.swipeRefreshLayout.setRefreshing(true);
- }
-
- @Override
- public void onResult(final InboxModel inboxModel) {
- if (inboxModel != null) {
- endCursor = inboxModel.getOldestCursor();
- if ("MINCURSOR".equals(endCursor) || "MAXCURSOR".equals(endCursor))
- endCursor = null;
- // todo get request / unseen count from inboxModel
- final InboxThreadModel[] threads = inboxModel.getThreads();
- if (threads != null && threads.length > 0) {
- List list = listViewModel.getList().getValue();
- list = list != null ? new LinkedList<>(list) : new LinkedList<>();
- // final int oldSize = list != null ? list.size() : 0;
- final List newList = Arrays.asList(threads);
- list.addAll(newList);
- listViewModel.getList().postValue(list);
- }
- }
- binding.swipeRefreshLayout.setRefreshing(false);
- stopCurrentExecutor();
- }
- };
private FragmentDirectMessagesInboxBinding binding;
+ private DMRefreshBroadcastReceiver receiver;
+ private DirectMessageInboxAdapter inboxAdapter;
+ private MainActivity fragmentActivity;
+ private boolean scrollToTop = false;
+ private boolean navigating;
+ private Observer> threadsObserver;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- fragmentActivity = requireActivity();
+ fragmentActivity = (MainActivity) getActivity();
+ if (fragmentActivity != null) {
+ viewModel = new ViewModelProvider(fragmentActivity).get(DirectInboxViewModel.class);
+ }
}
@Override
@@ -96,85 +68,134 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
}
binding = FragmentDirectMessagesInboxBinding.inflate(inflater, container, false);
root = binding.getRoot();
- binding.swipeRefreshLayout.setOnRefreshListener(this);
- inboxList = binding.inboxList;
- inboxList.setHasFixedSize(true);
- final Context context = getContext();
- if (context == null) return root;
- layoutManager = new LinearLayoutManager(context);
- inboxList.setLayoutManager(layoutManager);
- final DirectMessageInboxAdapter inboxAdapter = new DirectMessageInboxAdapter(inboxThreadModel -> {
- final NavDirections action = DirectMessageInboxFragmentDirections
- .actionDMInboxFragmentToDMThreadFragment(inboxThreadModel.getThreadId(), inboxThreadModel.getThreadTitle());
- NavHostFragment.findNavController(this).navigate(action);
- });
- inboxList.setAdapter(inboxAdapter);
- listViewModel = new ViewModelProvider(this).get(InboxThreadModelListViewModel.class);
- listViewModel.getList().observe(fragmentActivity, inboxAdapter::submitList);
return root;
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
- initData();
+ init();
}
@Override
public void onRefresh() {
- endCursor = null;
lazyLoader.resetState();
- listViewModel.getList().postValue(Collections.emptyList());
- stopCurrentExecutor();
- currentlyRunning = new InboxFetcher(null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ scrollToTop = true;
+ if (viewModel != null) {
+ viewModel.refresh();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ unregisterReceiver();
}
@Override
public void onResume() {
super.onResume();
- if (refreshPlease) {
- onRefresh();
- refreshPlease = false;
- }
+ observeViewModel();
+ final Context context = getContext();
+ if (context == null) return;
+ receiver = new DMRefreshBroadcastReceiver(() -> {
+ Log.d(TAG, "onResume: broadcast received");
+ // refreshInbox = true;
+ });
+ context.registerReceiver(receiver, new IntentFilter(DMRefreshBroadcastReceiver.ACTION_REFRESH_DM));
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ unregisterReceiver();
+ }
+
+ private void unregisterReceiver() {
+ if (receiver == null) return;
+ final Context context = getContext();
+ if (context == null) return;
+ context.unregisterReceiver(receiver);
+ receiver = null;
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull final Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ init();
}
@Override
public void onDestroy() {
super.onDestroy();
- if (listViewModel != null) {
- listViewModel.getList().postValue(Collections.emptyList());
- }
+ removeViewModelObservers();
+ viewModel.onDestroy();
}
- private void initData() {
- lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
- if (!TextUtils.isEmpty(endCursor))
- currentlyRunning = new InboxFetcher(endCursor, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- endCursor = null;
+ private void observeViewModel() {
+ threadsObserver = list -> {
+ if (inboxAdapter == null) return;
+ inboxAdapter.submitList(list, () -> {
+ if (!scrollToTop) return;
+ binding.inboxList.smoothScrollToPosition(0);
+ scrollToTop = false;
+ });
+ };
+ viewModel.getThreads().observe(fragmentActivity, threadsObserver);
+ viewModel.getFetchingInbox().observe(getViewLifecycleOwner(), fetching -> binding.swipeRefreshLayout.setRefreshing(fetching));
+ viewModel.getUnseenCount().observe(getViewLifecycleOwner(), this::setBottomNavBarBadge);
+ }
+
+ private void removeViewModelObservers() {
+ if (viewModel == null) return;
+ if (threadsObserver != null) {
+ viewModel.getThreads().removeObserver(threadsObserver);
+ }
+ // no need to explicitly remove observers whose lifecycle owner is getViewLifecycleOwner
+ }
+
+ private void init() {
+ final Context context = getContext();
+ if (context == null) return;
+ observeViewModel();
+ binding.swipeRefreshLayout.setOnRefreshListener(this);
+ binding.inboxList.setHasFixedSize(true);
+ binding.inboxList.setItemViewCacheSize(20);
+ final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
+ binding.inboxList.setLayoutManager(layoutManager);
+ inboxAdapter = new DirectMessageInboxAdapter(thread -> {
+ if (navigating) return;
+ navigating = true;
+ final Bundle bundle = new Bundle();
+ bundle.putString("threadId", thread.getThreadId());
+ bundle.putString("title", thread.getThreadTitle());
+ if (isAdded()) {
+ NavHostFragment.findNavController(this).navigate(R.id.action_inbox_to_thread, bundle);
+ }
+ navigating = false;
});
- inboxList.addOnScrollListener(lazyLoader);
- stopCurrentExecutor();
- currentlyRunning = new InboxFetcher(null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ inboxAdapter.setHasStableIds(true);
+ binding.inboxList.setAdapter(inboxAdapter);
+ lazyLoader = new RecyclerLazyLoaderAtEdge(layoutManager, page -> {
+ if (viewModel == null) return;
+ viewModel.fetchInbox();
+ });
+ binding.inboxList.addOnScrollListener(lazyLoader);
}
- private void stopCurrentExecutor() {
- if (currentlyRunning != null) {
- try {
- currentlyRunning.cancel(true);
- } catch (final Exception e) {
- if (BuildConfig.DEBUG) Log.e(TAG, "", e);
- }
+ private void setBottomNavBarBadge(final int unseenCount) {
+ final BottomNavigationView bottomNavView = fragmentActivity.getBottomNavView();
+ final BadgeDrawable badge = bottomNavView.getOrCreateBadge(R.id.direct_messages_nav_graph);
+ if (badge == null) return;
+ if (unseenCount == 0) {
+ badge.setVisible(false);
+ badge.clearNumber();
+ return;
}
- }
-
- public static class InboxThreadModelListViewModel extends ViewModel {
- private MutableLiveData> list;
-
- public MutableLiveData> getList() {
- if (list == null) {
- list = new MutableLiveData<>();
- }
- return list;
+ if (badge.getVerticalOffset() != 10) {
+ badge.setVerticalOffset(10);
}
+ badge.setNumber(unseenCount);
+ badge.setVisible(true);
}
}
diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java
index b6605ddc..6b6cfadf 100644
--- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java
+++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java
@@ -2,6 +2,7 @@ package awais.instagrabber.fragments.directmessages;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Editable;
@@ -30,19 +31,12 @@ import java.io.DataOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
-import java.util.Arrays;
-import java.util.List;
-import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
-import awais.instagrabber.adapters.DirectMessageMembersAdapter;
-import awais.instagrabber.asyncs.direct_messages.DirectMessageInboxThreadFetcher;
+import awais.instagrabber.broadcasts.DMRefreshBroadcastReceiver;
import awais.instagrabber.databinding.FragmentDirectMessagesSettingsBinding;
-import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.ProfileModel;
-import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.utils.Constants;
-import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.Utils;
public class DirectMessageSettingsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
@@ -56,34 +50,34 @@ public class DirectMessageSettingsFragment extends Fragment implements SwipeRefr
private String threadId;
private String threadTitle;
private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
- private AsyncTask currentlyRunning;
+ // private AsyncTask currentlyRunning;
private View.OnClickListener clickListener;
private View.OnClickListener basicClickListener;
- private final FetchListener fetchListener = new FetchListener() {
- @Override
- public void doBefore() {}
-
- @Override
- public void onResult(final InboxThreadModel threadModel) {
- if (threadModel == null) return;
- final List adminList = Arrays.asList(threadModel.getAdmins());
- final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
- if (userIdFromCookie == null) return;
- final boolean amAdmin = adminList.contains(Long.parseLong(userIdFromCookie));
- final DirectMessageMembersAdapter memberAdapter = new DirectMessageMembersAdapter(threadModel.getUsers(),
- adminList,
- amAdmin ? clickListener : basicClickListener);
- userList.setAdapter(memberAdapter);
- if (threadModel.getLeftUsers() != null && threadModel.getLeftUsers().length > 0) {
- leftTitle.setVisibility(View.VISIBLE);
- final DirectMessageMembersAdapter leftAdapter = new DirectMessageMembersAdapter(threadModel.getLeftUsers(),
- null,
- basicClickListener);
- leftUserList.setAdapter(leftAdapter);
- }
- }
- };
+ // private final FetchListener fetchListener = new FetchListener() {
+ // @Override
+ // public void doBefore() {}
+ //
+ // @Override
+ // public void onResult(final InboxThreadModel threadModel) {
+ // if (threadModel == null) return;
+ // final List adminList = threadModel.getAdmins();
+ // final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
+ // if (userIdFromCookie == null) return;
+ // final boolean amAdmin = adminList.contains(Long.parseLong(userIdFromCookie));
+ // final DirectMessageMembersAdapter memberAdapter = new DirectMessageMembersAdapter(threadModel.getUsers(),
+ // adminList,
+ // amAdmin ? clickListener : basicClickListener);
+ // userList.setAdapter(memberAdapter);
+ // if (threadModel.getLeftUsers() != null && threadModel.getLeftUsers().size() > 0) {
+ // leftTitle.setVisibility(View.VISIBLE);
+ // final DirectMessageMembersAdapter leftAdapter = new DirectMessageMembersAdapter(threadModel.getLeftUsers(),
+ // null,
+ // basicClickListener);
+ // leftUserList.setAdapter(leftAdapter);
+ // }
+ // }
+ // };
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
@@ -190,26 +184,26 @@ public class DirectMessageSettingsFragment extends Fragment implements SwipeRefr
.setNegativeButton(R.string.no, null)
.show());
- currentlyRunning = new DirectMessageInboxThreadFetcher(threadId, null, null, fetchListener).execute();
+ // currentlyRunning = new DirectMessageInboxThreadFetcher(threadId, null, null, fetchListener).execute();
return root;
}
@Override
public void onRefresh() {
stopCurrentExecutor();
- currentlyRunning = new DirectMessageInboxThreadFetcher(threadId, null, null, fetchListener).execute();
+ // currentlyRunning = new DirectMessageInboxThreadFetcher(threadId, null, null, fetchListener).execute();
}
private void stopCurrentExecutor() {
- if (currentlyRunning != null) {
- try {
- currentlyRunning.cancel(true);
- } catch (final Exception e) {
- if (BuildConfig.DEBUG) {
- Log.e(TAG, "", e);
- }
- }
- }
+ // if (currentlyRunning != null) {
+ // try {
+ // currentlyRunning.cancel(true);
+ // } catch (final Exception e) {
+ // if (BuildConfig.DEBUG) {
+ // Log.e(TAG, "", e);
+ // }
+ // }
+ // }
}
class ChangeSettings extends AsyncTask {
@@ -268,7 +262,7 @@ public class DirectMessageSettingsFragment extends Fragment implements SwipeRefr
titleText.clearFocus();
DirectMessageThreadFragment.hasSentSomething = true;
} else if (action.equals("leave")) {
- DirectMessageInboxFragment.refreshPlease = true;
+ context.sendBroadcast(new Intent(DMRefreshBroadcastReceiver.ACTION_REFRESH_DM));
NavHostFragment.findNavController(DirectMessageSettingsFragment.this).popBackStack(R.id.directMessagesInboxFragment, false);
} else {
DirectMessageThreadFragment.hasSentSomething = true;
diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
index 087468cb..b8a7e83f 100644
--- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
+++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
@@ -1,185 +1,209 @@
package awais.instagrabber.fragments.directmessages;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.Handler;
+import android.text.Editable;
import android.util.Log;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.widget.ArrayAdapter;
+import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.MutableLiveData;
-import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
+import androidx.navigation.NavBackStackEntry;
+import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.SimpleItemAnimator;
+import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
+import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
-import org.json.JSONException;
-import org.json.JSONObject;
+import com.google.android.material.snackbar.Snackbar;
+import com.google.common.collect.ImmutableList;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedList;
+import java.io.File;
import java.util.List;
+import java.util.Optional;
import awais.instagrabber.R;
-import awais.instagrabber.adapters.DirectMessageItemsAdapter;
-import awais.instagrabber.asyncs.ImageUploader;
-import awais.instagrabber.asyncs.PostFetcher;
-import awais.instagrabber.asyncs.direct_messages.DirectMessageInboxThreadFetcher;
-import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster;
-import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
+import awais.instagrabber.activities.CameraActivity;
+import awais.instagrabber.activities.MainActivity;
+import awais.instagrabber.adapters.DirectItemsAdapter;
+import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemOrHeader;
+import awais.instagrabber.adapters.viewholder.directmessages.DirectItemViewHolder;
+import awais.instagrabber.animations.CubicBezierInterpolator;
+import awais.instagrabber.customviews.RecordView;
+import awais.instagrabber.customviews.Tooltip;
+import awais.instagrabber.customviews.helpers.HeaderItemDecoration;
+import awais.instagrabber.customviews.helpers.HeightProvider;
+import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge;
+import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding;
-import awais.instagrabber.fragments.PostViewV2Fragment;
-import awais.instagrabber.interfaces.FetchListener;
-import awais.instagrabber.interfaces.MentionClickListener;
-import awais.instagrabber.models.ImageUploadOptions;
+import awais.instagrabber.dialogs.MediaPickerBottomDialogFragment;
import awais.instagrabber.models.ProfileModel;
-import awais.instagrabber.models.direct_messages.DirectItemModel;
-import awais.instagrabber.models.direct_messages.InboxThreadModel;
-import awais.instagrabber.models.enums.DirectItemType;
-import awais.instagrabber.models.enums.MediaItemType;
-import awais.instagrabber.models.enums.UserInboxDirection;
-import awais.instagrabber.utils.Constants;
-import awais.instagrabber.utils.CookieUtils;
-import awais.instagrabber.utils.DownloadUtils;
+import awais.instagrabber.models.Resource;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.utils.AppExecutors;
+import awais.instagrabber.utils.PermissionUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
+import awais.instagrabber.viewmodels.AppStateViewModel;
+import awais.instagrabber.viewmodels.DirectInboxViewModel;
+import awais.instagrabber.viewmodels.DirectThreadViewModel;
+
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
public class DirectMessageThreadFragment extends Fragment {
- private static final String TAG = "DirectMessagesThreadFmt";
- private static final int PICK_IMAGE = 100;
+ private static final String TAG = DirectMessageThreadFragment.class.getSimpleName();
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
+ private static final int AUDIO_RECORD_PERM_REQUEST_CODE = 1000;
+ private static final int CAMERA_REQUEST_CODE = 200;
+ private static final String UPDATING_TITLE = "Updating...";
+ private static final String MESSAGE_LABEL = "Message";
+ private static final String HOLD_TO_RECORD_AUDIO_LABEL = "Press and hold to record audio";
+ private static final String TRANSLATION_Y = "translationY";
- private AppCompatActivity fragmentActivity;
- private String threadId;
- private String threadTitle;
- private String cursor;
- private String lastMessage;
- private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
- private final String myId = CookieUtils.getUserIdFromCookie(cookie);
- private FragmentDirectMessagesThreadBinding binding;
- private DirectItemModelListViewModel listViewModel;
- private DirectItemModel directItemModel;
- private RecyclerView messageList;
- private boolean hasDeletedSomething;
- private boolean hasOlder = true;
+ private DirectItemsAdapter itemsAdapter;
+ private MainActivity fragmentActivity;
+ private DirectThreadViewModel viewModel;
public static boolean hasSentSomething;
-
- private final ProfileModel myProfileHolder = ProfileModel.getDefaultProfileModel();
- private final List users = new ArrayList<>();
- private final List leftUsers = new ArrayList<>();
- private ArrayAdapter dialogAdapter;
-
- private final View.OnClickListener clickListener = v -> {
- if (v == binding.commentSend) {
- final String text = binding.commentText.getText().toString();
- if (TextUtils.isEmpty(text)) {
- final Context context = getContext();
- if (context == null) return;
- Toast.makeText(context, R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show();
- return;
- }
- sendText(text, null, false);
- return;
- }
- if (v == binding.image) {
- final Intent intent = new Intent();
- intent.setType("image/*");
- intent.setAction(Intent.ACTION_GET_CONTENT);
- startActivityForResult(Intent.createChooser(intent, getString(R.string.select_picture)), PICK_IMAGE);
- }
- };
-
- private final FetchListener fetchListener = new FetchListener() {
- @Override
- public void doBefore() {
- binding.swipeRefreshLayout.setRefreshing(true);
- }
-
- @Override
- public void onResult(final InboxThreadModel result) {
- if (result == null && ("MINCURSOR".equals(cursor) || "MAXCURSOR".equals(cursor) || TextUtils.isEmpty(cursor))) {
- final Context context = getContext();
- if (context == null) return;
- Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
- }
-
- if (result != null) {
- cursor = result.getOldestCursor();
- hasOlder = result.hasOlder();
- if ("MINCURSOR".equals(cursor) || "MAXCURSOR".equals(cursor)) {
- cursor = null;
- }
- users.clear();
- users.addAll(Arrays.asList(result.getUsers()));
- leftUsers.clear();
- leftUsers.addAll(Arrays.asList(result.getLeftUsers()));
-
- List list = listViewModel.getList().getValue();
- final List newList = Arrays.asList(result.getItems());
- list = list != null ? new LinkedList<>(list) : new LinkedList<>();
- if (hasSentSomething || hasDeletedSomething) {
- list = newList;
- final Handler handler = new Handler();
- if (hasSentSomething) handler.postDelayed(() -> {
- if (messageList != null) {
- messageList.smoothScrollToPosition(0);
- }
- }, 200);
- hasSentSomething = false;
- hasDeletedSomething = false;
- } else {
- list.addAll(newList);
- }
- listViewModel.getList().postValue(list);
-
- lastMessage = result.getNewestCursor();
-
- if (Utils.settingsHelper.getBoolean(Constants.DM_MARK_AS_SEEN)) new ThreadAction().execute("seen", lastMessage);
- }
- binding.swipeRefreshLayout.setRefreshing(false);
- }
- };
private ConstraintLayout root;
private boolean shouldRefresh = true;
- private DirectItemModel.DirectItemMediaModel downloadMediaItem;
+ private List itemOrHeaders;
+ private MenuItem markAsSeenMenuItem;
+ private FragmentDirectMessagesThreadBinding binding;
+ private Tooltip tooltip;
+ private float initialSendX;
+ private ActionBar actionBar;
+ private AppStateViewModel appStateViewModel;
+ private Runnable prevTitleRunnable;
+ private int originalSoftInputMode;
+ private AnimatorSet animatorSet;
+ private boolean isEmojiPickerShown;
+ private boolean isKbShown;
+ private HeightProvider heightProvider;
+ private boolean isRecording;
+ private boolean wasKbShowing;
+ private int keyboardHeight = Utils.convertDpToPx(250);
+
+ // private final View.OnClickListener clickListener = v -> {
+ // if (v == binding.commentSend) {
+ // final String text = binding.commentText.getText().toString();
+ // if (TextUtils.isEmpty(text)) {
+ // final Context context = getContext();
+ // if (context == null) return;
+ // Toast.makeText(context, R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show();
+ // return;
+ // }
+ // sendText(text, null, false);
+ // return;
+ // }
+ // if (v == binding.image) {
+ // final Intent intent = new Intent();
+ // intent.setType("image/*");
+ // intent.setAction(Intent.ACTION_GET_CONTENT);
+ // startActivityForResult(Intent.createChooser(intent, getString(R.string.select_picture)), PICK_IMAGE);
+ // }
+ // };
+ // private final FetchListener fetchListener = new FetchListener() {
+ // @Override
+ // public void doBefore() {
+ // setTitle(true);g
+ // }
+ //
+ // @Override
+ // public void onResult(final InboxThreadModel result) {
+ // if (result == null && ("MINCURSOR".equals(cursor) || "MAXCURSOR".equals(cursor) || TextUtils.isEmpty(cursor))) {
+ // final Context context = getContext();
+ // if (context == null) return;
+ // Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
+ // }
+ //
+ // if (result != null) {
+ // cursor = result.getOldestCursor();
+ // hasOlder = result.hasOlder();
+ // if ("MINCURSOR".equals(cursor) || "MAXCURSOR".equals(cursor)) {
+ // cursor = null;
+ // }
+ // users.clear();
+ // users.addAll(result.getUsers());
+ // leftUsers.clear();
+ // leftUsers.addAll(result.getLeftUsers());
+ //
+ // List list = listViewModel.getList().getValue();
+ // final List newList = result.getItems();
+ // list = list != null ? new LinkedList<>(list) : new LinkedList<>();
+ // if (hasSentSomething || hasDeletedSomething) {
+ // list = newList;
+ // final Handler handler = new Handler();
+ // if (hasSentSomething) handler.postDelayed(() -> {
+ // binding.messageList.smoothScrollToPosition(0);
+ // }, 200);
+ // hasSentSomething = false;
+ // hasDeletedSomething = false;
+ // } else {
+ // list.addAll(newList);
+ // }
+ // listViewModel.getList().postValue(list);
+ // lastMessage = result.getNewestCursor();
+ //
+ // // if (Utils.settingsHelper.getBoolean(Constants.DM_MARK_AS_SEEN)) new ThreadAction().execute("seen", lastMessage);
+ // }
+ // setTitle(false);
+ // }
+ // };
+ // private DirectItemMediaModel downloadMediaItem;
+ // private int prevSizeChangeHeight;
+ // private ArrayAdapter dialogAdapter;
+ private final AppExecutors appExecutors = AppExecutors.getInstance();
+ private final Animatable2Compat.AnimationCallback micToSendAnimationCallback = new Animatable2Compat.AnimationCallback() {
+ @Override
+ public void onAnimationEnd(final Drawable drawable) {
+ AnimatedVectorDrawableCompat.unregisterAnimationCallback(drawable, this);
+ setSendToMicIcon();
+ }
+ };
+ private final Animatable2Compat.AnimationCallback sendToMicAnimationCallback = new Animatable2Compat.AnimationCallback() {
+ @Override
+ public void onAnimationEnd(final Drawable drawable) {
+ AnimatedVectorDrawableCompat.unregisterAnimationCallback(drawable, this);
+ setMicToSendIcon();
+ }
+ };
+
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- fragmentActivity = (AppCompatActivity) requireActivity();
+ fragmentActivity = (MainActivity) requireActivity();
+ appStateViewModel = new ViewModelProvider(fragmentActivity).get(AppStateViewModel.class);
+ viewModel = new ViewModelProvider(this).get(DirectThreadViewModel.class);
setHasOptionsMenu(true);
}
@@ -192,7 +216,15 @@ public class DirectMessageThreadFragment extends Fragment {
return root;
}
binding = FragmentDirectMessagesThreadBinding.inflate(inflater, container, false);
+ binding.send.setRecordView(binding.recordView);
root = binding.getRoot();
+ final Context context = getContext();
+ if (context == null) {
+ return root;
+ }
+ tooltip = new Tooltip(context, root, getResources().getColor(R.color.grey_400), getResources().getColor(R.color.black));
+ originalSoftInputMode = fragmentActivity.getWindow().getAttributes().softInputMode;
+ // todo check has camera and remove view
return root;
}
@@ -200,225 +232,35 @@ public class DirectMessageThreadFragment extends Fragment {
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
init();
+ binding.send.post(() -> initialSendX = binding.send.getX());
shouldRefresh = false;
}
- private void init() {
- listViewModel = new ViewModelProvider(fragmentActivity).get(DirectItemModelListViewModel.class);
- if (getArguments() == null) return;
- if (!DirectMessageThreadFragmentArgs.fromBundle(getArguments()).getThreadId().equals(threadId)) {
- listViewModel.empty();
- threadId = DirectMessageThreadFragmentArgs.fromBundle(getArguments()).getThreadId();
- }
- threadTitle = DirectMessageThreadFragmentArgs.fromBundle(getArguments()).getTitle();
- final ActionBar actionBar = fragmentActivity.getSupportActionBar();
- if (actionBar != null) {
- actionBar.setTitle(threadTitle);
- }
- binding.swipeRefreshLayout.setEnabled(false);
- messageList = binding.messageList;
- messageList.setHasFixedSize(true);
- binding.commentSend.setOnClickListener(clickListener);
- binding.image.setOnClickListener(clickListener);
- final Context context = getContext();
- if (context == null) return;
- final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
- layoutManager.setReverseLayout(true);
- messageList.setLayoutManager(layoutManager);
- messageList.addOnScrollListener(new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
- if (TextUtils.isEmpty(cursor) || !hasOlder) {
- return;
- }
- new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, cursor, fetchListener)
- .execute(); // serial because we don't want messages to be randomly ordered
- }));
- final DialogInterface.OnClickListener onDialogListener = (dialogInterface, which) -> {
- if (which == 0) {
- final DirectItemType itemType = directItemModel.getItemType();
- switch (itemType) {
- case MEDIA_SHARE:
- case CLIP:
- case FELIX_SHARE:
- final String shortCode = directItemModel.getMediaModel().getCode();
- new PostFetcher(shortCode, feedModel -> {
- final PostViewV2Fragment fragment = PostViewV2Fragment
- .builder(feedModel)
- .build();
- fragment.show(getChildFragmentManager(), "post_view");
- }).execute();
- break;
- case LINK:
- Intent linkIntent = new Intent(Intent.ACTION_VIEW);
- linkIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
- linkIntent.setData(Uri.parse(directItemModel.getLinkModel().getLinkContext().getLinkUrl()));
- startActivity(linkIntent);
- break;
- case TEXT:
- case REEL_SHARE:
- Utils.copyText(context, directItemModel.getText());
- Toast.makeText(context, R.string.clipboard_copied, Toast.LENGTH_SHORT).show();
- break;
- case RAVEN_MEDIA:
- case MEDIA:
- downloadItem(context);
- break;
- case STORY_SHARE:
- if (directItemModel.getReelShare() != null) {
- // StoryModel sm = new StoryModel(
- // directItemModel.getReelShare().getReelId(),
- // directItemModel.getReelShare().getMedia().getVideoUrl(),
- // directItemModel.getReelShare().getMedia().getMediaType(),
- // directItemModel.getTimestamp(),
- // directItemModel.getReelShare().getReelOwnerName(),
- // String.valueOf(directItemModel.getReelShare().getReelOwnerId()),
- // false
- // );
- // sm.setVideoUrl(directItemModel.getReelShare().getMedia().getVideoUrl());
- // StoryModel[] sms = {sm};
- // startActivity(new Intent(getContext(), StoryViewer.class)
- // .putExtra(Constants.EXTRAS_USERNAME, directItemModel.getReelShare().getReelOwnerName())
- // .putExtra(Constants.EXTRAS_STORIES, sms)
- // );
- } else if (directItemModel.getText() != null && directItemModel.getText().toString().contains("@")) {
- searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]);
- }
- break;
- case PLACEHOLDER:
- if (directItemModel.getText().toString().contains("@"))
- searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]);
- break;
- default:
- Log.d(TAG, "unsupported type " + itemType);
- }
- } else if (which == 1) {
- sendText(null, directItemModel.getItemId(), directItemModel.isLiked());
- } else if (which == 2) {
- if (directItemModel == null) {
- Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
- } else if (String.valueOf(directItemModel.getUserId()).equals(myId)) {
- new ThreadAction().execute("delete", directItemModel.getItemId());
- } else {
- searchUsername(getUser(directItemModel.getUserId()).getUsername());
- }
- }
- };
- final View.OnClickListener onClickListener = v -> {
- Object tag = v.getTag();
- if (tag instanceof ProfileModel) {
- searchUsername(((ProfileModel) tag).getUsername());
- } else if (tag instanceof DirectItemModel) {
- directItemModel = (DirectItemModel) tag;
- final DirectItemType itemType = directItemModel.getItemType();
- int firstOption = R.string.dms_inbox_raven_message_unknown;
- String[] dialogList;
-
- switch (itemType) {
- case MEDIA_SHARE:
- case CLIP:
- case FELIX_SHARE:
- firstOption = R.string.view_post;
- break;
- case LINK:
- firstOption = R.string.dms_inbox_open_link;
- break;
- case TEXT:
- case REEL_SHARE:
- firstOption = R.string.dms_inbox_copy_text;
- break;
- case RAVEN_MEDIA:
- case MEDIA:
- firstOption = R.string.dms_inbox_download;
- break;
- case STORY_SHARE:
- if (directItemModel.getReelShare() != null) {
- firstOption = R.string.show_stories;
- } else if (directItemModel.getText() != null && directItemModel.getText().toString().contains("@")) {
- firstOption = R.string.open_profile;
- }
- break;
- case PLACEHOLDER:
- if (directItemModel.getText().toString().contains("@"))
- firstOption = R.string.open_profile;
- break;
- }
-
- dialogList = new String[]{
- getString(firstOption),
- getString(directItemModel.isLiked() ? R.string.dms_inbox_unlike : R.string.dms_inbox_like),
- getString(String.valueOf(directItemModel.getUserId()).equals(myId) ? R.string.dms_inbox_unsend : R.string.dms_inbox_author)
- };
-
- dialogAdapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, dialogList);
-
- new AlertDialog.Builder(context)
- .setAdapter(dialogAdapter, onDialogListener)
- .show();
- }
- };
- final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> searchUsername(text);
- final DirectMessageItemsAdapter adapter = new DirectMessageItemsAdapter(users, leftUsers, onClickListener, mentionClickListener);
- messageList.setAdapter(adapter);
- listViewModel.getList().observe(fragmentActivity, adapter::submitList);
- if (listViewModel.isEmpty()) {
- new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, null, fetchListener)
- .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
- }
-
- private void downloadItem(final Context context) {
- final ProfileModel user = getUser(directItemModel.getUserId());
- final DirectItemModel.DirectItemMediaModel selectedItem = directItemModel.getItemType() == DirectItemType.MEDIA
- ? directItemModel.getMediaModel()
- : directItemModel.getRavenMediaModel().getMedia();
- final String url = selectedItem.getMediaType() == MediaItemType.MEDIA_TYPE_VIDEO
- ? selectedItem.getVideoUrl()
- : selectedItem.getThumbUrl();
- if (url == null) {
- Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
- } else {
- if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
- DownloadUtils.dmDownload(context, user.getUsername(), selectedItem.getId(), url);
- } else {
- requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
- }
- Toast.makeText(context, R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show();
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (hasSentSomething) {
- new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, null, fetchListener)
- .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
- final ActionBar actionBar = fragmentActivity.getSupportActionBar();
- if (actionBar != null) {
- actionBar.setTitle(threadTitle);
- }
- }
-
- @Override
- public void onPrepareOptionsMenu(@NonNull final Menu menu) {
- }
-
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.dm_thread_menu, menu);
+ markAsSeenMenuItem = menu.findItem(R.id.mark_as_seen);
+ if (markAsSeenMenuItem != null) {
+ markAsSeenMenuItem.setVisible(false);
+ }
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
final int itemId = item.getItemId();
- switch (itemId) {
- case R.id.info:
- final NavDirections action = DirectMessageThreadFragmentDirections.actionDMThreadFragmentToDMSettingsFragment(threadId, threadTitle);
- NavHostFragment.findNavController(this).navigate(action);
- return true;
- case R.id.mark_as_seen:
- new ThreadAction().execute("seen", lastMessage);
- item.setVisible(false);
- return true;
+ if (itemId == R.id.info) {
+ // final NavDirections action = DirectMessageThreadFragmentDirections.actionDMThreadFragmentToDMSettingsFragment(threadId, threadTitle);
+ // NavHostFragment.findNavController(this).navigate(action);
+ return true;
+ }
+ if (itemId == R.id.mark_as_seen) {
+ // new ThreadAction().execute("seen", lastMessage);
+ item.setVisible(false);
+ return true;
+ }
+ if (itemId == R.id.refresh && viewModel != null) {
+ viewModel.refreshChats();
+ return true;
}
return super.onOptionsItemSelected(item);
}
@@ -426,200 +268,816 @@ public class DirectMessageThreadFragment extends Fragment {
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
- if (requestCode == PICK_IMAGE && resultCode == Activity.RESULT_OK) {
+ if (requestCode == CAMERA_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (data == null || data.getData() == null) {
Log.w(TAG, "data is null!");
return;
}
final Uri uri = data.getData();
- sendImage(uri);
+ navigateToImageEditFragment(uri);
}
}
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (listViewModel != null) listViewModel.getList().postValue(Collections.emptyList());
- }
-
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
final Context context = getContext();
if (context == null) return;
- if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) {
- downloadItem(context);
+ if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // downloadItem(context);
+ }
+ if (requestCode == AUDIO_RECORD_PERM_REQUEST_CODE) {
+ if (PermissionUtils.hasAudioRecordPerms(context)) {
+ Toast.makeText(context, "You can send voice messages now!", Toast.LENGTH_LONG).show();
+ return;
+ }
+ Toast.makeText(context, "Require RECORD_AUDIO and WRITE_EXTERNAL_STORAGE permissions", Toast.LENGTH_LONG).show();
}
}
- private void sendText(final String text, final String itemId, final boolean delete) {
- DirectThreadBroadcaster.TextBroadcastOptions textOptions = null;
- DirectThreadBroadcaster.ReactionBroadcastOptions reactionOptions = null;
- if (text != null) {
- try {
- textOptions = new DirectThreadBroadcaster.TextBroadcastOptions(text);
- } catch (UnsupportedEncodingException e) {
- Log.e(TAG, "Error", e);
- return;
- }
- } else {
- reactionOptions = new DirectThreadBroadcaster.ReactionBroadcastOptions(itemId, delete);
+ @Override
+ public void onPause() {
+ if (isRecording) {
+ binding.recordView.cancelRecording(binding.send);
}
- broadcast(text != null ? textOptions : reactionOptions, result -> {
- if (result == null || result.getResponseCode() != HttpURLConnection.HTTP_OK) {
- final Context context = getContext();
- if (context == null) return;
- Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
+ if (isKbShown) {
+ wasKbShowing = true;
+ binding.emojiPicker.setAlpha(0);
+ }
+ super.onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ fragmentActivity.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_NOTHING | SOFT_INPUT_STATE_HIDDEN);
+ if (wasKbShowing) {
+ appExecutors.mainThread().execute(() -> {
+ binding.input.requestFocus();
+ binding.input.post(this::showKeyboard);
+ });
+ wasKbShowing = false;
+ }
+ setObservers();
+ if (initialSendX != 0) {
+ binding.send.setX(initialSendX);
+ }
+ binding.send.stopScale();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ cleanup();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ cleanup();
+ }
+
+ private void cleanup() {
+ if (prevTitleRunnable != null) {
+ appExecutors.mainThread().cancel(prevTitleRunnable);
+ }
+ if (heightProvider != null) {
+ // need to close the height provider popup before navigating back to prevent leak
+ heightProvider.dismiss();
+ }
+ if (originalSoftInputMode != 0) {
+ fragmentActivity.getWindow().setSoftInputMode(originalSoftInputMode);
+ }
+ for (int childCount = binding.chats.getChildCount(), i = 0; i < childCount; ++i) {
+ final RecyclerView.ViewHolder holder = binding.chats.getChildViewHolder(binding.chats.getChildAt(i));
+ if (holder == null) continue;
+ if (holder instanceof DirectItemViewHolder) {
+ ((DirectItemViewHolder) holder).cleanup();
+ }
+ }
+ }
+
+ private void init() {
+ final Context context = getContext();
+ if (context == null) return;
+ if (getArguments() == null) return;
+ actionBar = fragmentActivity.getSupportActionBar();
+ setObservers();
+ final DirectMessageThreadFragmentArgs fragmentArgs = DirectMessageThreadFragmentArgs.fromBundle(getArguments());
+ viewModel.getThreadTitle().postValue(fragmentArgs.getTitle());
+ final String threadId = fragmentArgs.getThreadId();
+ viewModel.setThreadId(threadId);
+ setupList();
+ root.post(this::setupInput);
+ }
+
+ private void getInitialData() {
+ final DirectInboxViewModel threadListViewModel = new ViewModelProvider(fragmentActivity).get(DirectInboxViewModel.class);
+ final List threads = threadListViewModel.getThreads().getValue();
+ final Optional first = threads != null
+ ? threads.stream()
+ .filter(thread -> thread.getThreadId().equals(viewModel.getThreadId()))
+ .findFirst()
+ : Optional.empty();
+ if (first.isPresent()) {
+ // setTitle(UPDATING_TITLE);
+ final DirectThread thread = first.get();
+ viewModel.setThread(thread);
+ if (itemsAdapter != null) {
+ itemsAdapter.setThread(thread);
+ }
+ return;
+ }
+ viewModel.fetchChats();
+ }
+
+ private void setupList() {
+ final Context context = getContext();
+ if (context == null) return;
+ binding.chats.setItemViewCacheSize(20);
+ final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
+ layoutManager.setReverseLayout(true);
+ // layoutManager.setStackFromEnd(false);
+ // binding.messageList.addItemDecoration(new VerticalSpaceItemDecoration(3));
+ final RecyclerView.ItemAnimator animator = binding.chats.getItemAnimator();
+ if (animator instanceof SimpleItemAnimator) {
+ final SimpleItemAnimator itemAnimator = (SimpleItemAnimator) animator;
+ itemAnimator.setSupportsChangeAnimations(false);
+ }
+ binding.chats.setLayoutManager(layoutManager);
+ binding.chats.addOnScrollListener(new RecyclerLazyLoaderAtEdge(layoutManager, true, page -> viewModel.fetchChats()));
+ final HeaderItemDecoration headerItemDecoration = new HeaderItemDecoration(binding.chats, itemPosition -> {
+ if (itemOrHeaders == null || itemOrHeaders.isEmpty()) return false;
+ try {
+ final DirectItemOrHeader itemOrHeader = itemOrHeaders.get(itemPosition);
+ return itemOrHeader.isHeader();
+ } catch (IndexOutOfBoundsException e) {
+ return false;
+ }
+ });
+ binding.chats.addItemDecoration(headerItemDecoration);
+ // final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> searchUsername(text);
+ // final DialogInterface.OnClickListener onDialogListener = (dialogInterface, which) -> {
+ // if (which == 0) {
+ // final DirectItemType itemType = directItemModel.getItemType();
+ // switch (itemType) {
+ // case MEDIA_SHARE:
+ // case CLIP:
+ // case FELIX_SHARE:
+ // final String shortCode = ((DirectItemMediaModel) directItemModel.getMediaModel()).getCode();
+ // new PostFetcher(shortCode, feedModel -> {
+ // final PostViewV2Fragment fragment = PostViewV2Fragment
+ // .builder(feedModel)
+ // .build();
+ // fragment.show(getChildFragmentManager(), "post_view");
+ // }).execute();
+ // break;
+ // case LINK:
+ // Intent linkIntent = new Intent(Intent.ACTION_VIEW);
+ // linkIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ // linkIntent.setData(Uri.parse(((DirectItemLinkModel) directItemModel.getMediaModel()).getLinkContext().getLinkUrl()));
+ // startActivity(linkIntent);
+ // break;
+ // case TEXT:
+ // case REEL_SHARE:
+ // Utils.copyText(context, directItemModel.getText());
+ // Toast.makeText(context, R.string.clipboard_copied, Toast.LENGTH_SHORT).show();
+ // break;
+ // case RAVEN_MEDIA:
+ // case MEDIA:
+ // downloadItem(context);
+ // break;
+ // case STORY_SHARE:
+ // if (directItemModel.getMediaModel() != null) {
+ // // StoryModel sm = new StoryModel(
+ // // directItemModel.getReelShare().getReelId(),
+ // // directItemModel.getReelShare().getMedia().getVideoUrl(),
+ // // directItemModel.getReelShare().getMedia().getMediaType(),
+ // // directItemModel.getTimestamp(),
+ // // directItemModel.getReelShare().getReelOwnerName(),
+ // // String.valueOf(directItemModel.getReelShare().getReelOwnerId()),
+ // // false
+ // // );
+ // // sm.setVideoUrl(directItemModel.getReelShare().getMedia().getVideoUrl());
+ // // StoryModel[] sms = {sm};
+ // // startActivity(new Intent(getContext(), StoryViewer.class)
+ // // .putExtra(Constants.EXTRAS_USERNAME, directItemModel.getReelShare().getReelOwnerName())
+ // // .putExtra(Constants.EXTRAS_STORIES, sms)
+ // // );
+ // } else if (directItemModel.getText() != null && directItemModel.getText().toString().contains("@")) {
+ // searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]);
+ // }
+ // break;
+ // case PLACEHOLDER:
+ // if (directItemModel.getText().toString().contains("@"))
+ // searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]);
+ // break;
+ // default:
+ // Log.d(TAG, "unsupported type " + itemType);
+ // }
+ // } else if (which == 1) {
+ // sendText(null, directItemModel.getItemId(), directItemModel.isLiked());
+ // } else if (which == 2) {
+ // if (directItemModel == null) {
+ // Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
+ // } else if (String.valueOf(directItemModel.getUserId()).equals(myId)) {
+ // new ThreadAction().execute("delete", directItemModel.getItemId());
+ // } else {
+ // searchUsername(getUser(directItemModel.getUserId()).getUsername());
+ // }
+ // }
+ // };
+ // final View.OnClickListener onClickListener = v -> {
+ // Object tag = v.getTag();
+ // if (tag instanceof ProfileModel) {
+ // searchUsername(((ProfileModel) tag).getUsername());
+ // } else if (tag instanceof DirectItemModel) {
+ // directItemModel = (DirectItemModel) tag;
+ // final DirectItemType itemType = directItemModel.getItemType();
+ // int firstOption = R.string.dms_inbox_raven_message_unknown;
+ // String[] dialogList;
+ //
+ // switch (itemType) {
+ // case MEDIA_SHARE:
+ // case CLIP:
+ // case FELIX_SHARE:
+ // firstOption = R.string.view_post;
+ // break;
+ // case LINK:
+ // firstOption = R.string.dms_inbox_open_link;
+ // break;
+ // case TEXT:
+ // case REEL_SHARE:
+ // firstOption = R.string.dms_inbox_copy_text;
+ // break;
+ // case RAVEN_MEDIA:
+ // case MEDIA:
+ // firstOption = R.string.dms_inbox_download;
+ // break;
+ // case STORY_SHARE:
+ // if (directItemModel.getMediaModel() != null) {
+ // firstOption = R.string.show_stories;
+ // } else if (directItemModel.getText() != null && directItemModel.getText().toString().contains("@")) {
+ // firstOption = R.string.open_profile;
+ // }
+ // break;
+ // case PLACEHOLDER:
+ // if (directItemModel.getText().toString().contains("@"))
+ // firstOption = R.string.open_profile;
+ // break;
+ // }
+ //
+ // dialogList = new String[]{
+ // getString(firstOption),
+ // getString(directItemModel.isLiked() ? R.string.dms_inbox_unlike : R.string.dms_inbox_like),
+ // getString(String.valueOf(directItemModel.getUserId()).equals(myId) ? R.string.dms_inbox_unsend : R.string.dms_inbox_author)
+ // };
+ //
+ // dialogAdapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, dialogList);
+ //
+ // new AlertDialog.Builder(context)
+ // .setAdapter(dialogAdapter, onDialogListener)
+ // .show();
+ // }
+ // };
+ }
+
+ private void setObservers() {
+ viewModel.getThreadTitle().observe(getViewLifecycleOwner(), this::setTitle);
+ viewModel.getFetching().observe(getViewLifecycleOwner(), fetching -> {
+ if (fetching) {
+ setTitle(UPDATING_TITLE);
return;
}
- if (text != null) {
- binding.commentText.setText("");
- } else {
- final View viewWithTag = binding.messageList.findViewWithTag(directItemModel);
- if (viewWithTag != null) {
- final ViewParent dim = viewWithTag.getParent();
- if (dim instanceof View) {
- final View dimView = (View) dim;
- final View likedContainer = dimView.findViewById(R.id.liked_container);
- if (likedContainer != null) {
- likedContainer.setVisibility(delete ? View.GONE : View.VISIBLE);
- }
- }
+ setTitle(viewModel.getThreadTitle().getValue());
+ });
+ viewModel.getThread().observe(getViewLifecycleOwner(), thread -> {
+ if (thread != null && itemsAdapter != null) itemsAdapter.setThread(thread);
+ });
+ appStateViewModel.getCurrentUser().observe(getViewLifecycleOwner(), currentUser -> {
+ viewModel.setCurrentUser(currentUser);
+ setupItemsAdapter(currentUser);
+ viewModel.getItems().observe(getViewLifecycleOwner(),
+ list -> itemsAdapter.submitList(list, () -> itemOrHeaders = itemsAdapter.getList()));
+ });
+ final NavController navController = NavHostFragment.findNavController(this);
+ final NavBackStackEntry backStackEntry = navController.getCurrentBackStackEntry();
+ if (backStackEntry != null) {
+ final MutableLiveData