1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-11-24 15:47:30 +00:00

Update DM module. (WIP)

Remove left over images


DM update (WIP)

Update DM module. (WIP)


Update DM module. (WIP)
This commit is contained in:
Ammar Githam 2021-01-02 11:54:32 +09:00
parent 6a5c2171c6
commit 343b2cf9d7
318 changed files with 25129 additions and 4517 deletions

View File

@ -36,5 +36,10 @@
<option name="name" value="maven" />
<option name="url" value="http://maven.geotoolkit.org/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://www.jitpack.io" />
</remote-repository>
</component>
</project>

View File

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

View File

@ -23,6 +23,7 @@
#noinspection ShrinkerUnresolvedReference
#-keep class !com.google.android.exoplayer2.**, ** { *; }
#-keep class !awais.instagrabber.** { *; }
-dontobfuscate
# prevent shrinking retrofit response entities
-keep class awais.instagrabber.repositories.responses.** { *; }

View File

@ -6,6 +6,13 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature
android:name="android.hardware.camera.any"
android:required="false" />
<application
android:name=".InstaGrabberApplication"
@ -21,7 +28,7 @@
android:name=".activities.MainActivity"
android:launchMode="singleTop"
android:taskAffinity=".Main"
android:windowSoftInputMode="adjustPan">
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
@ -114,6 +121,15 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.MainActivity" />
</activity>
<activity
android:name=".activities.CameraActivity"
android:label="Camera"
android:parentActivityName=".activities.MainActivity"
android:theme="@style/AppTheme.Light.White">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.MainActivity" />
</activity>
<provider
android:name="androidx.core.content.FileProvider"
@ -131,5 +147,13 @@
<service
android:name=".services.DeleteImageIntentService"
android:exported="false" />
<uses-library
android:name="org.apache.http.legacy"
android:required="false" />
<meta-data
android:name="fontProviderRequests"
android:value="Noto Color Emoji Compat" />
</application>
</manifest>

View File

@ -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());
if (TextUtils.isEmpty(settingsHelper.getString(Constants.DEVICE_UUID))) {
settingsHelper.putString(Constants.DEVICE_UUID, UUID.randomUUID().toString());
}
}
}

View File

@ -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<ProcessCameraProvider> 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);
}
}

View File

@ -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);
}
}

View File

@ -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<RecyclerView.ViewHolder> {
private static final String TAG = DirectItemsAdapter.class.getSimpleName();
private final ProfileModel currentUser;
private DirectThread thread;
private final AsyncListDiffer<DirectItemOrHeader> differ;
private static final DiffUtil.ItemCallback<DirectItemOrHeader> diffCallback = new DiffUtil.ItemCallback<DirectItemOrHeader>() {
@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<DirectItem> list) {
if (list == null) {
differ.submitList(null);
return;
}
differ.submitList(sectionAndSort(list));
}
public void submitList(@Nullable final List<DirectItem> list, @Nullable final Runnable commitCallback) {
if (list == null) {
differ.submitList(null, commitCallback);
return;
}
differ.submitList(sectionAndSort(list), commitCallback);
}
private List<DirectItemOrHeader> sectionAndSort(final List<DirectItem> list) {
final List<DirectItemOrHeader> 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<DirectItemOrHeader> 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));
}
}
}

View File

@ -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<InboxThreadModel, DirectMessageInboxItemViewHolder> {
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<DirectThread, DirectInboxItemViewHolder> {
private final OnItemClickListener onClickListener;
private static final DiffUtil.ItemCallback<InboxThreadModel> diffCallback = new DiffUtil.ItemCallback<InboxThreadModel>() {
private static final DiffUtil.ItemCallback<DirectThread> diffCallback = new DiffUtil.ItemCallback<DirectThread>() {
@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<DirectItem> oldItems = oldThread.getItems();
final List<DirectItem> 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));
public void onBindViewHolder(@NonNull final DirectInboxItemViewHolder holder, final int position) {
final DirectThread thread = getItem(position);
holder.bind(thread);
}
holder.bind(threadModel);
@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);
}
}

View File

@ -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<DirectItemModel, DirectMessageItemViewHolder> {
private final List<ProfileModel> users;
private final List<ProfileModel> leftUsers;
private final View.OnClickListener onClickListener;
private final MentionClickListener mentionClickListener;
private static final DiffUtil.ItemCallback<DirectItemModel> diffCallback = new DiffUtil.ItemCallback<DirectItemModel>() {
@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<ProfileModel> users,
final List<ProfileModel> 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();
}
}

View File

@ -14,11 +14,11 @@ import awais.instagrabber.databinding.ItemFollowBinding;
import awais.instagrabber.models.ProfileModel;
public final class DirectMessageMembersAdapter extends RecyclerView.Adapter<FollowsViewHolder> {
private final ProfileModel[] profileModels;
private final List<ProfileModel> profileModels;
private final List<Long> admins;
private final View.OnClickListener onClickListener;
public DirectMessageMembersAdapter(final ProfileModel[] profileModels,
public DirectMessageMembersAdapter(final List<ProfileModel> profileModels,
final List<Long> admins,
final View.OnClickListener onClickListener) {
this.profileModels = profileModels;
@ -36,12 +36,12 @@ public final class DirectMessageMembersAdapter extends RecyclerView.Adapter<Foll
@Override
public void onBindViewHolder(@NonNull final FollowsViewHolder holder, final int position) {
final ProfileModel model = profileModels[position];
final ProfileModel model = profileModels.get(position);
holder.bind(model, admins, onClickListener);
}
@Override
public int getItemCount() {
return profileModels.length;
return profileModels.size();
}
}

View File

@ -0,0 +1,94 @@
package awais.instagrabber.adapters;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import java.util.Collection;
import java.util.List;
import awais.instagrabber.adapters.viewholder.FilterViewHolder;
import awais.instagrabber.databinding.ItemFilterBinding;
import awais.instagrabber.fragments.imageedit.filters.filters.Filter;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter;
public class FiltersAdapter extends ListAdapter<Filter<?>, FilterViewHolder> {
private static final DiffUtil.ItemCallback<Filter<?>> DIFF_CALLBACK = new DiffUtil.ItemCallback<Filter<?>>() {
@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<GPUImageFilter> filters;
private final String originalKey;
private int selectedPosition = 0;
public FiltersAdapter(final Collection<GPUImageFilter> 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<Filter<?>> 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);
}
}

View File

@ -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<MediaEntry, MediaItemsAdapter.MediaItemViewHolder> {
private static final DiffUtil.ItemCallback<MediaEntry> diffCallback = new DiffUtil.ItemCallback<MediaEntry>() {
@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<ImageInfo> controllerListener = new BaseControllerListener<ImageInfo>() {
@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);
}
}

View File

@ -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<PostMediaViewHolder> {
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;
}
}
// 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<PostMediaViewHolder> {
// 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;
// }
// }

View File

@ -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<SimpleDraweeView> 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<DirectItem> 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<DirectUser> 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<DirectUser> users,
final long userId,
final long viewerId) {
if (userId == viewerId) {
return "You";
}
final Optional<DirectUser> 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<Long, DirectThreadLastSeenAt> 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);
}
}

