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:
parent
6a5c2171c6
commit
343b2cf9d7
@ -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>
|
@ -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'
|
||||
}
|
||||
|
5
app/proguard-rules.pro
vendored
5
app/proguard-rules.pro
vendored
@ -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.** { *; }
|
@ -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>
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
// }
|
||||
// }
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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());
|
||||
// }
|
||||
// }
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
// }
|
||||
// }
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
352
app/src/main/java/awais/instagrabber/customviews/RecordView.java
Normal file
352
app/src/main/java/awais/instagrabber/customviews/RecordView.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
118
app/src/main/java/awais/instagrabber/customviews/Tooltip.java
Normal file
118
app/src/main/java/awais/instagrabber/customviews/Tooltip.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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) {}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
// }
|
||||
// }
|
@ -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;
|
||||
// }
|
||||
// }
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user