View File

@ -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 = "<i>Unsupported message</i>";
}
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);
}
}

View File

@ -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<GPUImageFilter> tuneFilters;
private final FiltersAdapter.OnFilterClickListener onFilterClickListener;
private final AppExecutors appExecutors;
public FilterViewHolder(@NonNull final ItemFilterBinding binding,
final Collection<GPUImageFilter> 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.<GPUImageFilter>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));
}
}

View File

@ -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());
// }
// }

View File

@ -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<DirectItemActionLog.TextRange> 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<DirectItemActionLog.TextRange> 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);
}
}

View File

@ -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<Integer, Integer> 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());
}
}

View File

@ -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));
}
}

View File

@ -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();
}
}

View File

@ -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());
}
}

View File

@ -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<Integer, Integer> 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);
}
}

View File

@ -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<Integer, Integer> 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);
}
}

View File

@ -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);
}
}

View File

@ -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<SimpleDraweeView> 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<DirectItemMedia> 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);
}
}

View File

@ -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<Integer, Integer> 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<Integer, Integer> 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);
}
}

View File

@ -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);
}
}

View File

@ -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<Integer, Integer> 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);
}
}

View File

@ -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);
}
}

View File

@ -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<DirectItemActionLog.TextRange> 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);
}
}

View File

@ -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<Long> 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<DirectUser> 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<DirectUser> 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<DirectUser> users) {
final DirectItemReactions reactions = item.getReactions();
final List<DirectItemEmojiReaction> 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<DirectUser> 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<DirectUser> 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
}
}

View File

@ -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<Float> 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;
}
}
}

View File

@ -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("<small>" + text + "</small>", FROM_HTML_MODE_COMPACT));
binding.tvMessage.setVisibility(View.VISIBLE);
}
}

View File

@ -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<Integer, Integer> 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());
}
}

View File

@ -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));
}
}

View File

@ -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<ProfileModel> users, final List<ProfileModel> 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<ProfileModel> users, final List<ProfileModel> 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));
}
}

View File

@ -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()));
}
}

View File

@ -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(
"<small>" + context.getString(R.string.dms_inbox_media_shared_from, modelUser.getUsername()) + "</small>",
FROM_HTML_MODE_COMPACT));
}
final Pair<Integer, Integer> 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);
}
}

View File

@ -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<Integer, Integer> 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);
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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<Integer, Integer> 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);
}
}
}

View File

@ -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<Integer, Integer> 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());
}
}
}

View File

@ -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<Integer, Integer> 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());
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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() {

View File

@ -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));
}
}

View File

@ -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();
}
}

View File

@ -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<ImageUploadOptions, Void, ImageUploader.ImageUploadResponse> {
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<String, String> 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<String, String> createPhotoRuploadParams(final ImageUploadOptions options, final String uploadId) {
final Map<String, Integer> 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<String, String> 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<String, String> 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;
}
}
}

View File

@ -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<Void, Void, InboxThreadModel> {
private static final String TAG = "DMInboxThreadFetcher";
private final String id;
private final String endCursor;
private final FetchListener<InboxThreadModel> fetchListener;
private final UserInboxDirection direction;
public DirectMessageInboxThreadFetcher(final String id,
final UserInboxDirection direction,
final String cursor,
final FetchListener<InboxThreadModel> 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<String, String> 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);
}
}

View File

@ -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<DirectThreadBroadcaster.BroadcastOptions, Void, DirectThreadBroadcaster.DirectThreadBroadcastResponse> {
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<String, String> 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<String, String> 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<String, String> 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<String, String> getFormMap() {
final Map<String, String> 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<String, String> getFormMap() {
final Map<String, String> 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<String, String> getFormMap() {
final Map<String, String> 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<BroadcastOptions, Void, DirectThreadBroadcastResponse> {
// 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<String, String> 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);
// }
// }

View File

@ -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<Void, Void, InboxModel> {
private static final String TAG = "InboxFetcher";
private final String endCursor;
private final FetchListener<InboxModel> fetchListener;
public InboxFetcher(final String endCursor, final FetchListener<InboxModel> 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);
}
}

View File

@ -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();
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

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

View File

@ -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<Emoji> 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<Emoji> 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 + '\'' +
'}';
}
}

View File

@ -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<String, Emoji> 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<String, Emoji> 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);
}
}

View File

@ -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);
}
}

View File

@ -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<String, EmojiCategoryType> 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;
}
}

View File

@ -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<EmojiGridAdapter.EmojiViewHolder> {
private static final String TAG = EmojiGridAdapter.class.getSimpleName();
private static final DiffUtil.ItemCallback<Emoji> diffCallback = new DiffUtil.ItemCallback<Emoji>() {
@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<Emoji> 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<EmojiCategoryType, EmojiCategory> 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<Emoji> 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);
}
}

View File

@ -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<EmojiCategory> 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<Emoji> 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();
}
}

View File

@ -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<EmojiCategoryPageViewHolder> {
private static final DiffUtil.ItemCallback<EmojiCategory> diffCallback = new DiffUtil.ItemCallback<EmojiCategory>() {
@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<EmojiCategory> 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();
}
}

View File

@ -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();
}
}

View File

@ -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<String, String> 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<String> 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);
});
}
}

View File

@ -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<Emoji> 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);
}
});
}
}

View File

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

View File

@ -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 <a href="https://gist.github.com/filipkowicz/1a769001fae407b8813ab4387c42fcbd/3cda7542b12100b01da449e8648368b8f1369c70">this gist</a> 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<Integer, RecyclerView.ViewHolder> 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);
}
}

View File

@ -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);
}
}

View File

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

View File

@ -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,
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) {
if (!recyclerView.canScrollVertically(atTop ? -1 : 1)
&& newState == RecyclerView.SCROLL_STATE_IDLE
&& !loading
&& lazyLoadListener != null) {
loading = true;
new Handler().postDelayed(() -> lazyLoadListener.onLoadMore(++currentPage), 500);
}
new Handler().postDelayed(() -> lazyLoadListener.onLoadMore(++currentPage), 300);
}
}

View File

@ -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) {}
}

View File

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

View File

@ -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,32 +50,36 @@ 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) {
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;
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;
@ -85,7 +90,7 @@ public final class WaveformSeekBar extends View {
} else if (waveGravity == WaveGravity.CENTER) {
top = (float) getPaddingTop() + availableHeight / 2F - waveHeight / 2F;
} else if (waveGravity == WaveGravity.BOTTOM) {
top = mCanvasHeight - (float) getPaddingBottom() - waveHeight;
top = getMeasuredHeight() - (float) getPaddingBottom() - waveHeight;
} else {
top = 0;
}
@ -117,11 +122,10 @@ public final class WaveformSeekBar extends View {
lastWaveRight = mWaveRect.right + waveGap;
if (lastWaveRight + waveWidth > availableWidth + getPaddingLeft())
if (lastWaveRight + waveWidth > availableWidth + getPaddingLeft()) {
break;
i += 1 / step;
}
i++;
}
}
@ -151,11 +155,26 @@ 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() {
@ -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) {
public void setSample(final float[] sample) {
if (sample == this.sample) return;
this.sample = sample;
calculateWaveDimensions();
invalidate();
}
}
}

View File

@ -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<View> 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<View> 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<String> 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<MediaController.AlbumEntry> 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);
}
}

View File

@ -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,36 +175,37 @@ 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:
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;
case R.id.action_dms:
} 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 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);
}
// 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();

View File

@ -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<Void, Void, InboxModel> 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<InboxModel> fetchListener = new FetchListener<InboxModel>() {
@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<InboxThreadModel> list = listViewModel.getList().getValue();
list = list != null ? new LinkedList<>(list) : new LinkedList<>();
// final int oldSize = list != null ? list.size() : 0;
final List<InboxThreadModel> 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<List<DirectThread>> 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;
});
inboxList.addOnScrollListener(lazyLoader);
stopCurrentExecutor();
currentlyRunning = new InboxFetcher(null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
};
viewModel.getThreads().observe(fragmentActivity, threadsObserver);
viewModel.getFetchingInbox().observe(getViewLifecycleOwner(), fetching -> binding.swipeRefreshLayout.setRefreshing(fetching));
viewModel.getUnseenCount().observe(getViewLifecycleOwner(), this::setBottomNavBarBadge);
}
private void stopCurrentExecutor() {
if (currentlyRunning != null) {
try {
currentlyRunning.cancel(true);
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
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
}
public static class InboxThreadModelListViewModel extends ViewModel {
private MutableLiveData<List<InboxThreadModel>> list;
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;
});
inboxAdapter.setHasStableIds(true);
binding.inboxList.setAdapter(inboxAdapter);
lazyLoader = new RecyclerLazyLoaderAtEdge(layoutManager, page -> {
if (viewModel == null) return;
viewModel.fetchInbox();
});
binding.inboxList.addOnScrollListener(lazyLoader);
}
public MutableLiveData<List<InboxThreadModel>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}
return list;
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;
}
if (badge.getVerticalOffset() != 10) {
badge.setVerticalOffset(10);
}
badge.setNumber(unseenCount);
badge.setVisible(true);
}
}

View File

@ -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<Void, Void, InboxThreadModel> currentlyRunning;
// private AsyncTask<Void, Void, InboxThreadModel> currentlyRunning;
private View.OnClickListener clickListener;
private View.OnClickListener basicClickListener;
private final FetchListener<InboxThreadModel> fetchListener = new FetchListener<InboxThreadModel>() {
@Override
public void doBefore() {}
@Override
public void onResult(final InboxThreadModel threadModel) {
if (threadModel == null) return;
final List<Long> 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<InboxThreadModel> fetchListener = new FetchListener<InboxThreadModel>() {
// @Override
// public void doBefore() {}
//
// @Override
// public void onResult(final InboxThreadModel threadModel) {
// if (threadModel == null) return;
// final List<Long> 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<String, Void, Void> {
@ -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;

View File

@ -0,0 +1,526 @@
package awais.instagrabber.fragments.imageedit;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.constraintlayout.widget.Barrier;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import com.google.android.material.slider.Slider;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import awais.instagrabber.adapters.FiltersAdapter;
import awais.instagrabber.databinding.FragmentFiltersBinding;
import awais.instagrabber.fragments.imageedit.filters.FiltersHelper;
import awais.instagrabber.fragments.imageedit.filters.FiltersHelper.FilterType;
import awais.instagrabber.fragments.imageedit.filters.filters.Filter;
import awais.instagrabber.fragments.imageedit.filters.filters.FilterFactory;
import awais.instagrabber.fragments.imageedit.filters.properties.FloatProperty;
import awais.instagrabber.fragments.imageedit.filters.properties.Property;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.BitmapUtils;
import awais.instagrabber.utils.SerializablePair;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.FiltersFragmentViewModel;
import awais.instagrabber.viewmodels.ImageEditViewModel;
import jp.co.cyberagent.android.gpuimage.GPUImage;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilterGroup;
public class FiltersFragment extends Fragment {
private static final String TAG = FiltersFragment.class.getSimpleName();
private static final String ARGS_SOURCE_URI = "source_uri";
private static final String ARGS_DEST_URI = "dest_uri";
private static final String ARGS_TUNING_FILTERS = "tuning_filters";
private static final String ARGS_FILTER = "filter";
private static final String ARGS_TAB = "tab";
private final Map<FilterType, Filter<?>> tuningFilters = new HashMap<>();
private final Map<Property<?>, Integer> propertySliderIdMap = new HashMap<>();
private GPUImageFilterGroup filterGroup;
private Filter<? extends GPUImageFilter> appliedFilter;
private FragmentFiltersBinding binding;
private AppExecutors appExecutors;
private Uri sourceUri;
private Uri destUri;
private FiltersFragmentViewModel viewModel;
private boolean isFilterGroupSet = false;
private FilterCallback callback;
private FiltersAdapter filtersAdapter;
private HashMap<FilterType, Map<Integer, Object>> initialTuningFiltersValues;
private SerializablePair<FilterType, Map<Integer, Object>> initialFilter;
@NonNull
public static FiltersFragment newInstance(@NonNull final Uri sourceUri,
@NonNull final Uri destUri,
@NonNull final ImageEditViewModel.Tab tab) {
return newInstance(sourceUri, destUri, null, null, tab);
}
@NonNull
public static FiltersFragment newInstance(@NonNull final Uri sourceUri,
@NonNull final Uri destUri,
final HashMap<FilterType, Map<Integer, Object>> appliedTuningFilters,
final SerializablePair<FilterType, Map<Integer, Object>> appliedFilter,
@NonNull final ImageEditViewModel.Tab tab) {
final Bundle args = new Bundle();
args.putParcelable(ARGS_SOURCE_URI, sourceUri);
args.putParcelable(ARGS_DEST_URI, destUri);
if (appliedTuningFilters != null) {
args.putSerializable(ARGS_TUNING_FILTERS, appliedTuningFilters);
}
if (appliedFilter != null) {
args.putSerializable(ARGS_FILTER, appliedFilter);
}
args.putString(ARGS_TAB, tab.name());
final FiltersFragment fragment = new FiltersFragment();
fragment.setArguments(args);
return fragment;
}
public FiltersFragment() {
filterGroup = new GPUImageFilterGroup();
filterGroup.addFilter(new GPUImageFilter());
}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
appExecutors = AppExecutors.getInstance();
viewModel = new ViewModelProvider(this).get(FiltersFragmentViewModel.class);
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
binding = FragmentFiltersBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
init(savedInstanceState);
}
@Override
public void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState);
final ImageEditViewModel.Tab tab = viewModel.getCurrentTab().getValue();
if (tab != null) {
outState.putString(ARGS_TAB, tab.name());
}
}
@Override
public void onPause() {
super.onPause();
// binding.preview.onPause();
}
@Override
public void onResume() {
super.onResume();
// binding.preview.onResume();
}
@Override
public void onDestroyView() {
super.onDestroyView();
for (final GPUImageFilter filter : filterGroup.getFilters()) {
filter.destroy();
}
filterGroup.getFilters().clear();
filterGroup.destroy();
}
private void init(final Bundle savedInstanceState) {
setupObservers();
final Bundle arguments = getArguments();
if (arguments == null) return;
final Parcelable uriParcelable = arguments.getParcelable(ARGS_SOURCE_URI);
if (!(uriParcelable instanceof Uri)) return;
sourceUri = (Uri) uriParcelable;
final Parcelable destUriParcelable = arguments.getParcelable(ARGS_DEST_URI);
if (!(destUriParcelable instanceof Uri)) return;
destUri = (Uri) destUriParcelable;
final Serializable tuningFiltersSerializable = arguments.getSerializable(ARGS_TUNING_FILTERS);
if (tuningFiltersSerializable instanceof HashMap) {
try {
//noinspection unchecked
initialTuningFiltersValues = (HashMap<FilterType, Map<Integer, Object>>) tuningFiltersSerializable;
} catch (Exception e) {
Log.e(TAG, "init: ", e);
}
}
final Serializable filterSerializable = arguments.getSerializable(ARGS_FILTER);
if (filterSerializable instanceof SerializablePair) {
try {
//noinspection unchecked
initialFilter = (SerializablePair<FilterType, Map<Integer, Object>>) filterSerializable;
} catch (Exception e) {
Log.e(TAG, "init: ", e);
}
}
final Context context = getContext();
if (context == null) return;
binding.preview.setScaleType(GPUImage.ScaleType.CENTER_INSIDE);
appExecutors.tasksThread().execute(() -> {
binding.preview.setImage(sourceUri);
setPreviewBounds();
});
setCurrentTab(ImageEditViewModel.Tab.valueOf(savedInstanceState != null && savedInstanceState.containsKey(ARGS_TAB)
? savedInstanceState.getString(ARGS_TAB)
: arguments.getString(ARGS_TAB)));
binding.cancel.setOnClickListener(v -> {
if (callback == null) return;
callback.onCancel();
});
binding.reset.setOnClickListener(v -> {
final ImageEditViewModel.Tab tab = viewModel.getCurrentTab().getValue();
if (tab == ImageEditViewModel.Tab.TUNE) {
final Collection<Filter<?>> filters = tuningFilters.values();
for (final Filter<?> filter : filters) {
if (filter == null) continue;
filter.reset();
}
resetSliders();
}
if (tab == ImageEditViewModel.Tab.FILTERS) {
final List<GPUImageFilter> groupFilters = filterGroup.getFilters();
if (appliedFilter != null) {
groupFilters.remove(appliedFilter.getInstance());
appliedFilter = null;
}
if (filtersAdapter != null) {
filtersAdapter.setSelected(0);
}
binding.preview.post(() -> binding.preview.setFilter(filterGroup = new GPUImageFilterGroup(groupFilters)));
}
});
binding.apply.setOnClickListener(v -> {
if (callback == null) return;
final List<Filter<?>> appliedTunings = getAppliedTunings();
appExecutors.tasksThread().submit(() -> {
final Bitmap bitmap = binding.preview.getGPUImage().getBitmapWithFilterApplied();
try {
BitmapUtils.convertToJpegAndSaveToUri(context, bitmap, destUri);
callback.onApply(destUri, appliedTunings, appliedFilter);
} catch (Exception e) {
Log.e(TAG, "init: ", e);
}
});
});
}
@NonNull
private List<Filter<?>> getAppliedTunings() {
return tuningFilters
.values()
.stream()
.filter(Objects::nonNull)
.filter(filter -> {
final Map<Integer, Property<?>> propertyMap = filter.getProperties();
if (propertyMap == null) return false;
final Collection<Property<?>> properties = propertyMap.values();
return properties.stream()
.noneMatch(property -> {
final Object value = property.getValue();
if (value == null) {
return false;
}
return value.equals(property.getDefaultValue());
});
})
.collect(Collectors.toList());
}
private void resetSliders() {
final Set<Map.Entry<Property<?>, Integer>> entries = propertySliderIdMap.entrySet();
for (final Map.Entry<Property<?>, Integer> entry : entries) {
final Property<?> property = entry.getKey();
final Integer viewId = entry.getValue();
final Slider slider = (Slider) binding.getRoot().findViewById(viewId);
if (slider == null) continue;
final Object defaultValue = property.getDefaultValue();
if (!(defaultValue instanceof Float)) continue;
slider.setValue((float) defaultValue);
}
}
private void setPreviewBounds() {
InputStream inputStream = null;
try {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
final Context context = getContext();
if (context == null) return;
inputStream = context.getContentResolver().openInputStream(sourceUri);
BitmapFactory.decodeStream(inputStream, null, options);
final float ratio = (float) options.outWidth / options.outHeight;
appExecutors.mainThread().execute(() -> {
final ViewGroup.LayoutParams previewLayoutParams = binding.preview.getLayoutParams();
if (options.outHeight > options.outWidth) {
previewLayoutParams.width = (int) (binding.preview.getHeight() * ratio);
} else {
previewLayoutParams.height = (int) (binding.preview.getWidth() / ratio);
}
binding.preview.setRatio(ratio);
binding.preview.requestLayout();
});
} catch (FileNotFoundException e) {
Log.e(TAG, "setPreviewBounds: ", e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException ignored) {}
}
}
}
private void setupObservers() {
viewModel.isLoading().observe(getViewLifecycleOwner(), loading -> {
});
viewModel.getCurrentTab().observe(getViewLifecycleOwner(), tab -> {
switch (tab) {
case TUNE:
setupTuning();
break;
case FILTERS:
setupFilters();
break;
default:
break;
}
});
}
private void setupTuning() {
initTuningControls();
binding.filters.setVisibility(View.GONE);
binding.tuneControlsWrapper.setVisibility(View.VISIBLE);
}
private void initTuningControls() {
final Context context = getContext();
if (context == null) return;
final ConstraintLayout controlsParent = new ConstraintLayout(context);
controlsParent.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
final Barrier sliderBarrier = new Barrier(context);
sliderBarrier.setId(Barrier.generateViewId());
sliderBarrier.setType(Barrier.START);
controlsParent.addView(sliderBarrier);
binding.tuneControlsWrapper.addView(controlsParent);
final int labelPadding = Utils.convertDpToPx(8);
final List<Filter<?>> tuneFilters = FiltersHelper.getTuneFilters();
Slider previousSlider = null;
// Need to iterate backwards
for (int i = tuneFilters.size() - 1; i >= 0; i--) {
final Filter<?> tuneFilter = tuneFilters.get(i);
if (tuneFilter.getProperties() == null || tuneFilter.getProperties().isEmpty() || tuneFilter.getProperties().size() > 1) continue;
final int propKey = tuneFilter.getProperties().keySet().iterator().next();
final Property<?> property = tuneFilter.getProperties().values().iterator().next();
if (!(property instanceof FloatProperty)) continue;
final GPUImageFilter filterInstance = tuneFilter.getInstance();
tuningFilters.put(tuneFilter.getType(), tuneFilter);
filterGroup.addFilter(filterInstance);
final FloatProperty floatProperty = (FloatProperty) property;
final Slider slider = new Slider(context);
final int viewId = Slider.generateViewId();
slider.setId(viewId);
propertySliderIdMap.put(floatProperty, viewId);
final ConstraintLayout.LayoutParams sliderLayoutParams = new ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_CONSTRAINT,
ConstraintLayout.LayoutParams.WRAP_CONTENT);
sliderLayoutParams.startToEnd = sliderBarrier.getId();
sliderLayoutParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
if (previousSlider == null) {
sliderLayoutParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
} else {
sliderLayoutParams.bottomToTop = previousSlider.getId();
final ConstraintLayout.LayoutParams prevSliderLayoutParams = (ConstraintLayout.LayoutParams) previousSlider.getLayoutParams();
prevSliderLayoutParams.topToBottom = slider.getId();
}
if (i == 0) {
sliderLayoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
}
slider.setLayoutParams(sliderLayoutParams);
slider.setValueFrom(floatProperty.getMinValue());
slider.setValueTo(floatProperty.getMaxValue());
float defaultValue = floatProperty.getDefaultValue();
if (initialTuningFiltersValues != null && initialTuningFiltersValues.containsKey(tuneFilter.getType())) {
final Map<Integer, Object> valueMap = initialTuningFiltersValues.get(tuneFilter.getType());
if (valueMap != null) {
final Object value = valueMap.get(propKey);
if (value instanceof Float) {
defaultValue = (float) value;
tuneFilter.adjust(propKey, value);
}
}
}
slider.setValue(defaultValue);
slider.addOnChangeListener((slider1, value, fromUser) -> {
final Filter<?> filter = tuningFilters.get(tuneFilter.getType());
if (filter != null) {
tuneFilter.adjust(propKey, value);
}
binding.preview.post(() -> binding.preview.requestRender());
});
final AppCompatTextView label = new AppCompatTextView(context);
label.setId(AppCompatTextView.generateViewId());
final ConstraintLayout.LayoutParams labelLayoutParams = new ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.WRAP_CONTENT,
ConstraintLayout.LayoutParams.MATCH_CONSTRAINT);
labelLayoutParams.topToTop = slider.getId();
labelLayoutParams.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
labelLayoutParams.endToStart = sliderBarrier.getId();
labelLayoutParams.bottomToBottom = slider.getId();
labelLayoutParams.horizontalBias = 1;
label.setLayoutParams(labelLayoutParams);
label.setGravity(Gravity.CENTER);
label.setPadding(labelPadding, labelPadding, labelPadding, labelPadding);
label.setText(tuneFilter.getLabel());
controlsParent.addView(label);
controlsParent.addView(slider);
previousSlider = slider;
}
addInitialFilter();
if (!isFilterGroupSet) {
isFilterGroupSet = true;
binding.preview.post(() -> binding.preview.setFilter(filterGroup));
}
}
private void addInitialFilter() {
if (initialFilter == null) return;
final Filter<?> instance = FilterFactory.getInstance(initialFilter.first);
if (instance == null) return;
addFilterToGroup(instance, initialFilter.second);
appliedFilter = instance;
}
private void setupFilters() {
final Context context = getContext();
if (context == null) return;
addTuneFilters();
binding.filters.setVisibility(View.VISIBLE);
final RecyclerView.ItemAnimator animator = binding.filters.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
final SimpleItemAnimator itemAnimator = (SimpleItemAnimator) animator;
itemAnimator.setSupportsChangeAnimations(false);
}
binding.tuneControlsWrapper.setVisibility(View.GONE);
binding.filters.setLayoutManager(new LinearLayoutManager(context, RecyclerView.HORIZONTAL, false));
final FiltersAdapter.OnFilterClickListener onFilterClickListener = (position, filter) -> {
if (appliedFilter != null && appliedFilter.equals(filter)) return;
final List<GPUImageFilter> filters = filterGroup.getFilters();
if (appliedFilter != null) {
// remove applied filter from current filter list
filters.remove(appliedFilter.getInstance());
}
// add the new filter
filters.add(filter.getInstance());
filterGroup = new GPUImageFilterGroup(filters);
binding.preview.post(() -> binding.preview.setFilter(filterGroup));
filtersAdapter.setSelected(position);
appliedFilter = filter;
};
BitmapUtils.getThumbnail(context, sourceUri, new BitmapUtils.ThumbnailLoadCallback() {
@Override
public void onLoad(@Nullable final Bitmap bitmap, final int width, final int height) {
filtersAdapter = new FiltersAdapter(
tuningFilters.values()
.stream()
.map(Filter::getInstance)
.collect(Collectors.toList()),
sourceUri.toString(),
bitmap,
onFilterClickListener
);
appExecutors.mainThread().execute(() -> {
binding.filters.setAdapter(filtersAdapter);
filtersAdapter.submitList(FiltersHelper.getFilters(), () -> {
if (appliedFilter == null) return;
filtersAdapter.setSelectedFilter(appliedFilter.getInstance());
});
});
}
@Override
public void onFailure(@NonNull final Throwable t) {
Log.e(TAG, "onFailure: ", t);
}
});
addInitialFilter();
binding.preview.setFilter(filterGroup);
}
private void addTuneFilters() {
if (initialTuningFiltersValues == null) return;
final List<Filter<?>> tuneFilters = FiltersHelper.getTuneFilters();
for (final Filter<?> tuneFilter : tuneFilters) {
if (!initialTuningFiltersValues.containsKey(tuneFilter.getType())) continue;
addFilterToGroup(tuneFilter, initialTuningFiltersValues.get(tuneFilter.getType()));
}
}
private void addFilterToGroup(@NonNull final Filter<?> tuneFilter, final Map<Integer, Object> valueMap) {
final GPUImageFilter filter = tuneFilter.getInstance();
filterGroup.addFilter(filter);
if (valueMap == null) return;
final Set<Map.Entry<Integer, Object>> entries = valueMap.entrySet();
for (final Map.Entry<Integer, Object> entry : entries) {
tuneFilter.adjust(entry.getKey(), entry.getValue());
}
}
public void setCurrentTab(final ImageEditViewModel.Tab tab) {
viewModel.setCurrentTab(tab);
}
public void setCallback(final FilterCallback callback) {
if (callback == null) return;
this.callback = callback;
}
public interface FilterCallback {
void onApply(final Uri uri, List<Filter<?>> tuningFilters, Filter<?> filter);
void onCancel();
}
}

View File

@ -0,0 +1,285 @@
package awais.instagrabber.fragments.imageedit;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.RectF;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.SavedStateHandle;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.yalantis.ucrop.UCrop;
import com.yalantis.ucrop.UCropActivity;
import com.yalantis.ucrop.UCropFragment;
import com.yalantis.ucrop.UCropFragmentCallback;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.databinding.FragmentImageEditBinding;
import awais.instagrabber.fragments.imageedit.filters.filters.Filter;
import awais.instagrabber.models.SavedImageEditState;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.ImageEditViewModel;
public class ImageEditFragment extends Fragment {
private static final String TAG = ImageEditFragment.class.getSimpleName();
private static final String ARGS_URI = "uri";
private static final String FILTERS_FRAGMENT_TAG = "Filters";
private FragmentImageEditBinding binding;
private ImageEditViewModel viewModel;
private ImageEditViewModel.Tab previousTab;
private FiltersFragment filtersFragment;
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
setEnabled(false);
remove();
if (previousTab != ImageEditViewModel.Tab.CROP
&& previousTab != ImageEditViewModel.Tab.TUNE
&& previousTab != ImageEditViewModel.Tab.FILTERS) {
return;
}
final FragmentManager fragmentManager = getChildFragmentManager();
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setReorderingAllowed(true)
.remove(previousTab == ImageEditViewModel.Tab.CROP ? uCropFragment : filtersFragment)
.commit();
viewModel.setCurrentTab(ImageEditViewModel.Tab.RESULT);
}
};
private FragmentActivity fragmentActivity;
private UCropFragment uCropFragment;
public static ImageEditFragment newInstance(final Uri uri) {
final Bundle args = new Bundle();
args.putParcelable(ARGS_URI, uri);
final ImageEditFragment fragment = new ImageEditFragment();
fragment.setArguments(args);
return fragment;
}
public ImageEditFragment() {}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = getActivity();
viewModel = new ViewModelProvider(this).get(ImageEditViewModel.class);
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
binding = FragmentImageEditBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
init();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public void onDestroyView() {
super.onDestroyView();
}
private void init() {
setupObservers();
final Bundle arguments = getArguments();
if (arguments == null) return;
final Parcelable parcelable = arguments.getParcelable(ARGS_URI);
Uri originalUri = null;
if (parcelable instanceof Uri) {
originalUri = (Uri) parcelable;
}
if (originalUri == null) return;
viewModel.setOriginalUri(originalUri);
viewModel.setCurrentTab(ImageEditViewModel.Tab.RESULT);
}
private void setupObservers() {
viewModel.isLoading().observe(getViewLifecycleOwner(), loading -> {});
viewModel.getCurrentTab().observe(getViewLifecycleOwner(), tab -> {
if (tab == null) return;
switch (tab) {
case RESULT:
setupResult();
break;
case CROP:
setupCropFragment();
break;
case TUNE:
case FILTERS:
setupFilterFragment();
break;
}
previousTab = tab;
});
viewModel.isCropped().observe(getViewLifecycleOwner(), isCropped -> binding.crop.setSelected(isCropped));
viewModel.isTuned().observe(getViewLifecycleOwner(), isTuned -> binding.tune.setSelected(isTuned));
viewModel.isFiltered().observe(getViewLifecycleOwner(), isFiltered -> binding.filters.setSelected(isFiltered));
viewModel.getResultUri().observe(getViewLifecycleOwner(), uri -> {
if (uri == null) {
binding.preview.setController(null);
return;
}
binding.preview.setController(Fresco.newDraweeControllerBuilder()
.setImageRequest(ImageRequestBuilder.newBuilderWithSource(uri)
.disableDiskCache()
.disableMemoryCache()
.build())
.build());
});
}
private void setupResult() {
binding.fragmentContainerView.setVisibility(View.GONE);
binding.cropBottomControls.setVisibility(View.GONE);
binding.preview.setVisibility(View.VISIBLE);
binding.resultBottomControls.setVisibility(View.VISIBLE);
binding.crop.setOnClickListener(v -> viewModel.setCurrentTab(ImageEditViewModel.Tab.CROP));
binding.tune.setOnClickListener(v -> viewModel.setCurrentTab(ImageEditViewModel.Tab.TUNE));
binding.filters.setOnClickListener(v -> viewModel.setCurrentTab(ImageEditViewModel.Tab.FILTERS));
binding.cancel.setOnClickListener(v -> {
viewModel.cancel();
final NavController navController = NavHostFragment.findNavController(this);
navController.navigateUp();
});
binding.done.setOnClickListener(v -> {
final Context context = getContext();
if (context == null) return;
Utils.mediaScanFile(context, viewModel.getDestinationFile(), (path, uri) -> {
final NavBackStackEntry navBackStackEntry = NavHostFragment.findNavController(this).getPreviousBackStackEntry();
if (navBackStackEntry == null) return;
final SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
savedStateHandle.set("result", uri);
});
});
}
private void setupCropFragment() {
final Context context = getContext();
if (context == null) return;
binding.preview.setVisibility(View.GONE);
binding.resultBottomControls.setVisibility(View.GONE);
binding.fragmentContainerView.setVisibility(View.VISIBLE);
binding.cropBottomControls.setVisibility(View.VISIBLE);
final UCrop.Options options = new UCrop.Options();
options.setCompressionFormat(Bitmap.CompressFormat.JPEG);
options.setFreeStyleCropEnabled(true);
options.setAllowedGestures(UCropActivity.SCALE, UCropActivity.ROTATE, UCropActivity.ALL);
final UCrop uCrop = UCrop.of(viewModel.getOriginalUri(), viewModel.getCropDestinationUri()).withOptions(options);
final SavedImageEditState savedState = viewModel.getSavedImageEditState();
if (savedState != null && savedState.getCropImageMatrixValues() != null && savedState.getCropRect() != null) {
uCrop.withSavedState(savedState.getCropImageMatrixValues(), savedState.getCropRect());
}
uCropFragment = uCrop.getFragment(uCrop.getIntent(context).getExtras());
final FragmentManager fragmentManager = getChildFragmentManager();
uCropFragment.setCallback(new UCropFragmentCallback() {
@Override
public void loadingProgress(final boolean showLoader) {
Log.d(TAG, "loadingProgress: " + showLoader);
}
@Override
public void onCropFinish(final UCropFragment.UCropResult result) {
Log.d(TAG, "onCropFinish: " + result.mResultCode);
if (result.mResultCode == AppCompatActivity.RESULT_OK) {
final Intent resultData = result.mResultData;
final Bundle extras = resultData.getExtras();
if (extras == null) return;
final Object uri = extras.get(UCrop.EXTRA_OUTPUT_URI);
final Object imageMatrixValues = extras.get(UCrop.EXTRA_IMAGE_MATRIX_VALUES);
final Object cropRect = extras.get(UCrop.EXTRA_CROP_RECT);
if (uri instanceof Uri && imageMatrixValues instanceof float[] && cropRect instanceof RectF) {
Log.d(TAG, "onCropFinish: result uri: " + uri);
viewModel.setCropResult((float[]) imageMatrixValues, (RectF) cropRect);
viewModel.setCurrentTab(ImageEditViewModel.Tab.RESULT);
}
}
}
});
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setReorderingAllowed(true)
.replace(R.id.fragment_container_view, uCropFragment, UCropFragment.TAG)
.commit();
if (!onBackPressedCallback.isEnabled()) {
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
onBackPressedCallback.setEnabled(true);
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
}
binding.cropCancel.setOnClickListener(v -> onBackPressedCallback.handleOnBackPressed());
binding.cropReset.setOnClickListener(v -> uCropFragment.reset());
binding.cropDone.setOnClickListener(v -> uCropFragment.cropAndSaveImage());
}
private void setupFilterFragment() {
binding.resultBottomControls.setVisibility(View.GONE);
binding.preview.setVisibility(View.GONE);
binding.cropBottomControls.setVisibility(View.GONE);
binding.fragmentContainerView.setVisibility(View.VISIBLE);
final Boolean isCropped = viewModel.isCropped().getValue();
final Uri uri = isCropped != null && isCropped ? viewModel.getCropDestinationUri() : viewModel.getOriginalUri();
final ImageEditViewModel.Tab value = viewModel.getCurrentTab().getValue();
final SavedImageEditState savedImageEditState = viewModel.getSavedImageEditState();
filtersFragment = FiltersFragment.newInstance(
uri,
viewModel.getDestinationUri(),
savedImageEditState.getAppliedTuningFilters(),
savedImageEditState.getAppliedFilter(),
value == null ? ImageEditViewModel.Tab.TUNE : value
);
final FragmentManager fragmentManager = getChildFragmentManager();
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setReorderingAllowed(true)
.replace(R.id.fragment_container_view, filtersFragment, FILTERS_FRAGMENT_TAG)
.commit();
if (!onBackPressedCallback.isEnabled()) {
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
onBackPressedCallback.setEnabled(true);
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
}
filtersFragment.setCallback(new FiltersFragment.FilterCallback() {
@Override
public void onApply(final Uri uri, final List<Filter<?>> tuningFilters, final Filter<?> filter) {
viewModel.setAppliedFilters(tuningFilters, filter);
viewModel.setCurrentTab(ImageEditViewModel.Tab.RESULT);
}
@Override
public void onCancel() {
onBackPressedCallback.handleOnBackPressed();
}
});
}
}

View File

@ -0,0 +1,129 @@
package awais.instagrabber.fragments.imageedit.filters;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import awais.instagrabber.fragments.imageedit.filters.filters.Filter;
import awais.instagrabber.fragments.imageedit.filters.filters.FilterFactory;
public final class FiltersHelper {
public static List<Filter<?>> getTuneFilters() {
return TUNING_FILTERS.stream()
.map(FilterFactory::getInstance)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
public static List<Filter<?>> getFilters() {
// Return all non-tuning filters
return Arrays.stream(FilterType.values())
.filter(filterType -> !TUNING_FILTERS.contains(filterType))
.map(FilterFactory::getInstance)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
public static final List<FilterType> TUNING_FILTERS = ImmutableList.of(
FilterType.BRIGHTNESS,
FilterType.CONTRAST,
FilterType.VIBRANCE,
FilterType.SATURATION,
FilterType.SHARPEN,
FilterType.EXPOSURE
);
public enum FilterType {
// Tune
BRIGHTNESS,
CONTRAST,
VIBRANCE,
SATURATION,
SHARPEN,
EXPOSURE,
// Filters
NORMAL,
SEPIA,
CLARENDON,
ONE977,
ADEN,
VIGNETTE,
// BILATERAL_BLUR,
// BOX_BLUR,
// BULGE_DISTORTION,
// CGA_COLORSPACE,
// COLOR_BALANCE,
// CROSSHATCH,
// DILATION,
// EMBOSS,
// FALSE_COLOR,
// GAMMA,
// GAUSSIAN_BLUR,
// GLASS_SPHERE,
// GRAYSCALE,
// HALFTONE,
// HAZE,
// HIGHLIGHT_SHADOW,
// HUE,
// INVERT,
// KUWAHARA,
// LAPLACIAN,
// LEVELS_FILTER_MIN,
// LOOKUP_AMATORKA,
// LUMINANCE,
// LUMINANCE_THRESHOLD,
// MONOCHROME,
// NON_MAXIMUM_SUPPRESSION,
// OPACITY,
// PIXELATION,
// POSTERIZE,
// RGB,
// RGB_DILATION,
// SKETCH,
// SMOOTH_TOON,
// SOBEL_EDGE_DETECTION,
// SOLARIZE,
// SPHERE_REFRACTION,
// SWIRL,
// THREE_X_THREE_CONVOLUTION,
// THRESHOLD_EDGE_DETECTION,
// TONE_CURVE,
// TOON,
// TRANSFORM2D,
// WEAK_PIXEL_INCLUSION,
// ZOOM_BLUR
// Can be separate tunings
// WHITE_BALANCE,
// BLEND_ADD,
// BLEND_ALPHA,
// BLEND_CHROMA_KEY,
// BLEND_COLOR,
// BLEND_COLOR_BURN,
// BLEND_COLOR_DODGE,
// BLEND_DARKEN,
// BLEND_DIFFERENCE,
// BLEND_DISSOLVE,
// BLEND_DIVIDE,
// BLEND_EXCLUSION,
// BLEND_HARD_LIGHT,
// BLEND_HUE,
// BLEND_LIGHTEN,
// BLEND_LINEAR_BURN,
// BLEND_LUMINOSITY,
// BLEND_MULTIPLY,
// BLEND_NORMAL,
// BLEND_OVERLAY,
// BLEND_SATURATION,
// BLEND_SCREEN,
// BLEND_SOFT_LIGHT,
// BLEND_SOURCE_OVER,
// BLEND_SUBTRACT,
}
}

View File

@ -0,0 +1,16 @@
package awais.instagrabber.fragments.imageedit.filters.custom;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageBrightnessFilter;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageContrastFilter;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilterGroup;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageHueFilter;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageSaturationFilter;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageSepiaToneFilter;
public class GPUImage1977Filter extends GPUImageFilterGroup {
public GPUImage1977Filter() {
addFilter(new GPUImageSepiaToneFilter(0.35f));
addFilter(new GPUImageHueFilter(-30f));
addFilter(new GPUImageSaturationFilter(1.4f));
}
}

View File

@ -0,0 +1,26 @@
package awais.instagrabber.fragments.imageedit.filters.custom;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageBrightnessFilter;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilterGroup;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageMultiplyBlendFilter;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageSaturationFilter;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageSepiaToneFilter;
public class GPUImageAdenFilter extends GPUImageFilterGroup {
public GPUImageAdenFilter() {
super();
addFilter(new GPUImageSepiaToneFilter(0.2f));
addFilter(new GPUImageBrightnessFilter(0.125f));
addFilter(new GPUImageSaturationFilter(1.4f));
final GPUImageMultiplyBlendFilter blendFilter = new GPUImageMultiplyBlendFilter();
final Bitmap bitmap = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.argb((int) (0.1 * 255), 125, 105, 24));
blendFilter.setBitmap(bitmap);
addFilter(blendFilter);
}
}

View File

@ -0,0 +1,30 @@
package awais.instagrabber.fragments.imageedit.filters.custom;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageBrightnessFilter;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageContrastFilter;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilterGroup;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageHueFilter;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageOverlayBlendFilter;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageSaturationFilter;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageSepiaToneFilter;
public class GPUImageClarendonFilter extends GPUImageFilterGroup {
public GPUImageClarendonFilter() {
super();
addFilter(new GPUImageBrightnessFilter(0.15f));
addFilter(new GPUImageContrastFilter(1.25f));
addFilter(new GPUImageSaturationFilter(1.15f));
addFilter(new GPUImageSepiaToneFilter(0.15f));
addFilter(new GPUImageHueFilter(5));
final GPUImageOverlayBlendFilter blendFilter = new GPUImageOverlayBlendFilter();
final Bitmap bitmap = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.argb((int) (0.4 * 255), 127, 187, 227));
blendFilter.setBitmap(bitmap);
addFilter(blendFilter);
}
}

View File

@ -0,0 +1,28 @@
package awais.instagrabber.fragments.imageedit.filters.filters;
import java.util.Map;
import awais.instagrabber.R;
import awais.instagrabber.fragments.imageedit.filters.FiltersHelper;
import awais.instagrabber.fragments.imageedit.filters.custom.GPUImageAdenFilter;
import awais.instagrabber.fragments.imageedit.filters.properties.Property;
public class AdenFilter extends Filter<GPUImageAdenFilter> {
private final GPUImageAdenFilter filter;
public AdenFilter() {
super(FiltersHelper.FilterType.ADEN, R.string.aden);
filter = new GPUImageAdenFilter();
}
@Override
public GPUImageAdenFilter getInstance() {
return filter;
}
@Override
public Map<Integer, Property<?>> getProperties() {
return null;
}
}

View File

@ -0,0 +1,42 @@
// package awais.instagrabber.fragments.imageedit.filters.filters;
//
// import java.util.Collections;
// import java.util.Map;
//
// import awais.instagrabber.R;
// import awais.instagrabber.fragments.imageedit.filters.FiltersHelper;
// import awais.instagrabber.fragments.imageedit.filters.properties.FloatProperty;
// import awais.instagrabber.fragments.imageedit.filters.properties.Property;
// import jp.co.cyberagent.android.gpuimage.filter.GPUImageBilateralBlurFilter;
//
// public class BilateralBlurFilter extends Filter<GPUImageBilateralBlurFilter> {
// private static final int PROP_DISTANCE = 0;
//
// private final GPUImageBilateralBlurFilter filter;
// private final Map<Integer, Property<?>> properties;
//
// public BilateralBlurFilter() {
// super(FiltersHelper.FilterType.BILATERAL_BLUR, R.string.bilateral_blur);
// properties = Collections.singletonMap(
// PROP_DISTANCE, new FloatProperty(-1, 8f, 0f, 15.0f)
// );
// filter = new GPUImageBilateralBlurFilter((Float) getProperty(PROP_DISTANCE).getDefaultValue());
// }
//
// @Override
// public Map<Integer, Property<?>> getProperties() {
// return properties;
// }
//
// @Override
// public void adjust(final int property, final Object value) {
// super.adjust(property, value);
// if (!(value instanceof Float)) return;
// filter.setDistanceNormalizationFactor((Float) value);
// }
//
// @Override
// public GPUImageBilateralBlurFilter getInstance() {
// return filter;
// }
// }

View File

@ -0,0 +1,42 @@
// package awais.instagrabber.fragments.imageedit.filters.filters;
//
// import java.util.Collections;
// import java.util.Map;
//
// import awais.instagrabber.R;
// import awais.instagrabber.fragments.imageedit.filters.FiltersHelper;
// import awais.instagrabber.fragments.imageedit.filters.properties.FloatProperty;
// import awais.instagrabber.fragments.imageedit.filters.properties.Property;
// import jp.co.cyberagent.android.gpuimage.filter.GPUImageBoxBlurFilter;
//
// public class BoxBlurFilter extends Filter<GPUImageBoxBlurFilter> {
// private static final int PROP_SIZE = 0;
//
// private final GPUImageBoxBlurFilter filter;
// private final Map<Integer, Property<?>> properties;
//
// public BoxBlurFilter() {
// super(FiltersHelper.FilterType.BOX_BLUR, R.string.box_blur);
// properties = Collections.singletonMap(
// PROP_SIZE, new FloatProperty(-1, 1f, 1f, 10.0f)
// );
// filter = new GPUImageBoxBlurFilter((Float) getProperty(PROP_SIZE).getDefaultValue());
// }
//
// @Override
// public Map<Integer, Property<?>> getProperties() {
// return properties;
// }
//
// @Override
// public void adjust(final int property, final Object value) {
// super.adjust(property, value);
// if (!(value instanceof Float)) return;
// filter.setBlurSize((Float) value);
// }
//
// @Override
// public GPUImageBoxBlurFilter getInstance() {
// return filter;
// }
// }

View File

@ -0,0 +1,42 @@
package awais.instagrabber.fragments.imageedit.filters.filters;
import java.util.Collections;
import java.util.Map;
import awais.instagrabber.R;
import awais.instagrabber.fragments.imageedit.filters.FiltersHelper;
import awais.instagrabber.fragments.imageedit.filters.properties.FloatProperty;
import awais.instagrabber.fragments.imageedit.filters.properties.Property;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageBrightnessFilter;
public class BrightnessFilter extends Filter<GPUImageBrightnessFilter> {
private static final int PROP_BRIGHTNESS = 0;
private final GPUImageBrightnessFilter filter;
private final Map<Integer, Property<?>> properties;
public BrightnessFilter() {
super(FiltersHelper.FilterType.BRIGHTNESS, R.string.brightness);
properties = Collections.singletonMap(
PROP_BRIGHTNESS, new FloatProperty(R.string.brightness, 0.0f, -1.0f, 1.0f)
);
filter = new GPUImageBrightnessFilter((Float) getProperty(PROP_BRIGHTNESS).getDefaultValue());
}
@Override
public Map<Integer, Property<?>> getProperties() {
return properties;
}
@Override
public void adjust(final int property, final Object value) {
super.adjust(property, value);
if (!(value instanceof Float)) return;
filter.setBrightness((Float) value);
}
@Override
public GPUImageBrightnessFilter getInstance() {
return filter;
}
}

View File

@ -0,0 +1,28 @@
package awais.instagrabber.fragments.imageedit.filters.filters;
import java.util.Map;
import awais.instagrabber.R;
import awais.instagrabber.fragments.imageedit.filters.FiltersHelper;
import awais.instagrabber.fragments.imageedit.filters.custom.GPUImageClarendonFilter;
import awais.instagrabber.fragments.imageedit.filters.properties.Property;
public class ClarendonFilter extends Filter<GPUImageClarendonFilter> {
private final GPUImageClarendonFilter filter;
public ClarendonFilter() {
super(FiltersHelper.FilterType.CLARENDON, R.string.clarendon);
filter = new GPUImageClarendonFilter();
}
@Override
public GPUImageClarendonFilter getInstance() {
return filter;
}
@Override
public Map<Integer, Property<?>> getProperties() {
return null;
}
}

Some files were not shown because too many files have changed in this diff Show More