Merge pull request #245 from ammargitham/new-posts-grid

New posts grid
This commit is contained in:
Austin Huang 2020-11-07 10:17:25 -05:00 committed by GitHub
commit a9003b43ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
248 changed files with 14408 additions and 6914 deletions

6
.idea/compiler.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
</component>
</project>

View File

@ -15,6 +15,7 @@
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>

View File

@ -31,5 +31,10 @@
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="http://maven.geotoolkit.org/" />
</remote-repository>
</component>
</project>

View File

@ -40,7 +40,7 @@
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -1,11 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="app" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
<module name="app" />
<module name="InstaGrabber.app" />
<option name="DEPLOY" value="true" />
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
<option name="DEPLOY_AS_INSTANT" value="false" />
<option name="ARTIFACT_NAME" value="" />
<option name="PM_INSTALL_OPTIONS" value="" />
<option name="ALL_USERS" value="false" />
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
<option name="MODE" value="default_activity" />
@ -41,11 +42,16 @@
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Sample Java Methods" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<option name="DEEP_LINK" value="" />
<option name="ACTIVITY_CLASS" value="awais.instagrabber.activities.MainActivity" />
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>

View File

@ -10,8 +10,8 @@ android {
minSdkVersion 21
targetSdkVersion 29
versionCode 51
versionName '19.0-a2'
versionCode 52
versionName '19.0'
multiDexEnabled true
@ -20,6 +20,9 @@ android {
}
compileOptions {
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
@ -36,32 +39,51 @@ android {
}
}
dependencies {
def appcompat_version = "1.2.0"
def nav_version = "2.3.0"
def preference_version = "1.1.1"
implementation 'com.google.android.material:material:1.3.0-alpha02'
implementation 'com.google.android.exoplayer:exoplayer:2.11.1'
configurations.all {
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
def appcompat_version = "1.2.0"
def nav_version = '2.3.1'
implementation 'com.google.android.material:material:1.3.0-alpha03'
implementation 'com.google.android.exoplayer:exoplayer-core:2.12.0'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.12.0'
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha05"
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha06"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
implementation "androidx.constraintlayout:constraintlayout:2.0.1"
implementation "androidx.preference:preference:$preference_version"
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.work:work-runtime:2.4.0"
implementation 'androidx.palette:palette:1.0.0'
// implementation 'com.github.hendrawd:StorageUtil:1.1.0'
implementation 'com.google.guava:guava:27.0.1-android'
// implementation 'com.github.hendrawd:StorageUtil:1.1.0'
implementation 'com.github.ammargitham:AutoLinkTextViewV2:master-SNAPSHOT'
implementation 'org.jsoup:jsoup:1.13.1'
implementation 'com.facebook.fresco:fresco:2.3.0'
implementation 'com.facebook.fresco:animated-gif:2.3.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
implementation 'com.github.dragon66:icafe:master-SNAPSHOT'
implementation 'javax.media:jai_imageio:1.1.1'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.0'
}

View File

@ -71,22 +71,7 @@
android:taskAffinity="awais.instagrabber.errorreport"
android:theme="@android:style/Theme.DeviceDefault.Dialog" />
<activity
android:name=".directdownload.MultiDirectDialog"
android:allowEmbedded="false"
android:allowTaskReparenting="false"
android:autoRemoveFromRecents="true"
android:clearTaskOnLaunch="true"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:documentLaunchMode="never"
android:excludeFromRecents="true"
android:finishOnTaskLaunch="true"
android:launchMode="singleTask"
android:lockTaskMode="never"
android:noHistory="false"
android:taskAffinity="awais.instagrabber.multidialog"
android:theme="@style/FlyingGayDialog" />
<activity
android:name=".directdownload.DirectDownload"
android:name=".activities.DirectDownload"
android:allowEmbedded="false"
android:allowTaskReparenting="false"
android:autoRemoveFromRecents="true"
@ -100,7 +85,7 @@
android:launchMode="singleTask"
android:lockTaskMode="never"
android:noHistory="false"
android:theme="@style/CompletelyTransparent">
android:theme="@style/Theme.AppCompat.Translucent">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEARCH" />
@ -138,6 +123,12 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<service android:name=".services.ActivityCheckerService" />
<service
android:name=".services.ActivityCheckerService"
android:exported="false" />
<service
android:name=".services.DeleteImageIntentService"
android:exported="false" />
</application>
</manifest>

View File

@ -32,12 +32,16 @@ public final class InstaGrabberApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// final Set<RequestListener> requestListeners = new HashSet<>();
// requestListeners.add(new RequestLoggingListener());
final ImagePipelineConfig imagePipelineConfig = ImagePipelineConfig
.newBuilder(this)
// .setMainDiskCacheConfig(diskCacheConfig)
// .setRequestListeners(requestListeners)
.setDownsampleEnabled(true)
.build();
Fresco.initialize(this, imagePipelineConfig);
// FLog.setMinimumLoggingLevel(FLog.VERBOSE);
if (BuildConfig.DEBUG) {
try {

View File

@ -0,0 +1,149 @@
package awais.instagrabber.activities;
import android.Manifest;
import android.app.Notification;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import awais.instagrabber.R;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.IntentModel;
import awais.instagrabber.models.enums.IntentModelType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.IntentUtils;
import awais.instagrabber.utils.TextUtils;
public final class DirectDownload extends AppCompatActivity {
private static final int NOTIFICATION_ID = 1900000000;
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private boolean contextFound = false;
private Intent intent;
private Context context;
private NotificationManagerCompat notificationManager;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_direct);
}
@Override
public void onWindowAttributesChanged(final WindowManager.LayoutParams params) {
super.onWindowAttributesChanged(params);
if (!contextFound) {
intent = getIntent();
context = getApplicationContext();
if (intent != null && context != null) {
contextFound = true;
checkPermissions();
}
}
}
@Override
public Resources getResources() {
if (!contextFound) {
intent = getIntent();
context = getApplicationContext();
if (intent != null && context != null) {
contextFound = true;
checkPermissions();
}
}
return super.getResources();
}
private synchronized void checkPermissions() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
doDownload();
return;
}
ActivityCompat.requestPermissions(this, DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) {
doDownload();
}
}
private synchronized void doDownload() {
notificationManager = NotificationManagerCompat.from(getApplicationContext());
final Intent intent = getIntent();
final String action = intent.getAction();
if (TextUtils.isEmpty(action) || Intent.ACTION_MAIN.equals(action)) {
finish();
return;
}
boolean error = true;
String data = null;
final Bundle extras = intent.getExtras();
if (extras != null) {
final Object extraData = extras.get(Intent.EXTRA_TEXT);
if (extraData != null) {
error = false;
data = extraData.toString();
}
}
if (error) {
final Uri intentData = intent.getData();
if (intentData != null) data = intentData.toString();
}
if (data == null || TextUtils.isEmpty(data)) {
finish();
return;
}
final IntentModel model = IntentUtils.parseUrl(data);
if (model == null || model.getType() != IntentModelType.POST) {
finish();
return;
}
final String text = model.getText();
new PostFetcher(text, new FetchListener<FeedModel>() {
@Override
public void doBefore() {
if (notificationManager == null) return;
final Notification fetchingPostNotification = new NotificationCompat.Builder(getApplicationContext(), Constants.DOWNLOAD_CHANNEL_ID)
.setCategory(NotificationCompat.CATEGORY_STATUS)
.setSmallIcon(R.drawable.ic_download)
.setAutoCancel(false)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setContentText(getString(R.string.direct_download_loading))
.build();
notificationManager.notify(NOTIFICATION_ID, fetchingPostNotification);
}
@Override
public void onResult(final FeedModel result) {
if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
if (result == null) {
finish();
return;
}
DownloadUtils.download(getApplicationContext(), result);
finish();
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}

View File

@ -5,6 +5,7 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
@ -59,7 +60,7 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
loginBinding = ActivityLoginBinding.inflate(getLayoutInflater());
loginBinding = ActivityLoginBinding.inflate(LayoutInflater.from(getApplicationContext()));
setContentView(loginBinding.getRoot());
initWebView();
@ -117,12 +118,9 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
webSettings.setDisplayZoomControls(false);
webSettings.setLoadWithOverviewMode(true);
webSettings.setUseWideViewPort(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowUniversalAccessFromFileURLs(true);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowUniversalAccessFromFileURLs(true);
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {

View File

@ -24,10 +24,12 @@ import android.widget.AutoCompleteTextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData;
import androidx.navigation.NavBackStackEntry;
@ -39,10 +41,10 @@ import androidx.navigation.ui.NavigationUI;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
@ -50,9 +52,12 @@ import java.util.Map;
import awais.instagrabber.R;
import awais.instagrabber.adapters.SuggestionsAdapter;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.asyncs.SuggestionsFetcher;
import awais.instagrabber.customviews.helpers.CustomHideBottomViewOnScrollBehavior;
import awais.instagrabber.databinding.ActivityMainBinding;
import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.fragments.main.FeedFragment;
import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.IntentModel;
@ -94,9 +99,12 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
R.id.notificationsViewer,
R.id.themePreferencesFragment,
R.id.favoritesFragment,
R.id.backupPreferencesFragment);
R.id.backupPreferencesFragment,
R.id.directMessagesThreadFragment
);
private static final Map<Integer, Integer> NAV_TO_MENU_ID_MAP = new HashMap<>();
private static final List<Integer> REMOVE_COLLAPSING_TOOLBAR_SCROLL_DESTINATIONS = Collections.singletonList(R.id.commentsViewerFragment);
private static final List<Integer> REMOVE_COLLAPSING_TOOLBAR_SCROLL_DESTINATIONS = ImmutableList.of(R.id.commentsViewerFragment,
R.id.directMessagesThreadFragment);
private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex";
private ActivityMainBinding binding;
@ -157,10 +165,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
bindActivityCheckerService();
}
getSupportFragmentManager().addOnBackStackChangedListener(this);
// Log.d("austin_debug", "dir: "+Arrays.toString(StorageUtil.getStorageDirectories(getApplicationContext())));
// final File sdcard = new File(StorageUtil.getStorageDirectories(getApplicationContext())[0]);
// Log.d("austin_debug", "files: "+Arrays.toString(sdcard.listFiles()));
}
@Override
@ -222,7 +226,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
@Override
public void onBackPressed() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isTaskRoot() && isBackStackEmpty) {
if (isTaskRoot() && isBackStackEmpty) {
finishAfterTransition();
} else {
super.onBackPressed();
@ -403,11 +407,14 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
}
final List<Integer> mainNavList = getMainNavList(main_nav_ids);
if (setDefaultFromSettings) {
final String defaultTabIdString = settingsHelper.getString(Constants.DEFAULT_TAB);
final String defaultTabResNameString = settingsHelper.getString(Constants.DEFAULT_TAB);
try {
final int defaultNavId = TextUtils.isEmpty(defaultTabIdString)
? R.navigation.profile_nav_graph
: Integer.parseInt(defaultTabIdString);
int navId = 0;
if (!TextUtils.isEmpty(defaultTabResNameString)) {
navId = getResources().getIdentifier(defaultTabResNameString, "navigation", getPackageName());
}
final int defaultNavId = navId <= 0 ? R.navigation.profile_nav_graph
: navId;
final int index = mainNavList.indexOf(defaultNavId);
if (index >= 0) firstFragmentGraphIndex = index;
setBottomNavSelectedItem(defaultNavId);
@ -422,8 +429,18 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
R.id.main_nav_host,
getIntent(),
firstFragmentGraphIndex);
navControllerLiveData.observe(this, this::setupNavigation);
navControllerLiveData.observe(this, navController -> setupNavigation(binding.toolbar, navController));
currentNavControllerLiveData = navControllerLiveData;
binding.bottomNavView.setOnNavigationItemReselectedListener(item -> {
// Log.d(TAG, "setupBottomNavigationBar: item: " + item);
final Fragment navHostFragment = getSupportFragmentManager().findFragmentById(R.id.main_nav_host);
if (navHostFragment != null) {
final Fragment fragment = navHostFragment.getChildFragmentManager().getPrimaryNavigationFragment();
if (fragment instanceof FeedFragment) {
((FeedFragment) fragment).scrollToTop();
}
}
});
}
private void setBottomNavSelectedItem(final int navId) {
@ -447,8 +464,11 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
return mainNavList;
}
private void setupNavigation(final NavController navController) {
NavigationUI.setupWithNavController(binding.toolbar, navController);
private void setupNavigation(final Toolbar toolbar, final NavController navController) {
if (navController == null) {
return;
}
NavigationUI.setupWithNavController(toolbar, navController);
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
// 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);
@ -572,13 +592,18 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
private void showPostView(@NonNull final IntentModel intentModel) {
final String shortCode = intentModel.getText();
// Log.d(TAG, "shortCode: " + shortCode);
final NavController navController = currentNavControllerLiveData.getValue();
if (currentNavControllerLiveData == null || navController == null) return;
final Bundle bundle = new Bundle();
bundle.putStringArray("idOrCodeArray", new String[]{shortCode});
bundle.putInt("index", 0);
bundle.putBoolean("isId", false);
navController.navigate(R.id.action_global_postViewFragment, bundle);
final AlertDialog alertDialog = new AlertDialog.Builder(this)
.setCancelable(false)
.setView(R.layout.dialog_opening_post)
.create();
alertDialog.show();
new PostFetcher(shortCode, feedModel -> {
final PostViewV2Fragment fragment = PostViewV2Fragment
.builder(feedModel)
.build();
fragment.setOnShowListener(dialog -> alertDialog.dismiss());
fragment.show(getSupportFragmentManager(), "post_view");
}).execute();
}
private void showLocationView(@NonNull final IntentModel intentModel) {
@ -626,4 +651,31 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
public BottomNavigationView getBottomNavView() {
return binding.bottomNavView;
}
// public void fitSystemWindows(final boolean fit) {
// binding.appBarLayout.setBackground(null);
// binding.appBarLayout.setFitsSystemWindows(fit);
// binding.collapsingToolbarLayout.setBackground(null);
// binding.collapsingToolbarLayout.setFitsSystemWindows(fit);
// final Drawable toolbarBackground = binding.toolbar.getBackground();
// binding.toolbar.setFitsSystemWindows(fit);
// binding.toolbar.setBackground(null);
// binding.toolbar.setClickable(false);
// }
//
// public int getNavHostContainerId() {
// return binding.mainNavHost.getId();
// }
public void setToolbar(final Toolbar toolbar) {
binding.appBarLayout.setVisibility(View.GONE);
setSupportActionBar(toolbar);
setupNavigation(toolbar, currentNavControllerLiveData.getValue());
}
public void resetToolbar() {
binding.appBarLayout.setVisibility(View.VISIBLE);
setSupportActionBar(binding.toolbar);
setupNavigation(binding.toolbar, currentNavControllerLiveData.getValue());
}
}

View File

@ -2,137 +2,191 @@ package awais.instagrabber.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.CommentViewHolder;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.adapters.viewholder.comments.ChildCommentViewHolder;
import awais.instagrabber.adapters.viewholder.comments.ParentCommentViewHolder;
import awais.instagrabber.databinding.ItemCommentBinding;
import awais.instagrabber.databinding.ItemCommentSmallBinding;
import awais.instagrabber.models.CommentModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.utils.LocaleUtils;
import awais.instagrabber.utils.TextUtils;
public final class CommentsAdapter extends RecyclerView.Adapter<CommentViewHolder> implements Filterable {
public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerView.ViewHolder> {
private static final int TYPE_PARENT = 1;
private static final int TYPE_CHILD = 2;
private CommentModel[] filteredCommentModels;
private LayoutInflater layoutInflater;
private final Map<Integer, Integer> positionTypeMap = new HashMap<>();
private final boolean isParent;
private final View.OnClickListener onClickListener;
private final MentionClickListener mentionClickListener;
private final CommentModel[] commentModels;
private final String[] quantityStrings = new String[2];
private final Filter filter = new Filter() {
@NonNull
// private final Filter filter = new Filter() {
// @NonNull
// @Override
// protected FilterResults performFiltering(final CharSequence filter) {
// final FilterResults results = new FilterResults();
// results.values = commentModels;
//
// final int commentsLen = commentModels == null ? 0 : commentModels.size();
// if (commentModels != null && commentsLen > 0 && !TextUtils.isEmpty(filter)) {
// final String query = filter.toString().toLowerCase();
// final ArrayList<CommentModel> filterList = new ArrayList<>(commentsLen);
//
// for (final CommentModel commentModel : commentModels) {
// final String commentText = commentModel.getText().toString().toLowerCase();
//
// if (commentText.contains(query)) filterList.add(commentModel);
// else {
// final List<CommentModel> childCommentModels = commentModel.getChildCommentModels();
// if (childCommentModels != null) {
// for (final CommentModel childCommentModel : childCommentModels) {
// final String childCommentText = childCommentModel.getText().toString().toLowerCase();
// if (childCommentText.contains(query)) filterList.add(commentModel);
// }
// }
// }
// }
// filterList.trimToSize();
// results.values = filterList.toArray(new CommentModel[0]);
// }
//
// return results;
// }
//
// @Override
// protected void publishResults(final CharSequence constraint, @NonNull final FilterResults results) {
// if (results.values instanceof List) {
// //noinspection unchecked
// filteredCommentModels = (List<CommentModel>) results.values;
// notifyDataSetChanged();
// }
// }
// };
private static final DiffUtil.ItemCallback<CommentModel> DIFF_CALLBACK = new DiffUtil.ItemCallback<CommentModel>() {
@Override
protected FilterResults performFiltering(final CharSequence filter) {
final FilterResults results = new FilterResults();
results.values = commentModels;
final int commentsLen = commentModels == null ? 0 : commentModels.length;
if (commentModels != null && commentsLen > 0 && !TextUtils.isEmpty(filter)) {
final String query = filter.toString().toLowerCase();
final ArrayList<CommentModel> filterList = new ArrayList<>(commentsLen);
for (final CommentModel commentModel : commentModels) {
final String commentText = commentModel.getText().toString().toLowerCase();
if (commentText.contains(query)) filterList.add(commentModel);
else {
final CommentModel[] childCommentModels = commentModel.getChildCommentModels();
if (childCommentModels != null) {
for (final CommentModel childCommentModel : childCommentModels) {
final String childCommentText = childCommentModel.getText().toString().toLowerCase();
if (childCommentText.contains(query)) filterList.add(commentModel);
}
}
}
}
filterList.trimToSize();
results.values = filterList.toArray(new CommentModel[0]);
}
return results;
public boolean areItemsTheSame(@NonNull final CommentModel oldItem, @NonNull final CommentModel newItem) {
return oldItem.getId().equals(newItem.getId());
}
@Override
protected void publishResults(final CharSequence constraint, @NonNull final FilterResults results) {
if (results.values instanceof CommentModel[]) {
filteredCommentModels = (CommentModel[]) results.values;
notifyDataSetChanged();
}
public boolean areContentsTheSame(@NonNull final CommentModel oldItem, @NonNull final CommentModel newItem) {
return oldItem.getId().equals(newItem.getId());
}
};
private final CommentCallback commentCallback;
private CommentModel selected;
private int selectedIndex;
public CommentsAdapter(final CommentModel[] commentModels,
final boolean isParent,
final View.OnClickListener onClickListener,
final MentionClickListener mentionClickListener) {
super();
this.commentModels = this.filteredCommentModels = commentModels;
this.isParent = isParent;
this.onClickListener = onClickListener;
this.mentionClickListener = mentionClickListener;
}
@Override
public Filter getFilter() {
return filter;
public CommentsAdapter(final CommentCallback commentCallback) {
super(DIFF_CALLBACK);
this.commentCallback = commentCallback;
}
@NonNull
@Override
public CommentViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
final Context context = parent.getContext();
if (quantityStrings[0] == null) quantityStrings[0] = context.getString(R.string.single_like);
if (quantityStrings[1] == null) quantityStrings[1] = context.getString(R.string.multiple_likes);
if (layoutInflater == null) layoutInflater = LayoutInflater.from(context);
final View view = layoutInflater.inflate(isParent ? R.layout.item_comment
: R.layout.item_comment_small,
parent,
false);
return new CommentViewHolder(view,
onClickListener,
mentionClickListener);
final LayoutInflater layoutInflater = LayoutInflater.from(context);
if (type == TYPE_PARENT) {
final ItemCommentBinding binding = ItemCommentBinding.inflate(layoutInflater, parent, false);
return new ParentCommentViewHolder(binding);
}
final ItemCommentSmallBinding binding = ItemCommentSmallBinding.inflate(layoutInflater, parent, false);
return new ChildCommentViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull final CommentViewHolder holder, final int position) {
final CommentModel commentModel = filteredCommentModels[position];
if (commentModel != null) {
holder.setCommentModel(commentModel);
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
final CommentModel commentModel = getItem(position);
if (commentModel == null) return;
final int type = getItemViewType(position);
final boolean selected = this.selected != null && this.selected.getId().equals(commentModel.getId());
if (type == TYPE_PARENT) {
final ParentCommentViewHolder viewHolder = (ParentCommentViewHolder) holder;
viewHolder.bind(commentModel, selected, commentCallback);
return;
}
final ChildCommentViewHolder viewHolder = (ChildCommentViewHolder) holder;
viewHolder.bind(commentModel, selected, commentCallback);
}
holder.setComment(commentModel.getText());
holder.setDate(commentModel.getDateTime());
holder.setLiked(commentModel.getLiked());
@Override
public void submitList(@Nullable final List<CommentModel> list) {
final List<CommentModel> flatList = flattenList(list);
super.submitList(flatList);
}
final long likes = commentModel.getLikes();
holder.setLikes(String.format(LocaleUtils.getCurrentLocale(), "%d %s", likes, quantityStrings[likes == 1 ? 0 : 1]));
@Override
public void submitList(@Nullable final List<CommentModel> list, @Nullable final Runnable commitCallback) {
final List<CommentModel> flatList = flattenList(list);
super.submitList(flatList, commitCallback);
}
final ProfileModel profileModel = commentModel.getProfileModel();
if (profileModel != null) {
holder.setUsername(profileModel.getUsername());
holder.getProfilePicView().setImageURI(profileModel.getSdProfilePic());
}
if (holder.isParent()) {
final CommentModel[] childCommentModels = commentModel.getChildCommentModels();
if (childCommentModels != null && childCommentModels.length > 0)
holder.setChildAdapter(new CommentsAdapter(childCommentModels, false, onClickListener, mentionClickListener));
else holder.hideChildComments();
private List<CommentModel> flattenList(final List<CommentModel> list) {
if (list == null) {
return Collections.emptyList();
}
final List<CommentModel> flatList = new ArrayList<>();
int lastCommentIndex = -1;
for (final CommentModel parent : list) {
lastCommentIndex++;
flatList.add(parent);
positionTypeMap.put(lastCommentIndex, TYPE_PARENT);
final List<CommentModel> children = parent.getChildCommentModels();
if (children != null) {
for (final CommentModel child : children) {
lastCommentIndex++;
flatList.add(child);
positionTypeMap.put(lastCommentIndex, TYPE_CHILD);
}
}
}
return flatList;
}
@Override
public int getItemCount() {
return filteredCommentModels == null ? 0 : filteredCommentModels.length;
public int getItemViewType(final int position) {
final Integer type = positionTypeMap.get(position);
if (type == null) {
return TYPE_PARENT;
}
return type;
}
public void setSelected(final CommentModel commentModel) {
this.selected = commentModel;
selectedIndex = getCurrentList().indexOf(commentModel);
notifyItemChanged(selectedIndex);
}
public void clearSelection() {
this.selected = null;
notifyItemChanged(selectedIndex);
}
public CommentModel getSelected() {
return selected;
}
public interface CommentCallback {
void onClick(final CommentModel comment);
void onHashtagClick(final String hashtag);
void onMentionClick(final String mention);
void onURLClick(final String url);
void onEmailClick(final String emailAddress);
}
}

View File

@ -107,6 +107,7 @@ public final class DirectMessageItemsAdapter extends ListAdapter<DirectItemModel
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);

View File

@ -1,57 +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 awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.DiscoverViewHolder;
import awais.instagrabber.models.DiscoverItemModel;
import awais.instagrabber.models.enums.MediaItemType;
public final class DiscoverAdapter extends MultiSelectListAdapter<DiscoverItemModel, DiscoverViewHolder> {
private static final DiffUtil.ItemCallback<DiscoverItemModel> diffCallback = new DiffUtil.ItemCallback<DiscoverItemModel>() {
@Override
public boolean areItemsTheSame(@NonNull final DiscoverItemModel oldItem, @NonNull final DiscoverItemModel newItem) {
return oldItem.getPostId().equals(newItem.getPostId());
}
@Override
public boolean areContentsTheSame(@NonNull final DiscoverItemModel oldItem, @NonNull final DiscoverItemModel newItem) {
return oldItem.getPostId().equals(newItem.getPostId());
}
};
public DiscoverAdapter(final OnItemClickListener<DiscoverItemModel> clickListener,
final OnItemLongClickListener<DiscoverItemModel> longClickListener) {
super(diffCallback, clickListener, longClickListener);
}
@NonNull
@Override
public DiscoverViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
return new DiscoverViewHolder(layoutInflater.inflate(R.layout.item_post, parent, false));
}
@Override
public void onBindViewHolder(@NonNull final DiscoverViewHolder holder, final int position) {
final DiscoverItemModel itemModel = getItem(position);
if (itemModel != null) {
itemModel.setPosition(position);
holder.itemView.setTag(itemModel);
holder.itemView.setOnClickListener(v -> getInternalOnItemClickListener().onItemClick(itemModel, position));
holder.itemView.setOnLongClickListener(v -> getInternalOnLongItemClickListener().onItemLongClick(itemModel, position));
final MediaItemType mediaType = itemModel.getItemType();
holder.typeIcon.setVisibility(
mediaType == MediaItemType.MEDIA_TYPE_VIDEO || mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
holder.typeIcon.setImageResource(mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? R.drawable.ic_slider_24 : R.drawable.ic_video_24);
holder.selectedView.setVisibility(itemModel.isSelected() ? View.VISIBLE : View.GONE);
holder.postImage.setImageURI(itemModel.getDisplayUrl());
}
}
}

View File

@ -0,0 +1,53 @@
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 awais.instagrabber.adapters.viewholder.TopicClusterViewHolder;
import awais.instagrabber.databinding.ItemDiscoverTopicBinding;
import awais.instagrabber.models.TopicCluster;
public class DiscoverTopicsAdapter extends ListAdapter<TopicCluster, TopicClusterViewHolder> {
private static final DiffUtil.ItemCallback<TopicCluster> DIFF_CALLBACK = new DiffUtil.ItemCallback<TopicCluster>() {
@Override
public boolean areItemsTheSame(@NonNull final TopicCluster oldItem, @NonNull final TopicCluster newItem) {
return oldItem.getId().equals(newItem.getId());
}
@Override
public boolean areContentsTheSame(@NonNull final TopicCluster oldItem, @NonNull final TopicCluster newItem) {
return oldItem.getCoverMedia().getDisplayUrl().equals(newItem.getCoverMedia().getDisplayUrl())
&& oldItem.getTitle().equals(newItem.getTitle());
}
};
private final OnTopicClickListener onTopicClickListener;
public DiscoverTopicsAdapter(final OnTopicClickListener onTopicClickListener) {
super(DIFF_CALLBACK);
this.onTopicClickListener = onTopicClickListener;
}
@NonNull
@Override
public TopicClusterViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final ItemDiscoverTopicBinding binding = ItemDiscoverTopicBinding.inflate(layoutInflater, parent, false);
return new TopicClusterViewHolder(binding, onTopicClickListener);
}
@Override
public void onBindViewHolder(@NonNull final TopicClusterViewHolder holder, final int position) {
final TopicCluster topicCluster = getItem(position);
holder.bind(topicCluster);
}
public interface OnTopicClickListener {
void onTopicClick(TopicCluster topicCluster, View root, View cover, View title, int titleColor, int backgroundColor);
}
}

View File

@ -1,111 +1,111 @@
package awais.instagrabber.adapters;
import android.content.Context;
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 awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder;
import awais.instagrabber.adapters.viewholder.feed.FeedPhotoViewHolder;
import awais.instagrabber.adapters.viewholder.feed.FeedSliderViewHolder;
import awais.instagrabber.adapters.viewholder.feed.FeedVideoViewHolder;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.databinding.ItemFeedPhotoBinding;
import awais.instagrabber.databinding.ItemFeedSliderBinding;
import awais.instagrabber.databinding.ItemFeedVideoBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Utils;
public final class FeedAdapter extends ListAdapter<FeedModel, FeedItemViewHolder> {
private static final String TAG = "FeedAdapter";
private final View.OnClickListener clickListener;
private final MentionClickListener mentionClickListener;
private final View.OnLongClickListener longClickListener = v -> {
final Object tag;
if (v instanceof RamboTextView && (tag = v.getTag()) instanceof FeedModel)
Utils.copyText(v.getContext(), ((FeedModel) tag).getPostCaption());
return true;
};
private static final DiffUtil.ItemCallback<FeedModel> diffCallback = new DiffUtil.ItemCallback<FeedModel>() {
@Override
public boolean areItemsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) {
return oldItem.getPostId().equals(newItem.getPostId());
}
@Override
public boolean areContentsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) {
return oldItem.getPostId().equals(newItem.getPostId());
}
};
public FeedAdapter(final View.OnClickListener clickListener,
final MentionClickListener mentionClickListener) {
super(diffCallback);
// private final static String ellipsize = "… more";
this.clickListener = clickListener;
this.mentionClickListener = mentionClickListener;
}
@NonNull
@Override
public FeedItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final Context context = parent.getContext();
final LayoutInflater layoutInflater = LayoutInflater.from(context);
final MediaItemType type = MediaItemType.valueOf(viewType);
switch (type) {
case MEDIA_TYPE_VIDEO: {
final ItemFeedVideoBinding binding = ItemFeedVideoBinding.inflate(layoutInflater, parent, false);
return new FeedVideoViewHolder(binding, mentionClickListener, clickListener, longClickListener);
}
case MEDIA_TYPE_SLIDER: {
final ItemFeedSliderBinding binding = ItemFeedSliderBinding.inflate(layoutInflater, parent, false);
return new FeedSliderViewHolder(binding, mentionClickListener, clickListener, longClickListener);
}
case MEDIA_TYPE_IMAGE:
default: {
final ItemFeedPhotoBinding binding = ItemFeedPhotoBinding.inflate(layoutInflater, parent, false);
return new FeedPhotoViewHolder(binding, mentionClickListener, clickListener, longClickListener);
}
}
}
@Override
public void onBindViewHolder(@NonNull final FeedItemViewHolder viewHolder, final int position) {
final FeedModel feedModel = getItem(position);
if (feedModel == null) {
return;
}
feedModel.setPosition(position);
viewHolder.bind(feedModel);
}
@Override
public int getItemViewType(final int position) {
return getItem(position).getItemType().getId();
}
@Override
public void onViewAttachedToWindow(@NonNull final FeedItemViewHolder holder) {
super.onViewAttachedToWindow(holder);
// Log.d(TAG, "attached holder: " + holder);
if (!(holder instanceof FeedSliderViewHolder)) return;
final FeedSliderViewHolder feedSliderViewHolder = (FeedSliderViewHolder) holder;
feedSliderViewHolder.startPlayingVideo();
}
@Override
public void onViewDetachedFromWindow(@NonNull final FeedItemViewHolder holder) {
super.onViewDetachedFromWindow(holder);
// Log.d(TAG, "detached holder: " + holder);
if (!(holder instanceof FeedSliderViewHolder)) return;
final FeedSliderViewHolder feedSliderViewHolder = (FeedSliderViewHolder) holder;
feedSliderViewHolder.stopPlayingVideo();
}
}
// package awais.instagrabber.adapters;
//
// import android.content.Context;
// 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 awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder;
// import awais.instagrabber.adapters.viewholder.feed.FeedPhotoViewHolder;
// import awais.instagrabber.adapters.viewholder.feed.FeedSliderViewHolder;
// import awais.instagrabber.adapters.viewholder.feed.FeedVideoViewHolder;
// import awais.instagrabber.customviews.RamboTextView;
// import awais.instagrabber.databinding.ItemFeedPhotoBinding;
// import awais.instagrabber.databinding.ItemFeedSliderBinding;
// import awais.instagrabber.databinding.ItemFeedVideoBinding;
// import awais.instagrabber.interfaces.MentionClickListener;
// import awais.instagrabber.models.FeedModel;
// import awais.instagrabber.models.enums.MediaItemType;
// import awais.instagrabber.utils.Utils;
//
// public final class FeedAdapter extends ListAdapter<FeedModel, FeedItemViewHolder> {
// private static final String TAG = "FeedAdapter";
// private final View.OnClickListener clickListener;
// private final MentionClickListener mentionClickListener;
// private final View.OnLongClickListener longClickListener = v -> {
// final Object tag;
// if (v instanceof RamboTextView && (tag = v.getTag()) instanceof FeedModel)
// Utils.copyText(v.getContext(), ((FeedModel) tag).getPostCaption());
// return true;
// };
//
// private static final DiffUtil.ItemCallback<FeedModel> diffCallback = new DiffUtil.ItemCallback<FeedModel>() {
// @Override
// public boolean areItemsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) {
// return oldItem.getPostId().equals(newItem.getPostId());
// }
//
// @Override
// public boolean areContentsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) {
// return oldItem.getPostId().equals(newItem.getPostId());
// }
// };
//
// public FeedAdapter(final View.OnClickListener clickListener,
// final MentionClickListener mentionClickListener) {
// super(diffCallback);
// // private final static String ellipsize = "… more";
// this.clickListener = clickListener;
// this.mentionClickListener = mentionClickListener;
// }
//
// @NonNull
// @Override
// public FeedItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
// final Context context = parent.getContext();
// final LayoutInflater layoutInflater = LayoutInflater.from(context);
// final MediaItemType type = MediaItemType.valueOf(viewType);
// switch (type) {
// case MEDIA_TYPE_VIDEO: {
// final ItemFeedVideoBinding binding = ItemFeedVideoBinding.inflate(layoutInflater, parent, false);
// return new FeedVideoViewHolder(binding, mentionClickListener, clickListener, longClickListener);
// }
// case MEDIA_TYPE_SLIDER: {
// final ItemFeedSliderBinding binding = ItemFeedSliderBinding.inflate(layoutInflater, parent, false);
// return new FeedSliderViewHolder(binding, mentionClickListener, clickListener, longClickListener);
// }
// case MEDIA_TYPE_IMAGE:
// default: {
// final ItemFeedPhotoBinding binding = ItemFeedPhotoBinding.inflate(layoutInflater, parent, false);
// return new FeedPhotoViewHolder(binding, mentionClickListener, clickListener, longClickListener);
// }
// }
// }
//
// @Override
// public void onBindViewHolder(@NonNull final FeedItemViewHolder viewHolder, final int position) {
// final FeedModel feedModel = getItem(position);
// if (feedModel == null) {
// return;
// }
// feedModel.setPosition(position);
// viewHolder.bind(feedModel, (feedModel1, view, postImage) -> {});
// }
//
// @Override
// public int getItemViewType(final int position) {
// return getItem(position).getItemType().getId();
// }
//
// @Override
// public void onViewAttachedToWindow(@NonNull final FeedItemViewHolder holder) {
// super.onViewAttachedToWindow(holder);
// // Log.d(TAG, "attached holder: " + holder);
// if (!(holder instanceof FeedSliderViewHolder)) return;
// final FeedSliderViewHolder feedSliderViewHolder = (FeedSliderViewHolder) holder;
// feedSliderViewHolder.startPlayingVideo();
// }
//
// @Override
// public void onViewDetachedFromWindow(@NonNull final FeedItemViewHolder holder) {
// super.onViewDetachedFromWindow(holder);
// // Log.d(TAG, "detached holder: " + holder);
// if (!(holder instanceof FeedSliderViewHolder)) return;
// final FeedSliderViewHolder feedSliderViewHolder = (FeedSliderViewHolder) holder;
// feedSliderViewHolder.stopPlayingVideo();
// }
// }

View File

@ -0,0 +1,241 @@
package awais.instagrabber.adapters;
import android.content.Context;
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 java.util.HashSet;
import java.util.Set;
import awais.instagrabber.adapters.viewholder.FeedGridItemViewHolder;
import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder;
import awais.instagrabber.adapters.viewholder.feed.FeedPhotoViewHolder;
import awais.instagrabber.adapters.viewholder.feed.FeedSliderViewHolder;
import awais.instagrabber.adapters.viewholder.feed.FeedVideoViewHolder;
import awais.instagrabber.databinding.ItemFeedGridBinding;
import awais.instagrabber.databinding.ItemFeedPhotoBinding;
import awais.instagrabber.databinding.ItemFeedSliderBinding;
import awais.instagrabber.databinding.ItemFeedVideoBinding;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.enums.MediaItemType;
public final class FeedAdapterV2 extends ListAdapter<FeedModel, RecyclerView.ViewHolder> {
private static final String TAG = "FeedAdapterV2";
private final FeedItemCallback feedItemCallback;
private final SelectionModeCallback selectionModeCallback;
private final Set<Integer> selectedPositions = new HashSet<>();
private final Set<FeedModel> selectedFeedModels = new HashSet<>();
private PostsLayoutPreferences layoutPreferences;
private boolean selectionModeActive = false;
private static final DiffUtil.ItemCallback<FeedModel> DIFF_CALLBACK = new DiffUtil.ItemCallback<FeedModel>() {
@Override
public boolean areItemsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) {
return oldItem.getPostId().equals(newItem.getPostId());
}
@Override
public boolean areContentsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) {
return oldItem.getPostId().equals(newItem.getPostId());
}
};
private final AdapterSelectionCallback adapterSelectionCallback = new AdapterSelectionCallback() {
@Override
public boolean onPostLongClick(final int position, final FeedModel feedModel) {
if (!selectionModeActive) {
selectionModeActive = true;
notifyDataSetChanged();
if (selectionModeCallback != null) {
selectionModeCallback.onSelectionStart();
}
}
selectedPositions.add(position);
selectedFeedModels.add(feedModel);
notifyItemChanged(position);
if (selectionModeCallback != null) {
selectionModeCallback.onSelectionChange(selectedFeedModels);
}
return true;
}
@Override
public void onPostClick(final int position, final FeedModel feedModel) {
if (!selectionModeActive) return;
if (selectedPositions.contains(position)) {
selectedPositions.remove(position);
selectedFeedModels.remove(feedModel);
} else {
selectedPositions.add(position);
selectedFeedModels.add(feedModel);
}
notifyItemChanged(position);
if (selectionModeCallback != null) {
selectionModeCallback.onSelectionChange(selectedFeedModels);
}
if (selectedPositions.isEmpty()) {
selectionModeActive = false;
notifyDataSetChanged();
if (selectionModeCallback != null) {
selectionModeCallback.onSelectionEnd();
}
}
}
};
public FeedAdapterV2(@NonNull final PostsLayoutPreferences layoutPreferences,
final FeedItemCallback feedItemCallback,
final SelectionModeCallback selectionModeCallback) {
super(DIFF_CALLBACK);
this.layoutPreferences = layoutPreferences;
this.feedItemCallback = feedItemCallback;
this.selectionModeCallback = selectionModeCallback;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final Context context = parent.getContext();
final LayoutInflater layoutInflater = LayoutInflater.from(context);
switch (layoutPreferences.getType()) {
case LINEAR:
return getLinearViewHolder(parent, layoutInflater, viewType);
case GRID:
case STAGGERED_GRID:
default:
final ItemFeedGridBinding binding = ItemFeedGridBinding.inflate(layoutInflater, parent, false);
return new FeedGridItemViewHolder(binding);
}
}
@NonNull
private RecyclerView.ViewHolder getLinearViewHolder(@NonNull final ViewGroup parent,
final LayoutInflater layoutInflater,
final int viewType) {
switch (MediaItemType.valueOf(viewType)) {
case MEDIA_TYPE_VIDEO: {
final ItemFeedVideoBinding binding = ItemFeedVideoBinding.inflate(layoutInflater, parent, false);
return new FeedVideoViewHolder(binding, feedItemCallback);
}
case MEDIA_TYPE_SLIDER: {
final ItemFeedSliderBinding binding = ItemFeedSliderBinding.inflate(layoutInflater, parent, false);
return new FeedSliderViewHolder(binding, feedItemCallback);
}
case MEDIA_TYPE_IMAGE:
default: {
final ItemFeedPhotoBinding binding = ItemFeedPhotoBinding.inflate(layoutInflater, parent, false);
return new FeedPhotoViewHolder(binding, feedItemCallback);
}
}
}
@Override
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder viewHolder, final int position) {
final FeedModel feedModel = getItem(position);
if (feedModel == null) return;
switch (layoutPreferences.getType()) {
case LINEAR:
((FeedItemViewHolder) viewHolder).bind(feedModel);
break;
case GRID:
case STAGGERED_GRID:
default:
((FeedGridItemViewHolder) viewHolder).bind(position,
feedModel,
layoutPreferences,
feedItemCallback,
adapterSelectionCallback,
selectionModeActive,
selectedPositions.contains(position));
}
}
@Override
public int getItemViewType(final int position) {
return getItem(position).getItemType().getId();
}
public void setLayoutPreferences(@NonNull final PostsLayoutPreferences layoutPreferences) {
this.layoutPreferences = layoutPreferences;
}
public void endSelection() {
if (!selectionModeActive) return;
selectionModeActive = false;
selectedPositions.clear();
selectedFeedModels.clear();
notifyDataSetChanged();
if (selectionModeCallback != null) {
selectionModeCallback.onSelectionEnd();
}
}
// @Override
// public void onViewAttachedToWindow(@NonNull final FeedItemViewHolder holder) {
// super.onViewAttachedToWindow(holder);
// // Log.d(TAG, "attached holder: " + holder);
// if (!(holder instanceof FeedSliderViewHolder)) return;
// final FeedSliderViewHolder feedSliderViewHolder = (FeedSliderViewHolder) holder;
// feedSliderViewHolder.startPlayingVideo();
// }
//
// @Override
// public void onViewDetachedFromWindow(@NonNull final FeedItemViewHolder holder) {
// super.onViewDetachedFromWindow(holder);
// // Log.d(TAG, "detached holder: " + holder);
// if (!(holder instanceof FeedSliderViewHolder)) return;
// final FeedSliderViewHolder feedSliderViewHolder = (FeedSliderViewHolder) holder;
// feedSliderViewHolder.stopPlayingVideo();
// }
public interface FeedItemCallback {
void onPostClick(final FeedModel feedModel,
final View profilePicView,
final View mainPostImage);
void onProfilePicClick(final FeedModel feedModel,
final View profilePicView);
void onNameClick(final FeedModel feedModel,
final View profilePicView);
void onLocationClick(final FeedModel feedModel);
void onMentionClick(final String mention);
void onHashtagClick(final String hashtag);
void onCommentsClick(final FeedModel feedModel);
void onDownloadClick(final FeedModel feedModel, final int childPosition);
void onEmailClick(final String emailId);
void onURLClick(final String url);
void onSliderClick(FeedModel feedModel, int position);
}
public interface AdapterSelectionCallback {
boolean onPostLongClick(final int position, FeedModel feedModel);
void onPostClick(final int position, FeedModel feedModel);
}
public interface SelectionModeCallback {
void onSelectionStart();
void onSelectionChange(final Set<FeedModel> selectedFeedModels);
void onSelectionEnd();
}
}

View File

@ -0,0 +1,40 @@
package awais.instagrabber.adapters;
import android.view.View;
import awais.instagrabber.models.FeedModel;
public class FeedItemCallbackAdapter implements FeedAdapterV2.FeedItemCallback {
@Override
public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {}
@Override
public void onProfilePicClick(final FeedModel feedModel, final View profilePicView) {}
@Override
public void onNameClick(final FeedModel feedModel, final View profilePicView) {}
@Override
public void onLocationClick(final FeedModel feedModel) {}
@Override
public void onMentionClick(final String mention) {}
@Override
public void onHashtagClick(final String hashtag) {}
@Override
public void onCommentsClick(final FeedModel feedModel) {}
@Override
public void onDownloadClick(final FeedModel feedModel, final int childPosition) {}
@Override
public void onEmailClick(final String emailId) {}
@Override
public void onURLClick(final String url) {}
@Override
public void onSliderClick(final FeedModel feedModel, final int position) {}
}

View File

@ -12,8 +12,8 @@ public abstract class MultiSelectListAdapter<T extends MultiSelectListAdapter.Se
ListAdapter<T, VH> {
private boolean isSelecting;
private final OnItemClickListener<T> internalOnItemClickListener;
private final OnItemLongClickListener<T> internalOnLongItemClickListener;
private OnItemClickListener<T> internalOnItemClickListener;
private OnItemLongClickListener<T> internalOnLongItemClickListener;
private final List<T> selectedItems = new ArrayList<>();

View File

@ -1,77 +1,75 @@
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.Arrays;
import awais.instagrabber.adapters.viewholder.PostViewerViewHolder;
import awais.instagrabber.databinding.ItemFullPostViewBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.ViewerPostModelWrapper;
public class PostViewAdapter extends ListAdapter<ViewerPostModelWrapper, PostViewerViewHolder> {
private final OnPostViewChildViewClickListener clickListener;
private final OnPostCaptionLongClickListener longClickListener;
private final MentionClickListener mentionClickListener;
private static final DiffUtil.ItemCallback<ViewerPostModelWrapper> diffCallback = new DiffUtil.ItemCallback<ViewerPostModelWrapper>() {
@Override
public boolean areItemsTheSame(@NonNull final ViewerPostModelWrapper oldItem,
@NonNull final ViewerPostModelWrapper newItem) {
return oldItem.getPosition() == newItem.getPosition();
}
@Override
public boolean areContentsTheSame(@NonNull final ViewerPostModelWrapper oldItem,
@NonNull final ViewerPostModelWrapper newItem) {
return Arrays.equals(oldItem.getViewerPostModels(), newItem.getViewerPostModels());
}
};
public PostViewAdapter(final OnPostViewChildViewClickListener clickListener,
final OnPostCaptionLongClickListener longClickListener,
final MentionClickListener mentionClickListener) {
super(diffCallback);
this.clickListener = clickListener;
this.longClickListener = longClickListener;
this.mentionClickListener = mentionClickListener;
}
@Override
public void onViewDetachedFromWindow(@NonNull final PostViewerViewHolder holder) {
holder.stopPlayingVideo();
}
@NonNull
@Override
public PostViewerViewHolder onCreateViewHolder(@NonNull final ViewGroup parent,
final int viewType) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final ItemFullPostViewBinding binding = ItemFullPostViewBinding
.inflate(layoutInflater, parent, false);
return new PostViewerViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull final PostViewerViewHolder holder, final int position) {
final ViewerPostModelWrapper item = getItem(position);
holder.bind(item, position, clickListener, longClickListener, mentionClickListener);
}
public interface OnPostViewChildViewClickListener {
void onClick(View v,
ViewerPostModelWrapper viewerPostModelWrapper,
int postPosition,
int childPosition);
}
public interface OnPostCaptionLongClickListener {
void onLongClick(String text);
}
}
// 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 awais.instagrabber.adapters.viewholder.PostViewerViewHolder;
// import awais.instagrabber.databinding.ItemFullPostViewBinding;
// import awais.instagrabber.interfaces.MentionClickListener;
// import awais.instagrabber.models.ViewerPostModelWrapper;
//
// public class PostViewAdapter extends ListAdapter<ViewerPostModelWrapper, PostViewerViewHolder> {
// private final OnPostViewChildViewClickListener clickListener;
// private final OnPostCaptionLongClickListener longClickListener;
// private final MentionClickListener mentionClickListener;
//
// private static final DiffUtil.ItemCallback<ViewerPostModelWrapper> diffCallback = new DiffUtil.ItemCallback<ViewerPostModelWrapper>() {
// @Override
// public boolean areItemsTheSame(@NonNull final ViewerPostModelWrapper oldItem,
// @NonNull final ViewerPostModelWrapper newItem) {
// return oldItem.getPosition() == newItem.getPosition();
// }
//
// @Override
// public boolean areContentsTheSame(@NonNull final ViewerPostModelWrapper oldItem,
// @NonNull final ViewerPostModelWrapper newItem) {
// return oldItem.getViewerPostModels().equals(newItem.getViewerPostModels());
// }
// };
//
// public PostViewAdapter(final OnPostViewChildViewClickListener clickListener,
// final OnPostCaptionLongClickListener longClickListener,
// final MentionClickListener mentionClickListener) {
// super(diffCallback);
// this.clickListener = clickListener;
// this.longClickListener = longClickListener;
// this.mentionClickListener = mentionClickListener;
// }
//
// @Override
// public void onViewDetachedFromWindow(@NonNull final PostViewerViewHolder holder) {
// holder.stopPlayingVideo();
// }
//
// @NonNull
// @Override
// public PostViewerViewHolder onCreateViewHolder(@NonNull final ViewGroup parent,
// final int viewType) {
// final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
// final ItemFullPostViewBinding binding = ItemFullPostViewBinding
// .inflate(layoutInflater, parent, false);
// return new PostViewerViewHolder(binding);
// }
//
// @Override
// public void onBindViewHolder(@NonNull final PostViewerViewHolder holder, final int position) {
// final ViewerPostModelWrapper item = getItem(position);
// holder.bind(item, position, clickListener, longClickListener, mentionClickListener);
// }
//
// public interface OnPostViewChildViewClickListener {
// void onClick(View v,
// ViewerPostModelWrapper viewerPostModelWrapper,
// int postPosition,
// int childPosition);
// }
//
// public interface OnPostCaptionLongClickListener {
// void onLongClick(String text);
// }
// }

View File

@ -15,6 +15,7 @@ import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
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;
@ -171,8 +172,9 @@ public class PostViewerChildAdapter extends ListAdapter<ViewerPostModel, PostVie
if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
player.setVolume(vol);
player.setPlayWhenReady(Utils.settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS));
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(itemView.getContext(), "instagram"))
.createMediaSource(Uri.parse(item.getDisplayUrl()));
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(
new DefaultDataSourceFactory(itemView.getContext(), "instagram")
).createMediaSource(MediaItem.fromUri(item.getDisplayUrl()));
// mediaSource.addEventListener(new Handler(), new MediaSourceEventListener() {
// @Override
// public void onLoadCompleted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
@ -194,7 +196,8 @@ public class PostViewerChildAdapter extends ListAdapter<ViewerPostModel, PostVie
// viewerBinding.progressView.setVisibility(View.GONE);
// }
// });
player.prepare(mediaSource);
player.setMediaSource(mediaSource);
player.prepare();
player.setVolume(vol);
// viewerBinding.bottomPanel.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24 : R.drawable.ic_volume_off_24);
// viewerBinding.bottomPanel.btnMute.setOnClickListener(onClickListener);

View File

@ -0,0 +1,15 @@
package awais.instagrabber.adapters;
public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback {
@Override
public void onThumbnailLoaded(final int position) {}
@Override
public void onItemClicked(final int position) {}
@Override
public void onPlayerPlay(final int position) {}
@Override
public void onPlayerPause(final int position) {}
}

View File

@ -0,0 +1,152 @@
package awais.instagrabber.adapters;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import awais.instagrabber.adapters.viewholder.SliderItemViewHolder;
import awais.instagrabber.adapters.viewholder.SliderPhotoViewHolder;
import awais.instagrabber.adapters.viewholder.SliderVideoViewHolder;
import awais.instagrabber.customviews.VerticalDragHelper;
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding;
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.enums.MediaItemType;
public final class SliderItemsAdapter extends ListAdapter<PostChild, SliderItemViewHolder> {
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener;
private final boolean loadVideoOnItemClick;
private final SliderCallback sliderCallback;
private final awais.instagrabber.databinding.LayoutExoCustomControlsBinding controlsBinding;
private static final DiffUtil.ItemCallback<PostChild> DIFF_CALLBACK = new DiffUtil.ItemCallback<PostChild>() {
@Override
public boolean areItemsTheSame(@NonNull final PostChild oldItem, @NonNull final PostChild newItem) {
return oldItem.getPostId().equals(newItem.getPostId());
}
@Override
public boolean areContentsTheSame(@NonNull final PostChild oldItem, @NonNull final PostChild newItem) {
return oldItem.getPostId().equals(newItem.getPostId());
}
};
public SliderItemsAdapter(final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener,
final LayoutExoCustomControlsBinding controlsBinding,
final boolean loadVideoOnItemClick,
final SliderCallback sliderCallback) {
super(DIFF_CALLBACK);
this.onVerticalDragListener = onVerticalDragListener;
this.loadVideoOnItemClick = loadVideoOnItemClick;
this.sliderCallback = sliderCallback;
this.controlsBinding = controlsBinding;
}
@NonNull
@Override
public SliderItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
final MediaItemType mediaItemType = MediaItemType.valueOf(viewType);
switch (mediaItemType) {
case MEDIA_TYPE_VIDEO: {
final LayoutVideoPlayerWithThumbnailBinding binding = LayoutVideoPlayerWithThumbnailBinding.inflate(inflater, parent, false);
return new SliderVideoViewHolder(binding, onVerticalDragListener, controlsBinding, loadVideoOnItemClick);
}
case MEDIA_TYPE_IMAGE:
default:
final ItemSliderPhotoBinding binding = ItemSliderPhotoBinding.inflate(inflater, parent, false);
return new SliderPhotoViewHolder(binding, onVerticalDragListener);
}
}
@Override
public void onBindViewHolder(@NonNull final SliderItemViewHolder holder, final int position) {
final PostChild model = getItem(position);
holder.bind(model, position, sliderCallback);
}
@Override
public int getItemViewType(final int position) {
final PostChild viewerPostModel = getItem(position);
return viewerPostModel.getItemType().getId();
}
// @NonNull
// @Override
// public Object instantiateItem(@NonNull final ViewGroup container, final int position) {
// final Context context = container.getContext();
// final ViewerPostModel sliderItem = sliderItems.get(position);
//
// if (sliderItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
// final ViewSwitcher viewSwitcher = createViewSwitcher(context, position, sliderItem.getThumbnailUrl(), sliderItem.getDisplayUrl());
// container.addView(viewSwitcher);
// return viewSwitcher;
// }
// final GenericDraweeHierarchy hierarchy = GenericDraweeHierarchyBuilder.newInstance(container.getResources())
// .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
// .build();
// final SimpleDraweeView photoView = new SimpleDraweeView(context, hierarchy);
// photoView.setLayoutParams(layoutParams);
// final ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(sliderItem.getDisplayUrl()))
// .setLocalThumbnailPreviewsEnabled(true)
// .setProgressiveRenderingEnabled(true)
// .build();
// photoView.setImageRequest(imageRequest);
// container.addView(photoView);
// return photoView;
// }
// @NonNull
// private ViewSwitcher createViewSwitcher(final Context context,
// final int position,
// final String thumbnailUrl,
// final String displayUrl) {
//
// final ViewSwitcher viewSwitcher = new ViewSwitcher(context);
// viewSwitcher.setLayoutParams(layoutParams);
//
// final FrameLayout frameLayout = new FrameLayout(context);
// frameLayout.setLayoutParams(layoutParams);
//
// final GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(context.getResources())
// .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
// .build();
// final SimpleDraweeView simpleDraweeView = new SimpleDraweeView(context, hierarchy);
// simpleDraweeView.setLayoutParams(layoutParams);
// simpleDraweeView.setImageURI(thumbnailUrl);
// frameLayout.addView(simpleDraweeView);
//
// final AppCompatImageView imageView = new AppCompatImageView(context);
// final int px = Utils.convertDpToPx(50);
// final FrameLayout.LayoutParams playButtonLayoutParams = new FrameLayout.LayoutParams(px, px);
// playButtonLayoutParams.gravity = Gravity.CENTER;
// imageView.setLayoutParams(playButtonLayoutParams);
// imageView.setImageResource(R.drawable.exo_icon_play);
// frameLayout.addView(imageView);
//
// viewSwitcher.addView(frameLayout);
//
// final PlayerView playerView = new PlayerView(context);
// viewSwitcher.addView(playerView);
// if (shouldAutoPlay && position == 0) {
// loadPlayer(context, position, displayUrl, viewSwitcher, factory, playerChangeListener);
// } else
// frameLayout.setOnClickListener(v -> loadPlayer(context, position, displayUrl, viewSwitcher, factory, playerChangeListener));
// return viewSwitcher;
// }
public interface SliderCallback {
void onThumbnailLoaded(int position);
void onItemClicked(int position);
void onPlayerPlay(int position);
void onPlayerPause(int position);
}
}

View File

@ -1,95 +0,0 @@
package awais.instagrabber.adapters.viewholder;
import android.text.Spannable;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.facebook.drawee.view.SimpleDraweeView;
import awais.instagrabber.R;
import awais.instagrabber.adapters.CommentsAdapter;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.CommentModel;
public final class CommentViewHolder extends RecyclerView.ViewHolder {
private final MentionClickListener mentionClickListener;
private final RecyclerView rvChildComments;
private final SimpleDraweeView ivProfilePic;
private final TextView tvUsername;
private final TextView tvDate;
private final TextView tvComment;
private final TextView tvLikes;
private final View container;
public CommentViewHolder(@NonNull final View itemView,
final View.OnClickListener onClickListener,
final MentionClickListener mentionClickListener) {
super(itemView);
container = itemView.findViewById(R.id.container);
if (onClickListener != null) container.setOnClickListener(onClickListener);
this.mentionClickListener = mentionClickListener;
ivProfilePic = itemView.findViewById(R.id.ivProfilePic);
tvUsername = itemView.findViewById(R.id.tvUsername);
tvDate = itemView.findViewById(R.id.tvDate);
tvLikes = itemView.findViewById(R.id.tvLikes);
tvComment = itemView.findViewById(R.id.tvComment);
tvUsername.setSelected(true);
tvDate.setSelected(true);
rvChildComments = itemView.findViewById(R.id.rvChildComments);
}
public final SimpleDraweeView getProfilePicView() {
return ivProfilePic;
}
public final boolean isParent() {
return rvChildComments != null;
}
public final void setCommentModel(final CommentModel commentModel) {
if (container != null) container.setTag(commentModel);
}
public final void setUsername(final String username) {
if (tvUsername != null) tvUsername.setText(username);
}
public final void setDate(final String date) {
if (tvDate != null) tvDate.setText(date);
}
public final void setLikes(final String likes) {
if (tvLikes != null) tvLikes.setText(likes);
}
public final void setLiked(final boolean liked) {
if (liked) container.setBackgroundColor(0x40FF69B4);
}
public final void setComment(final CharSequence comment) {
if (tvComment != null) {
tvComment.setText(comment, comment instanceof Spannable ? TextView.BufferType.SPANNABLE : TextView.BufferType.NORMAL);
((RamboTextView) tvComment).setMentionClickListener(mentionClickListener);
}
}
public final void setChildAdapter(final CommentsAdapter adapter) {
if (isParent()) {
rvChildComments.setAdapter(adapter);
rvChildComments.setVisibility(View.VISIBLE);
}
}
public final void hideChildComments() {
if (isParent()) rvChildComments.setVisibility(View.GONE);
}
}

View File

@ -97,6 +97,7 @@ public final class DirectMessageInboxItemViewHolder extends RecyclerView.ViewHol
case MEDIA_SHARE:
case RAVEN_MEDIA:
case CLIP:
case FELIX_SHARE:
messageText = context.getString(R.string.direct_messages_sent_media);
break;
case ACTION_LOG:

View File

@ -0,0 +1,161 @@
package awais.instagrabber.adapters.viewholder;
import android.content.res.ColorStateList;
import android.net.Uri;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.DimenRes;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.asyncs.DownloadedCheckerAsyncTask;
import awais.instagrabber.databinding.ItemFeedGridBinding;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.utils.TextUtils;
import static awais.instagrabber.models.PostsLayoutPreferences.PostsLayoutType.STAGGERED_GRID;
public class FeedGridItemViewHolder extends RecyclerView.ViewHolder {
private final ItemFeedGridBinding binding;
public FeedGridItemViewHolder(@NonNull final ItemFeedGridBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(final int position,
@NonNull final FeedModel feedModel,
@NonNull final PostsLayoutPreferences layoutPreferences,
final FeedAdapterV2.FeedItemCallback feedItemCallback,
final FeedAdapterV2.AdapterSelectionCallback adapterSelectionCallback,
final boolean selectionModeActive,
final boolean selected) {
itemView.setOnClickListener(v -> {
if (!selectionModeActive && feedItemCallback != null) {
feedItemCallback.onPostClick(feedModel, binding.profilePic, binding.postImage);
return;
}
if (selectionModeActive && adapterSelectionCallback != null) {
adapterSelectionCallback.onPostClick(position, feedModel);
}
});
if (adapterSelectionCallback != null) {
itemView.setOnLongClickListener(v -> adapterSelectionCallback.onPostLongClick(position, feedModel));
}
binding.selectedView.setVisibility(selected ? View.VISIBLE : View.GONE);
// for rounded borders (clip view to background shape)
itemView.setClipToOutline(layoutPreferences.getHasRoundedCorners());
if (layoutPreferences.getType() == STAGGERED_GRID) {
final float aspectRatio = (float) feedModel.getImageWidth() / feedModel.getImageHeight();
binding.postImage.setAspectRatio(aspectRatio);
} else {
binding.postImage.setAspectRatio(1);
}
if (layoutPreferences.isAvatarVisible()) {
binding.profilePic.setVisibility(View.VISIBLE);
binding.profilePic.setImageURI(feedModel.getProfileModel().getSdProfilePic());
final ViewGroup.LayoutParams layoutParams = binding.profilePic.getLayoutParams();
@DimenRes final int dimenRes;
switch (layoutPreferences.getProfilePicSize()) {
case SMALL:
dimenRes = R.dimen.profile_pic_size_small;
break;
case TINY:
dimenRes = R.dimen.profile_pic_size_tiny;
break;
default:
case REGULAR:
dimenRes = R.dimen.profile_pic_size_regular;
break;
}
final int dimensionPixelSize = itemView.getResources().getDimensionPixelSize(dimenRes);
layoutParams.width = dimensionPixelSize;
layoutParams.height = dimensionPixelSize;
binding.profilePic.requestLayout();
} else {
binding.profilePic.setVisibility(View.GONE);
}
if (layoutPreferences.isNameVisible()) {
binding.name.setVisibility(View.VISIBLE);
binding.name.setText(feedModel.getProfileModel().getUsername());
} else {
binding.name.setVisibility(View.GONE);
}
String thumbnailUrl = null;
final int typeIconRes;
switch (feedModel.getItemType()) {
case MEDIA_TYPE_IMAGE:
typeIconRes = -1;
thumbnailUrl = feedModel.getThumbnailUrl();
break;
case MEDIA_TYPE_VIDEO:
thumbnailUrl = feedModel.getThumbnailUrl();
typeIconRes = R.drawable.exo_icon_play;
break;
case MEDIA_TYPE_SLIDER:
final List<PostChild> sliderItems = feedModel.getSliderItems();
if (sliderItems != null) {
thumbnailUrl = sliderItems.get(0).getThumbnailUrl();
}
typeIconRes = R.drawable.ic_checkbox_multiple_blank_stroke;
break;
default:
typeIconRes = -1;
thumbnailUrl = null;
}
if (TextUtils.isEmpty(thumbnailUrl)) {
binding.postImage.setController(null);
return;
}
if (typeIconRes <= 0) {
binding.typeIcon.setVisibility(View.GONE);
} else {
binding.typeIcon.setVisibility(View.VISIBLE);
binding.typeIcon.setImageResource(typeIconRes);
}
final ImageRequest requestBuilder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(thumbnailUrl))
.setLocalThumbnailPreviewsEnabled(true)
.setProgressiveRenderingEnabled(true)
.build();
final PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder()
.setImageRequest(requestBuilder)
.setOldController(binding.postImage.getController());
binding.postImage.setController(builder.build());
final DownloadedCheckerAsyncTask task = new DownloadedCheckerAsyncTask(result -> {
final List<Boolean> checkList = result.get(feedModel.getPostId());
if (checkList == null || checkList.isEmpty()) {
return;
}
switch (feedModel.getItemType()) {
case MEDIA_TYPE_IMAGE:
case MEDIA_TYPE_VIDEO:
binding.downloaded.setVisibility(checkList.get(0) ? View.VISIBLE : View.GONE);
binding.downloaded.setImageTintList(ColorStateList.valueOf(itemView.getResources().getColor(R.color.green_A400)));
break;
case MEDIA_TYPE_SLIDER:
binding.downloaded.setVisibility(checkList.get(0) ? View.VISIBLE : View.GONE);
boolean allDownloaded = checkList.size() == feedModel.getSliderItems().size();
if (allDownloaded) {
allDownloaded = checkList.stream().allMatch(downloaded -> downloaded);
}
binding.downloaded.setImageTintList(ColorStateList.valueOf(itemView.getResources().getColor(
allDownloaded ? R.color.green_A400 : R.color.yellow_400)));
break;
default:
}
});
task.execute(feedModel);
}
}

View File

@ -42,6 +42,9 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder {
text = R.string.mention_notif;
subtext = model.getText();
break;
case TAGGED:
text = R.string.tagged_notif;
break;
case FOLLOW:
text = R.string.follow_notif;
break;

View File

@ -19,11 +19,11 @@ public final class PostMediaViewHolder extends RecyclerView.ViewHolder {
public void bind(final ViewerPostModel model, final int position, final View.OnClickListener clickListener) {
if (model == null) return;
model.setPosition(position);
// 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.getSliderDisplayUrl());
binding.icon.setImageURI(model.getDisplayUrl());
}
}

View File

@ -25,7 +25,7 @@ public final class PostViewHolder extends RecyclerView.ViewHolder {
final OnItemClickListener<PostModel> clickListener,
final OnItemLongClickListener<PostModel> longClickListener) {
if (postModel == null) return;
postModel.setPosition(position);
// postModel.setPosition(position);
itemView.setOnClickListener(v -> clickListener.onItemClick(postModel, position));
itemView.setOnLongClickListener(v -> longClickListener.onItemLongClick(postModel, position));

View File

@ -1,239 +1,240 @@
package awais.instagrabber.adapters.viewholder;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.ui.PlayerView;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.adapters.PostViewAdapter.OnPostCaptionLongClickListener;
import awais.instagrabber.adapters.PostViewAdapter.OnPostViewChildViewClickListener;
import awais.instagrabber.adapters.PostViewerChildAdapter;
import awais.instagrabber.databinding.ItemFullPostViewBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.ViewerPostModelWrapper;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
public class PostViewerViewHolder extends RecyclerView.ViewHolder {
private static final String TAG = "PostViewerViewHolder";
private final ItemFullPostViewBinding binding;
private int currentChildPosition;
public PostViewerViewHolder(@NonNull final ItemFullPostViewBinding binding) {
super(binding.getRoot());
this.binding = binding;
binding.topPanel.viewStoryPost.setVisibility(View.GONE);
}
public void bind(final ViewerPostModelWrapper wrapper,
final int position,
final OnPostViewChildViewClickListener clickListener,
final OnPostCaptionLongClickListener longClickListener,
final MentionClickListener mentionClickListener) {
if (wrapper == null) return;
final ViewerPostModel[] items = wrapper.getViewerPostModels();
if (items == null || items.length <= 0) return;
if (items[0] == null) return;
final PostViewerChildAdapter adapter = new PostViewerChildAdapter();
binding.mediaViewPager.setAdapter(adapter);
final ViewerPostModel firstPost = items[0];
setPostInfo(firstPost, mentionClickListener);
setMediaItems(items, adapter);
setupListeners(wrapper,
position,
clickListener,
longClickListener,
mentionClickListener,
firstPost.getLocation());
}
private void setPostInfo(final ViewerPostModel firstPost,
final MentionClickListener mentionClickListener) {
final ProfileModel profileModel = firstPost.getProfileModel();
if (profileModel == null) return;
binding.topPanel.title.setText(profileModel.getUsername());
final String locationName = firstPost.getLocationName();
if (!TextUtils.isEmpty(locationName)) {
binding.topPanel.location.setVisibility(View.VISIBLE);
binding.topPanel.location.setText(locationName);
} else binding.topPanel.location.setVisibility(View.GONE);
binding.topPanel.ivProfilePic.setImageURI(profileModel.getSdProfilePic());
binding.bottomPanel.commentsCount.setText(String.valueOf(firstPost.getCommentsCount()));
final CharSequence postCaption = firstPost.getPostCaption();
if (TextUtils.hasMentions(postCaption)) {
binding.bottomPanel.viewerCaption.setMentionClickListener(mentionClickListener);
binding.bottomPanel.viewerCaption
.setText(TextUtils.getMentionText(postCaption), TextView.BufferType.SPANNABLE);
} else {
binding.bottomPanel.viewerCaption.setMentionClickListener(null);
binding.bottomPanel.viewerCaption.setText(postCaption);
}
binding.bottomPanel.tvPostDate.setText(firstPost.getPostDate());
setupLikes(firstPost);
setupSave(firstPost);
}
private void setupLikes(final ViewerPostModel firstPost) {
final boolean liked = firstPost.getLike();
final long likeCount = firstPost.getLikes();
final Resources resources = itemView.getContext().getResources();
if (liked) {
final String unlikeString = resources.getString(R.string.unlike, String.valueOf(likeCount));
binding.btnLike.setText(unlikeString);
ViewCompat.setBackgroundTintList(binding.btnLike,
ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_pink_background)));
} else {
final String likeString = resources.getString(R.string.like, String.valueOf(likeCount));
binding.btnLike.setText(likeString);
ViewCompat.setBackgroundTintList(binding.btnLike,
ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_lightpink_background)));
}
}
private void setupSave(final ViewerPostModel firstPost) {
final boolean saved = firstPost.getBookmark();
if (saved) {
binding.btnBookmark.setText(R.string.unbookmark);
ViewCompat.setBackgroundTintList(binding.btnBookmark,
ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_orange_background)));
} else {
binding.btnBookmark.setText(R.string.bookmark);
ViewCompat.setBackgroundTintList(
binding.btnBookmark,
ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_lightorange_background)));
}
}
private void setupListeners(final ViewerPostModelWrapper wrapper,
final int position,
final OnPostViewChildViewClickListener clickListener,
final OnPostCaptionLongClickListener longClickListener,
final MentionClickListener mentionClickListener,
final String location) {
final View.OnClickListener onClickListener = v -> clickListener
.onClick(v, wrapper, position, currentChildPosition);
binding.bottomPanel.btnComments.setOnClickListener(onClickListener);
binding.topPanel.title.setOnClickListener(onClickListener);
binding.topPanel.ivProfilePic.setOnClickListener(onClickListener);
binding.bottomPanel.btnDownload.setOnClickListener(onClickListener);
binding.bottomPanel.viewerCaption.setOnClickListener(onClickListener);
binding.btnLike.setOnClickListener(onClickListener);
binding.btnBookmark.setOnClickListener(onClickListener);
binding.bottomPanel.viewerCaption.setOnLongClickListener(v -> {
longClickListener.onLongClick(binding.bottomPanel.viewerCaption.getText().toString());
return true;
});
if (!TextUtils.isEmpty(location)) {
binding.topPanel.location.setOnClickListener(v -> mentionClickListener
.onClick(binding.topPanel.location, location, false, true));
}
}
private void setMediaItems(final ViewerPostModel[] items,
final PostViewerChildAdapter adapter) {
final List<ViewerPostModel> filteredList = new ArrayList<>();
for (final ViewerPostModel model : items) {
final MediaItemType itemType = model.getItemType();
if (itemType == MediaItemType.MEDIA_TYPE_VIDEO || itemType == MediaItemType.MEDIA_TYPE_IMAGE) {
filteredList.add(model);
}
}
binding.mediaCounter.setVisibility(filteredList.size() > 1 ? View.VISIBLE : View.GONE);
final String counter = "1/" + filteredList.size();
binding.mediaCounter.setText(counter);
adapter.submitList(filteredList);
binding.mediaViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(final int position) {
if (filteredList.size() <= 0 || position >= filteredList.size()) return;
currentChildPosition = position;
final String counter = (position + 1) + "/" + filteredList.size();
binding.mediaCounter.setText(counter);
final ViewerPostModel viewerPostModel = filteredList.get(position);
if (viewerPostModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
setVideoDetails(viewerPostModel);
setVolumeListener(position);
return;
}
setImageDetails();
}
});
}
private void setVolumeListener(final int position) {
binding.bottomPanel.btnMute.setOnClickListener(v -> {
try {
final RecyclerView.ViewHolder viewHolder = ((RecyclerView) binding.mediaViewPager
.getChildAt(0)).findViewHolderForAdapterPosition(position);
if (viewHolder != null) {
final View itemView = viewHolder.itemView;
if (itemView instanceof PlayerView) {
final SimpleExoPlayer player = (SimpleExoPlayer) ((PlayerView) itemView)
.getPlayer();
if (player == null) return;
final float vol = player.getVolume() == 0f ? 1f : 0f;
player.setVolume(vol);
binding.bottomPanel.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24
: R.drawable.ic_volume_off_24);
Utils.sessionVolumeFull = vol == 1f;
}
}
} catch (Exception e) {
Log.e(TAG, "Error", e);
}
});
}
private void setImageDetails() {
binding.bottomPanel.btnMute.setVisibility(View.GONE);
binding.bottomPanel.videoViewsContainer.setVisibility(View.GONE);
}
private void setVideoDetails(final ViewerPostModel viewerPostModel) {
binding.bottomPanel.btnMute.setVisibility(View.VISIBLE);
final long videoViews = viewerPostModel.getVideoViews();
if (videoViews < 0) {
binding.bottomPanel.videoViewsContainer.setVisibility(View.GONE);
return;
}
binding.bottomPanel.tvVideoViews.setText(String.valueOf(videoViews));
binding.bottomPanel.videoViewsContainer.setVisibility(View.VISIBLE);
}
public void stopPlayingVideo() {
try {
final RecyclerView.ViewHolder viewHolder = ((RecyclerView) binding.mediaViewPager
.getChildAt(0)).findViewHolderForAdapterPosition(currentChildPosition);
if (viewHolder != null) {
final View itemView = viewHolder.itemView;
if (itemView instanceof PlayerView) {
final Player player = ((PlayerView) itemView).getPlayer();
if (player != null) {
player.setPlayWhenReady(false);
}
}
}
} catch (Exception e) {
Log.e(TAG, "Error", e);
}
}
}
// package awais.instagrabber.adapters.viewholder;
//
// import android.content.res.ColorStateList;
// import android.content.res.Resources;
// import android.util.Log;
// import android.view.View;
// import android.widget.TextView;
//
// import androidx.annotation.NonNull;
// import androidx.core.content.ContextCompat;
// import androidx.core.view.ViewCompat;
// import androidx.recyclerview.widget.RecyclerView;
// import androidx.viewpager2.widget.ViewPager2;
//
// import com.google.android.exoplayer2.Player;
// import com.google.android.exoplayer2.SimpleExoPlayer;
// import com.google.android.exoplayer2.ui.PlayerView;
//
// import java.util.ArrayList;
// import java.util.List;
//
// import awais.instagrabber.R;
// import awais.instagrabber.adapters.PostViewAdapter.OnPostCaptionLongClickListener;
// import awais.instagrabber.adapters.PostViewAdapter.OnPostViewChildViewClickListener;
// import awais.instagrabber.adapters.PostViewerChildAdapter;
// import awais.instagrabber.databinding.ItemFullPostViewBinding;
// import awais.instagrabber.interfaces.MentionClickListener;
// import awais.instagrabber.models.PostChild;
// import awais.instagrabber.models.ProfileModel;
// import awais.instagrabber.models.ViewerPostModel;
// import awais.instagrabber.models.ViewerPostModelWrapper;
// import awais.instagrabber.models.enums.MediaItemType;
// import awais.instagrabber.utils.TextUtils;
// import awais.instagrabber.utils.Utils;
//
// public class PostViewerViewHolder extends RecyclerView.ViewHolder {
// private static final String TAG = "PostViewerViewHolder";
//
// private final ItemFullPostViewBinding binding;
// private int currentChildPosition;
//
// public PostViewerViewHolder(@NonNull final ItemFullPostViewBinding binding) {
// super(binding.getRoot());
// this.binding = binding;
// binding.topPanel.viewStoryPost.setVisibility(View.GONE);
// }
//
// public void bind(final ViewerPostModelWrapper wrapper,
// final int position,
// final OnPostViewChildViewClickListener clickListener,
// final OnPostCaptionLongClickListener longClickListener,
// final MentionClickListener mentionClickListener) {
// if (wrapper == null) return;
// final List<PostChild> items = wrapper.getViewerPostModels();
// if (items == null || items.size() == 0) return;
// if (items.get(0) == null) return;
// final PostViewerChildAdapter adapter = new PostViewerChildAdapter();
// binding.mediaViewPager.setAdapter(adapter);
// final PostChild firstPost = items.get(0);
// setPostInfo(firstPost, mentionClickListener);
// setMediaItems(items, adapter);
// setupListeners(wrapper,
// position,
// clickListener,
// longClickListener,
// mentionClickListener,
// firstPost.getLocation());
// }
//
// private void setPostInfo(final PostChild firstPost,
// final MentionClickListener mentionClickListener) {
// final ProfileModel profileModel = firstPost.getProfileModel();
// if (profileModel == null) return;
// binding.topPanel.title.setText(profileModel.getUsername());
// final String locationName = firstPost.getLocationName();
// if (!TextUtils.isEmpty(locationName)) {
// binding.topPanel.location.setVisibility(View.VISIBLE);
// binding.topPanel.location.setText(locationName);
// } else binding.topPanel.location.setVisibility(View.GONE);
// binding.topPanel.ivProfilePic.setImageURI(profileModel.getSdProfilePic());
// binding.bottomPanel.commentsCount.setText(String.valueOf(firstPost.getCommentsCount()));
// final CharSequence postCaption = firstPost.getPostCaption();
// if (TextUtils.hasMentions(postCaption)) {
// binding.bottomPanel.viewerCaption.setMentionClickListener(mentionClickListener);
// binding.bottomPanel.viewerCaption
// .setText(TextUtils.getMentionText(postCaption), TextView.BufferType.SPANNABLE);
// } else {
// binding.bottomPanel.viewerCaption.setMentionClickListener(null);
// binding.bottomPanel.viewerCaption.setText(postCaption);
// }
// binding.bottomPanel.tvPostDate.setText(firstPost.getPostDate());
// setupLikes(firstPost);
// setupSave(firstPost);
// }
//
// private void setupLikes(final ViewerPostModel firstPost) {
// final boolean liked = firstPost.getLike();
// final long likeCount = firstPost.getLikes();
// final Resources resources = itemView.getContext().getResources();
// if (liked) {
// final String unlikeString = resources.getString(R.string.unlike, String.valueOf(likeCount));
// binding.btnLike.setText(unlikeString);
// ViewCompat.setBackgroundTintList(binding.btnLike,
// ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_pink_background)));
// } else {
// final String likeString = resources.getString(R.string.like, String.valueOf(likeCount));
// binding.btnLike.setText(likeString);
// ViewCompat.setBackgroundTintList(binding.btnLike,
// ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_lightpink_background)));
// }
// }
//
// private void setupSave(final ViewerPostModel firstPost) {
// final boolean saved = firstPost.isSaved();
// if (saved) {
// binding.btnBookmark.setText(R.string.unbookmark);
// ViewCompat.setBackgroundTintList(binding.btnBookmark,
// ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_orange_background)));
// } else {
// binding.btnBookmark.setText(R.string.bookmark);
// ViewCompat.setBackgroundTintList(
// binding.btnBookmark,
// ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_lightorange_background)));
// }
// }
//
// private void setupListeners(final ViewerPostModelWrapper wrapper,
// final int position,
// final OnPostViewChildViewClickListener clickListener,
// final OnPostCaptionLongClickListener longClickListener,
// final MentionClickListener mentionClickListener,
// final String location) {
// final View.OnClickListener onClickListener = v -> clickListener
// .onClick(v, wrapper, position, currentChildPosition);
// binding.bottomPanel.btnComments.setOnClickListener(onClickListener);
// binding.topPanel.title.setOnClickListener(onClickListener);
// binding.topPanel.ivProfilePic.setOnClickListener(onClickListener);
// binding.bottomPanel.btnDownload.setOnClickListener(onClickListener);
// binding.bottomPanel.viewerCaption.setOnClickListener(onClickListener);
// binding.btnLike.setOnClickListener(onClickListener);
// binding.btnBookmark.setOnClickListener(onClickListener);
// binding.bottomPanel.viewerCaption.setOnLongClickListener(v -> {
// longClickListener.onLongClick(binding.bottomPanel.viewerCaption.getText().toString());
// return true;
// });
// if (!TextUtils.isEmpty(location)) {
// binding.topPanel.location.setOnClickListener(v -> mentionClickListener
// .onClick(binding.topPanel.location, location, false, true));
// }
// }
//
// private void setMediaItems(final List<ViewerPostModel> items,
// final PostViewerChildAdapter adapter) {
// final List<ViewerPostModel> filteredList = new ArrayList<>();
// for (final ViewerPostModel model : items) {
// final MediaItemType itemType = model.getItemType();
// if (itemType == MediaItemType.MEDIA_TYPE_VIDEO || itemType == MediaItemType.MEDIA_TYPE_IMAGE) {
// filteredList.add(model);
// }
// }
// binding.mediaCounter.setVisibility(filteredList.size() > 1 ? View.VISIBLE : View.GONE);
// final String counter = "1/" + filteredList.size();
// binding.mediaCounter.setText(counter);
// adapter.submitList(filteredList);
// binding.mediaViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
// @Override
// public void onPageSelected(final int position) {
// if (filteredList.size() <= 0 || position >= filteredList.size()) return;
// currentChildPosition = position;
// final String counter = (position + 1) + "/" + filteredList.size();
// binding.mediaCounter.setText(counter);
// final ViewerPostModel viewerPostModel = filteredList.get(position);
// if (viewerPostModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
// setVideoDetails(viewerPostModel);
// setVolumeListener(position);
// return;
// }
// setImageDetails();
// }
// });
// }
//
// private void setVolumeListener(final int position) {
// binding.bottomPanel.btnMute.setOnClickListener(v -> {
// try {
// final RecyclerView.ViewHolder viewHolder = ((RecyclerView) binding.mediaViewPager
// .getChildAt(0)).findViewHolderForAdapterPosition(position);
// if (viewHolder != null) {
// final View itemView = viewHolder.itemView;
// if (itemView instanceof PlayerView) {
// final SimpleExoPlayer player = (SimpleExoPlayer) ((PlayerView) itemView)
// .getPlayer();
// if (player == null) return;
// final float vol = player.getVolume() == 0f ? 1f : 0f;
// player.setVolume(vol);
// binding.bottomPanel.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24
// : R.drawable.ic_volume_off_24);
// Utils.sessionVolumeFull = vol == 1f;
// }
// }
// } catch (Exception e) {
// Log.e(TAG, "Error", e);
// }
// });
// }
//
// private void setImageDetails() {
// binding.bottomPanel.btnMute.setVisibility(View.GONE);
// binding.bottomPanel.videoViewsContainer.setVisibility(View.GONE);
// }
//
// private void setVideoDetails(final ViewerPostModel viewerPostModel) {
// binding.bottomPanel.btnMute.setVisibility(View.VISIBLE);
// final long videoViews = viewerPostModel.getVideoViews();
// if (videoViews < 0) {
// binding.bottomPanel.videoViewsContainer.setVisibility(View.GONE);
// return;
// }
// binding.bottomPanel.tvVideoViews.setText(String.valueOf(videoViews));
// binding.bottomPanel.videoViewsContainer.setVisibility(View.VISIBLE);
// }
//
// public void stopPlayingVideo() {
// try {
// final RecyclerView.ViewHolder viewHolder = ((RecyclerView) binding.mediaViewPager
// .getChildAt(0)).findViewHolderForAdapterPosition(currentChildPosition);
// if (viewHolder != null) {
// final View itemView = viewHolder.itemView;
// if (itemView instanceof PlayerView) {
// final Player player = ((PlayerView) itemView).getPlayer();
// if (player != null) {
// player.setPlayWhenReady(false);
// }
// }
// }
// } catch (Exception e) {
// Log.e(TAG, "Error", e);
// }
// }
// }

View File

@ -0,0 +1,21 @@
package awais.instagrabber.adapters.viewholder;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.adapters.SliderItemsAdapter;
import awais.instagrabber.models.PostChild;
public abstract class SliderItemViewHolder extends RecyclerView.ViewHolder {
private static final String TAG = "FeedSliderItemViewHolder";
public SliderItemViewHolder(@NonNull final View itemView) {
super(itemView);
}
public abstract void bind(final PostChild model,
final int position,
final SliderItemsAdapter.SliderCallback sliderCallback);
}

View File

@ -0,0 +1,122 @@
package awais.instagrabber.adapters.viewholder;
import android.graphics.drawable.Animatable;
import android.net.Uri;
import androidx.annotation.NonNull;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import awais.instagrabber.adapters.SliderItemsAdapter;
import awais.instagrabber.customviews.VerticalDragHelper;
import awais.instagrabber.customviews.drawee.AnimatedZoomableController;
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
import awais.instagrabber.models.PostChild;
public class SliderPhotoViewHolder extends SliderItemViewHolder {
private static final String TAG = "FeedSliderPhotoViewHolder";
private final ItemSliderPhotoBinding binding;
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener;
public SliderPhotoViewHolder(@NonNull final ItemSliderPhotoBinding binding,
final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener) {
super(binding.getRoot());
this.binding = binding;
this.onVerticalDragListener = onVerticalDragListener;
}
public void bind(@NonNull final PostChild model,
final int position,
final SliderItemsAdapter.SliderCallback sliderCallback) {
final ImageRequest requestBuilder = ImageRequestBuilder
.newBuilderWithSource(Uri.parse(model.getDisplayUrl()))
.setLocalThumbnailPreviewsEnabled(true)
.build();
binding.getRoot()
.setController(Fresco.newDraweeControllerBuilder()
.setImageRequest(requestBuilder)
.setControllerListener(new BaseControllerListener<ImageInfo>() {
@Override
public void onFailure(final String id, final Throwable throwable) {
if (sliderCallback != null) {
sliderCallback.onThumbnailLoaded(position);
}
}
@Override
public void onFinalImageSet(final String id,
final ImageInfo imageInfo,
final Animatable animatable) {
if (sliderCallback != null) {
sliderCallback.onThumbnailLoaded(position);
}
}
})
.setLowResImageRequest(ImageRequest.fromUri(model.getThumbnailUrl()))
.build());
binding.getRoot().setOnClickListener(v -> {
if (sliderCallback != null) {
sliderCallback.onItemClicked(position);
}
});
final AnimatedZoomableController zoomableController = AnimatedZoomableController.newInstance();
zoomableController.setMaxScaleFactor(3f);
binding.getRoot().setZoomableController(zoomableController);
if (onVerticalDragListener != null) {
binding.getRoot().setOnVerticalDragListener(onVerticalDragListener);
}
}
// private void setDimensions(final FeedModel feedModel, final int spanCount, final boolean animate) {
// final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
// final int deviceWidth = Utils.displayMetrics.widthPixels;
// final int spanWidth = deviceWidth / spanCount;
// final int spanHeight = NumberUtils.getResultingHeight(spanWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
// final int width = spanWidth == 0 ? deviceWidth : spanWidth;
// final int height = spanHeight == 0 ? deviceWidth + 1 : spanHeight;
// if (animate) {
// Animation animation = AnimationUtils.expand(
// binding.imageViewer,
// layoutParams.width,
// layoutParams.height,
// width,
// height,
// new Animation.AnimationListener() {
// @Override
// public void onAnimationStart(final Animation animation) {
// showOrHideDetails(spanCount);
// }
//
// @Override
// public void onAnimationEnd(final Animation animation) {
// // showOrHideDetails(spanCount);
// }
//
// @Override
// public void onAnimationRepeat(final Animation animation) {
//
// }
// });
// binding.imageViewer.startAnimation(animation);
// } else {
// layoutParams.width = width;
// layoutParams.height = height;
// binding.imageViewer.requestLayout();
// }
// }
//
// private void showOrHideDetails(final int spanCount) {
// if (spanCount == 1) {
// binding.itemFeedTop.getRoot().setVisibility(View.VISIBLE);
// binding.itemFeedBottom.getRoot().setVisibility(View.VISIBLE);
// } else {
// binding.itemFeedTop.getRoot().setVisibility(View.GONE);
// binding.itemFeedBottom.getRoot().setVisibility(View.GONE);
// }
// }
}

View File

@ -0,0 +1,185 @@
package awais.instagrabber.adapters.viewholder;
import android.annotation.SuppressLint;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import awais.instagrabber.adapters.SliderItemsAdapter;
import awais.instagrabber.customviews.VerticalDragHelper;
import awais.instagrabber.customviews.VideoPlayerCallbackAdapter;
import awais.instagrabber.customviews.VideoPlayerViewHelper;
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding;
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class SliderVideoViewHolder extends SliderItemViewHolder {
private static final String TAG = "SliderVideoViewHolder";
private final LayoutVideoPlayerWithThumbnailBinding binding;
private final LayoutExoCustomControlsBinding controlsBinding;
private final boolean loadVideoOnItemClick;
private VideoPlayerViewHelper videoPlayerViewHelper;
@SuppressLint("ClickableViewAccessibility")
public SliderVideoViewHolder(@NonNull final LayoutVideoPlayerWithThumbnailBinding binding,
final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener,
final LayoutExoCustomControlsBinding controlsBinding,
final boolean loadVideoOnItemClick) {
super(binding.getRoot());
this.binding = binding;
this.controlsBinding = controlsBinding;
this.loadVideoOnItemClick = loadVideoOnItemClick;
if (onVerticalDragListener != null) {
final VerticalDragHelper thumbnailVerticalDragHelper = new VerticalDragHelper(binding.thumbnailParent);
final VerticalDragHelper playerVerticalDragHelper = new VerticalDragHelper(binding.playerView);
thumbnailVerticalDragHelper.setOnVerticalDragListener(onVerticalDragListener);
playerVerticalDragHelper.setOnVerticalDragListener(onVerticalDragListener);
binding.thumbnailParent.setOnTouchListener((v, event) -> {
final boolean onDragTouch = thumbnailVerticalDragHelper.onDragTouch(event);
if (onDragTouch) {
return true;
}
return thumbnailVerticalDragHelper.onGestureTouchEvent(event);
});
binding.playerView.setOnTouchListener((v, event) -> {
final boolean onDragTouch = playerVerticalDragHelper.onDragTouch(event);
if (onDragTouch) {
return true;
}
return playerVerticalDragHelper.onGestureTouchEvent(event);
});
}
}
public void bind(@NonNull final PostChild model,
final int position,
final SliderItemsAdapter.SliderCallback sliderCallback) {
final float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
final VideoPlayerViewHelper.VideoPlayerCallback videoPlayerCallback = new VideoPlayerCallbackAdapter() {
@Override
public void onThumbnailClick() {
if (sliderCallback != null) {
sliderCallback.onItemClicked(position);
}
}
@Override
public void onThumbnailLoaded() {
if (sliderCallback != null) {
sliderCallback.onThumbnailLoaded(position);
}
}
@Override
public void onPlayerViewLoaded() {
// binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
final ViewGroup.LayoutParams layoutParams = binding.playerView.getLayoutParams();
final int requiredWidth = Utils.displayMetrics.widthPixels;
final int resultingHeight = NumberUtils.getResultingHeight(requiredWidth, model.getHeight(), model.getWidth());
layoutParams.width = requiredWidth;
layoutParams.height = resultingHeight;
binding.playerView.requestLayout();
// setMuteIcon(vol == 0f && Utils.sessionVolumeFull ? 1f : vol);
}
@Override
public void onPlay() {
if (sliderCallback != null) {
sliderCallback.onPlayerPlay(position);
}
}
@Override
public void onPause() {
if (sliderCallback != null) {
sliderCallback.onPlayerPause(position);
}
}
};
final float aspectRatio = (float) model.getWidth() / model.getHeight();
videoPlayerViewHelper = new VideoPlayerViewHelper(binding.getRoot().getContext(),
binding,
model.getDisplayUrl(),
vol,
aspectRatio,
model.getThumbnailUrl(),
loadVideoOnItemClick,
controlsBinding,
videoPlayerCallback);
// binding.itemFeedBottom.btnMute.setOnClickListener(v -> {
// final float newVol = videoPlayerViewHelper.toggleMute();
// setMuteIcon(newVol);
// Utils.sessionVolumeFull = newVol == 1f;
// });
binding.playerView.setOnClickListener(v -> {
if (sliderCallback != null) {
sliderCallback.onItemClicked(position);
}
});
}
public void pause() {
if (videoPlayerViewHelper == null) return;
videoPlayerViewHelper.pause();
}
public void releasePlayer() {
if (videoPlayerViewHelper == null) return;
videoPlayerViewHelper.releasePlayer();
}
// private void setDimensions(final FeedModel feedModel, final int spanCount, final boolean animate) {
// final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
// final int deviceWidth = Utils.displayMetrics.widthPixels;
// final int spanWidth = deviceWidth / spanCount;
// final int spanHeight = NumberUtils.getResultingHeight(spanWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
// final int width = spanWidth == 0 ? deviceWidth : spanWidth;
// final int height = spanHeight == 0 ? deviceWidth + 1 : spanHeight;
// if (animate) {
// Animation animation = AnimationUtils.expand(
// binding.imageViewer,
// layoutParams.width,
// layoutParams.height,
// width,
// height,
// new Animation.AnimationListener() {
// @Override
// public void onAnimationStart(final Animation animation) {
// showOrHideDetails(spanCount);
// }
//
// @Override
// public void onAnimationEnd(final Animation animation) {
// // showOrHideDetails(spanCount);
// }
//
// @Override
// public void onAnimationRepeat(final Animation animation) {
//
// }
// });
// binding.imageViewer.startAnimation(animation);
// } else {
// layoutParams.width = width;
// layoutParams.height = height;
// binding.imageViewer.requestLayout();
// }
// }
//
// private void showOrHideDetails(final int spanCount) {
// if (spanCount == 1) {
// binding.itemFeedTop.getRoot().setVisibility(View.VISIBLE);
// binding.itemFeedBottom.getRoot().setVisibility(View.VISIBLE);
// } else {
// binding.itemFeedTop.getRoot().setVisibility(View.GONE);
// binding.itemFeedBottom.getRoot().setVisibility(View.GONE);
// }
// }
}

View File

@ -0,0 +1,99 @@
package awais.instagrabber.adapters.viewholder;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.palette.graphics.Palette;
import androidx.recyclerview.widget.RecyclerView;
import com.facebook.common.executors.CallerThreadExecutor;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import java.util.concurrent.atomic.AtomicInteger;
import awais.instagrabber.R;
import awais.instagrabber.adapters.DiscoverTopicsAdapter;
import awais.instagrabber.databinding.ItemDiscoverTopicBinding;
import awais.instagrabber.models.TopicCluster;
public class TopicClusterViewHolder extends RecyclerView.ViewHolder {
private final ItemDiscoverTopicBinding binding;
private final DiscoverTopicsAdapter.OnTopicClickListener onTopicClickListener;
public TopicClusterViewHolder(@NonNull final ItemDiscoverTopicBinding binding,
final DiscoverTopicsAdapter.OnTopicClickListener onTopicClickListener) {
super(binding.getRoot());
this.binding = binding;
this.onTopicClickListener = onTopicClickListener;
}
public void bind(final TopicCluster topicCluster) {
if (topicCluster == null) {
return;
}
final AtomicInteger titleColor = new AtomicInteger(-1);
final AtomicInteger backgroundColor = new AtomicInteger(-1);
if (onTopicClickListener != null) {
itemView.setOnClickListener(v -> onTopicClickListener.onTopicClick(
topicCluster,
binding.getRoot(),
binding.cover,
binding.title,
titleColor.get(),
backgroundColor.get()
));
}
// binding.title.setTransitionName("title-" + topicCluster.getId());
binding.cover.setTransitionName("cover-" + topicCluster.getId());
final ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(Uri.parse(topicCluster.getCoverMedia().getDisplayUrl()))
.build();
final ImagePipeline imagePipeline = Fresco.getImagePipeline();
final DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline
.fetchDecodedImage(imageRequest, CallerThreadExecutor.getInstance());
dataSource.subscribe(new BaseBitmapDataSubscriber() {
@Override
public void onNewResultImpl(@Nullable Bitmap bitmap) {
if (dataSource.isFinished()) {
dataSource.close();
}
if (bitmap != null) {
Palette.from(bitmap).generate(p -> {
final Palette.Swatch swatch = p.getDominantSwatch();
final Resources resources = itemView.getResources();
int titleTextColor = resources.getColor(R.color.white);
if (swatch != null) {
backgroundColor.set(swatch.getRgb());
GradientDrawable gd = new GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
new int[]{Color.TRANSPARENT, backgroundColor.get()});
titleTextColor = swatch.getTitleTextColor();
binding.background.setBackground(gd);
}
titleColor.set(titleTextColor);
binding.title.setTextColor(titleTextColor);
});
}
}
@Override
public void onFailureImpl(@NonNull DataSource dataSource) {
dataSource.close();
}
}, CallerThreadExecutor.getInstance());
binding.cover.setImageRequest(imageRequest);
binding.title.setText(topicCluster.getTitle());
}
}

View File

@ -0,0 +1,96 @@
package awais.instagrabber.adapters.viewholder.comments;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
import awais.instagrabber.adapters.CommentsAdapter.CommentCallback;
import awais.instagrabber.databinding.ItemCommentSmallBinding;
import awais.instagrabber.models.CommentModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.utils.Utils;
public final class ChildCommentViewHolder extends RecyclerView.ViewHolder {
private final ItemCommentSmallBinding binding;
public ChildCommentViewHolder(@NonNull final ItemCommentSmallBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(final CommentModel comment,
final boolean selected,
final CommentCallback commentCallback) {
if (comment == null) return;
if (commentCallback != null) {
itemView.setOnClickListener(v -> commentCallback.onClick(comment));
}
if (selected) {
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_selected));
} else {
itemView.setBackgroundColor(itemView.getResources().getColor(android.R.color.transparent));
}
setupCommentText(comment, commentCallback);
binding.tvDate.setText(comment.getDateTime());
setLiked(comment.getLiked());
setLikes((int) comment.getLikes());
setUser(comment);
}
private void setupCommentText(final CommentModel comment, final CommentCallback commentCallback) {
binding.tvComment.clearOnURLClickListeners();
binding.tvComment.clearOnHashtagClickListeners();
binding.tvComment.clearOnMentionClickListeners();
binding.tvComment.clearOnEmailClickListeners();
binding.tvComment.setText(comment.getText());
binding.tvComment.addOnHashtagListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onHashtagClick(originalText);
});
binding.tvComment.addOnMentionClickListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onMentionClick(originalText);
});
binding.tvComment.addOnEmailClickListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onEmailClick(originalText);
});
binding.tvComment.addOnURLClickListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onURLClick(originalText);
});
binding.tvComment.setOnLongClickListener(v -> {
Utils.copyText(itemView.getContext(), comment.getText());
return true;
});
binding.tvComment.setOnClickListener(v -> commentCallback.onClick(comment));
}
private void setUser(final CommentModel comment) {
final ProfileModel profileModel = comment.getProfileModel();
if (profileModel == null) return;
binding.tvUsername.setText(profileModel.getUsername());
binding.ivProfilePic.setImageURI(profileModel.getSdProfilePic());
binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
}
private void setLikes(final int likes) {
final String likesString = itemView.getResources().getQuantityString(R.plurals.likes_count, likes, likes);
binding.tvLikes.setText(likesString);
}
public final void setLiked(final boolean liked) {
if (liked) {
// container.setBackgroundColor(0x40FF69B4);
return;
}
}
}

View File

@ -0,0 +1,96 @@
package awais.instagrabber.adapters.viewholder.comments;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
import awais.instagrabber.adapters.CommentsAdapter.CommentCallback;
import awais.instagrabber.databinding.ItemCommentBinding;
import awais.instagrabber.models.CommentModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.utils.Utils;
public final class ParentCommentViewHolder extends RecyclerView.ViewHolder {
private final ItemCommentBinding binding;
public ParentCommentViewHolder(@NonNull final ItemCommentBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(final CommentModel comment,
final boolean selected,
final CommentCallback commentCallback) {
if (comment == null) return;
if (commentCallback != null) {
itemView.setOnClickListener(v -> commentCallback.onClick(comment));
}
if (selected) {
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_selected));
} else {
itemView.setBackgroundColor(itemView.getResources().getColor(android.R.color.transparent));
}
setupCommentText(comment, commentCallback);
binding.tvDate.setText(comment.getDateTime());
setLiked(comment.getLiked());
setLikes((int) comment.getLikes());
setUser(comment);
}
private void setupCommentText(final CommentModel comment, final CommentCallback commentCallback) {
binding.tvComment.clearOnURLClickListeners();
binding.tvComment.clearOnHashtagClickListeners();
binding.tvComment.clearOnMentionClickListeners();
binding.tvComment.clearOnEmailClickListeners();
binding.tvComment.setText(comment.getText());
binding.tvComment.addOnHashtagListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onHashtagClick(originalText);
});
binding.tvComment.addOnMentionClickListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onMentionClick(originalText);
});
binding.tvComment.addOnEmailClickListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onEmailClick(originalText);
});
binding.tvComment.addOnURLClickListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onURLClick(originalText);
});
binding.tvComment.setOnLongClickListener(v -> {
Utils.copyText(itemView.getContext(), comment.getText());
return true;
});
binding.tvComment.setOnClickListener(v -> commentCallback.onClick(comment));
}
private void setUser(final CommentModel comment) {
final ProfileModel profileModel = comment.getProfileModel();
if (profileModel == null) return;
binding.tvUsername.setText(profileModel.getUsername());
binding.ivProfilePic.setImageURI(profileModel.getSdProfilePic());
binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
}
private void setLikes(final int likes) {
final String likesString = itemView.getResources().getQuantityString(R.plurals.likes_count, likes, likes);
binding.tvLikes.setText(likesString);
}
public final void setLiked(final boolean liked) {
if (liked) {
// container.setBackgroundColor(0x40FF69B4);
return;
}
}
}

View File

@ -1,33 +1,64 @@
package awais.instagrabber.adapters.viewholder.directmessages;
import android.net.Uri;
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 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.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 = (int) (Utils.displayMetrics.widthPixels - Utils.convertDpToPx(64) - getItemMargin());
setItemView(binding.getRoot());
removeElevation();
}
@Override
public void bindItem(final DirectItemModel directItemModel) {
final DirectItemModel.DirectItemAnimatedMediaModel animatedMediaModel = directItemModel.getAnimatedMediaModel();
final String url = animatedMediaModel.getGifUrl();
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();
final ImageRequest request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url))
.setResizeOptions(ResizeOptions.forDimensions(width, height))
.build();
binding.ivAnimatedMessage.setController(Fresco.newDraweeControllerBuilder()
.setUri(directItemModel.getAnimatedMediaModel().getGifUrl())
.setImageRequest(request)
.setAutoPlayAnimations(true)
.build());
binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
}
}

View File

@ -71,6 +71,10 @@ public abstract class DirectMessageItemViewHolder extends RecyclerView.ViewHolde
this.binding.messageCard.addView(view);
}
public int getItemMargin() {
return itemMargin;
}
public abstract void bindItem(final DirectItemModel directItemModel);
@Nullable
@ -88,4 +92,8 @@ public abstract class DirectMessageItemViewHolder extends RecyclerView.ViewHolde
}
return null;
}
protected void removeElevation() {
binding.messageCard.setCardElevation(0);
}
}

View File

@ -26,7 +26,7 @@ public class DirectMessageMediaViewHolder extends DirectMessageItemViewHolder {
super(baseBinding, onClickListener);
this.binding = binding;
maxHeight = itemView.getResources().getDimensionPixelSize(R.dimen.dm_media_img_max_height);
maxWidth = (int) (Utils.displayMetrics.widthPixels * 0.8);
maxWidth = (int) (Utils.displayMetrics.widthPixels - Utils.convertDpToPx(64) - getItemMargin());
setItemView(binding.getRoot());
}

View File

@ -1,92 +1,110 @@
package awais.instagrabber.adapters.viewholder.feed;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.transition.TransitionManager;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.customviews.CommentMentionClickSpan;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.databinding.ItemFeedBottomBinding;
import awais.instagrabber.databinding.ItemFeedTopBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.TextUtils;
import static android.text.TextUtils.TruncateAt.END;
public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
public static final int MAX_CHARS = 255;
public static final int MAX_LINES_COLLAPSED = 5;
private final ItemFeedTopBinding topBinding;
private final ItemFeedBottomBinding bottomBinding;
private final MentionClickListener mentionClickListener;
private final FeedAdapterV2.FeedItemCallback feedItemCallback;
public FeedItemViewHolder(@NonNull final View root,
final ItemFeedTopBinding topBinding,
final ItemFeedBottomBinding bottomBinding,
final MentionClickListener mentionClickListener,
final View.OnClickListener clickListener,
final View.OnLongClickListener longClickListener) {
final FeedAdapterV2.FeedItemCallback feedItemCallback) {
super(root);
this.topBinding = topBinding;
this.bottomBinding = bottomBinding;
this.mentionClickListener = mentionClickListener;
// topBinding.title.setMovementMethod(new LinkMovementMethod());
bottomBinding.btnComments.setOnClickListener(clickListener);
topBinding.viewStoryPost.setOnClickListener(clickListener);
topBinding.ivProfilePic.setOnClickListener(clickListener);
bottomBinding.btnDownload.setOnClickListener(clickListener);
bottomBinding.viewerCaption.setOnClickListener(clickListener);
bottomBinding.viewerCaption.setOnLongClickListener(longClickListener);
bottomBinding.viewerCaption.setMentionClickListener(mentionClickListener);
topBinding.title.setMovementMethod(new LinkMovementMethod());
this.feedItemCallback = feedItemCallback;
}
public void bind(final FeedModel feedModel) {
if (feedModel == null) {
return;
}
topBinding.viewStoryPost.setTag(feedModel);
topBinding.ivProfilePic.setTag(feedModel);
bottomBinding.btnDownload.setTag(feedModel);
bottomBinding.viewerCaption.setTag(feedModel);
bottomBinding.btnComments.setTag(feedModel);
final ProfileModel profileModel = feedModel.getProfileModel();
if (profileModel != null) {
topBinding.ivProfilePic.setImageURI(profileModel.getSdProfilePic());
final int titleLen = profileModel.getUsername().length() + 1;
final SpannableString spannableString = new SpannableString("@" + profileModel.getUsername());
spannableString.setSpan(new CommentMentionClickSpan(), 0, titleLen, 0);
topBinding.title.setText(spannableString);
topBinding.title.setMentionClickListener(
(view, text, isHashtag, isLocation) -> mentionClickListener.onClick(null, profileModel.getUsername(), false, false));
}
setupProfilePic(feedModel);
setupLocation(feedModel);
bottomBinding.tvPostDate.setText(feedModel.getPostDate());
final long commentsCount = feedModel.getCommentsCount();
bottomBinding.commentsCount.setText(String.valueOf(commentsCount));
final String locationName = feedModel.getLocationName();
final String locationId = feedModel.getLocationId();
setLocation(locationName, locationId);
CharSequence postCaption = feedModel.getPostCaption();
final boolean captionEmpty = TextUtils.isEmpty(postCaption);
bottomBinding.viewerCaption.setVisibility(captionEmpty ? View.GONE : View.VISIBLE);
if (!captionEmpty) {
if (TextUtils.hasMentions(postCaption)) {
postCaption = TextUtils.getMentionText(postCaption);
feedModel.setPostCaption(postCaption);
bottomBinding.viewerCaption.setText(postCaption, TextView.BufferType.SPANNABLE);
} else {
bottomBinding.viewerCaption.setText(postCaption);
}
setupComments(feedModel);
setupCaption(feedModel);
if (feedModel.getItemType() != MediaItemType.MEDIA_TYPE_SLIDER) {
bottomBinding.btnDownload.setOnClickListener(v -> feedItemCallback.onDownloadClick(feedModel, -1));
}
expandCollapseTextView(bottomBinding.viewerCaption, feedModel.getPostCaption());
bindItem(feedModel);
}
private void setLocation(final String locationName, final String locationId) {
private void setupComments(final FeedModel feedModel) {
final long commentsCount = feedModel.getCommentsCount();
bottomBinding.commentsCount.setText(String.valueOf(commentsCount));
bottomBinding.commentsCount.setOnClickListener(v -> feedItemCallback.onCommentsClick(feedModel));
}
private void setupProfilePic(final FeedModel feedModel) {
final ProfileModel profileModel = feedModel.getProfileModel();
if (profileModel != null) {
topBinding.ivProfilePic.setOnClickListener(v -> feedItemCallback.onProfilePicClick(feedModel, topBinding.ivProfilePic));
topBinding.ivProfilePic.setImageURI(profileModel.getSdProfilePic());
setupTitle(feedModel);
}
}
private void setupTitle(final FeedModel feedModel) {
// final int titleLen = profileModel.getUsername().length() + 1;
// final SpannableString spannableString = new SpannableString();
// spannableString.setSpan(new CommentMentionClickSpan(), 0, titleLen, 0);
final ProfileModel profileModel = feedModel.getProfileModel();
final String title = "@" + profileModel.getUsername();
topBinding.title.setText(title);
topBinding.title.setOnClickListener(v -> feedItemCallback.onNameClick(feedModel, topBinding.ivProfilePic));
}
private void setupCaption(final FeedModel feedModel) {
bottomBinding.viewerCaption.clearOnMentionClickListeners();
bottomBinding.viewerCaption.clearOnHashtagClickListeners();
bottomBinding.viewerCaption.clearOnURLClickListeners();
bottomBinding.viewerCaption.clearOnEmailClickListeners();
final CharSequence postCaption = feedModel.getPostCaption();
final boolean captionEmpty = TextUtils.isEmpty(postCaption);
bottomBinding.viewerCaption.setVisibility(captionEmpty ? View.GONE : View.VISIBLE);
if (captionEmpty) return;
bottomBinding.viewerCaption.setText(postCaption);
bottomBinding.viewerCaption.setMaxLines(MAX_LINES_COLLAPSED);
bottomBinding.viewerCaption.setEllipsize(END);
bottomBinding.viewerCaption.setOnClickListener(v -> bottomBinding.getRoot().post(() -> {
TransitionManager.beginDelayedTransition(bottomBinding.getRoot());
if (bottomBinding.viewerCaption.getMaxLines() == MAX_LINES_COLLAPSED) {
bottomBinding.viewerCaption.setMaxLines(Integer.MAX_VALUE);
bottomBinding.viewerCaption.setEllipsize(null);
return;
}
bottomBinding.viewerCaption.setMaxLines(MAX_LINES_COLLAPSED);
bottomBinding.viewerCaption.setEllipsize(END);
}));
bottomBinding.viewerCaption.addOnMentionClickListener(autoLinkItem -> feedItemCallback.onMentionClick(autoLinkItem.getOriginalText()));
bottomBinding.viewerCaption.addOnHashtagListener(autoLinkItem -> feedItemCallback.onHashtagClick(autoLinkItem.getOriginalText()));
bottomBinding.viewerCaption.addOnEmailClickListener(autoLinkItem -> feedItemCallback.onEmailClick(autoLinkItem.getOriginalText()));
bottomBinding.viewerCaption.addOnURLClickListener(autoLinkItem -> feedItemCallback.onURLClick(autoLinkItem.getOriginalText()));
}
private void setupLocation(final FeedModel feedModel) {
final String locationName = feedModel.getLocationName();
if (TextUtils.isEmpty(locationName)) {
topBinding.location.setVisibility(View.GONE);
topBinding.title.setLayoutParams(new RelativeLayout.LayoutParams(
@ -98,41 +116,9 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
topBinding.title.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT
));
topBinding.location.setOnClickListener(v -> mentionClickListener.onClick(topBinding.location, locationId, false, true));
topBinding.location.setOnClickListener(v -> feedItemCallback.onLocationClick(feedModel));
}
}
/**
* expands or collapses {@link RamboTextView} [stg idek why i wrote this documentation]
*
* @param textView the {@link RamboTextView} view, to expand and collapse
* @param caption caption
* @return isExpanded
*/
public static boolean expandCollapseTextView(@NonNull final RamboTextView textView, final CharSequence caption) {
if (TextUtils.isEmpty(caption)) return false;
final TextView.BufferType bufferType = caption instanceof Spanned ? TextView.BufferType.SPANNABLE : TextView.BufferType.NORMAL;
if (textView.isCaptionExpanded()) {
textView.setText(caption, bufferType);
textView.setCaptionIsExpanded(false);
return true;
}
int i = TextUtils.indexOfChar(caption, '\r', 0);
if (i == -1) i = TextUtils.indexOfChar(caption, '\n', 0);
if (i == -1) i = MAX_CHARS;
final int captionLen = caption.length();
final int minTrim = Math.min(MAX_CHARS, i);
if (captionLen <= minTrim) return false;
if (TextUtils.hasMentions(caption))
textView.setText(TextUtils.getMentionText(caption), TextView.BufferType.SPANNABLE);
textView.setCaptionIsExpandable(true);
textView.setCaptionIsExpanded(true);
return true;
}
public abstract void bindItem(final FeedModel feedModel);
}

View File

@ -1,8 +1,9 @@
package awais.instagrabber.adapters.viewholder.feed;
import android.net.Uri;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
@ -13,25 +14,24 @@ import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.databinding.ItemFeedPhotoBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
public class FeedPhotoViewHolder extends FeedItemViewHolder {
private static final String TAG = "FeedPhotoViewHolder";
private final ItemFeedPhotoBinding binding;
private final FeedAdapterV2.FeedItemCallback feedItemCallback;
public FeedPhotoViewHolder(@NonNull final ItemFeedPhotoBinding binding,
final MentionClickListener mentionClickListener,
final View.OnClickListener clickListener,
final View.OnLongClickListener longClickListener) {
super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, mentionClickListener, clickListener, longClickListener);
final FeedAdapterV2.FeedItemCallback feedItemCallback) {
super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, feedItemCallback);
this.binding = binding;
binding.itemFeedBottom.videoViewsContainer.setVisibility(View.GONE);
binding.itemFeedBottom.btnMute.setVisibility(View.GONE);
this.feedItemCallback = feedItemCallback;
binding.itemFeedBottom.tvVideoViews.setVisibility(View.GONE);
// binding.itemFeedBottom.btnMute.setVisibility(View.GONE);
binding.imageViewer.setAllowTouchInterceptionWhileZoomed(false);
final GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(itemView.getContext().getResources())
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
@ -44,33 +44,35 @@ public class FeedPhotoViewHolder extends FeedItemViewHolder {
if (feedModel == null) {
return;
}
final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
final int requiredWidth = Utils.displayMetrics.widthPixels;
layoutParams.width = feedModel.getImageWidth() == 0 ? requiredWidth : feedModel.getImageWidth();
layoutParams.height = feedModel.getImageHeight() == 0 ? requiredWidth + 1 : feedModel.getImageHeight();
binding.imageViewer.requestLayout();
final String thumbnailUrl = feedModel.getThumbnailUrl();
String url = feedModel.getDisplayUrl();
if (TextUtils.isEmpty(url)) url = thumbnailUrl;
final ImageRequest requestBuilder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url))
.setLocalThumbnailPreviewsEnabled(true)
.setProgressiveRenderingEnabled(true)
.build();
binding.imageViewer.setController(Fresco.newDraweeControllerBuilder()
.setImageRequest(requestBuilder)
.setOldController(binding.imageViewer.getController())
.setLowResImageRequest(ImageRequest.fromUri(thumbnailUrl))
.build());
// binding.imageViewer.setImageURI(url);
// final RequestBuilder<Bitmap> thumbnailRequestBuilder = glide
// .asBitmap()
// .load(thumbnailUrl)
// .diskCacheStrategy(DiskCacheStrategy.ALL);
// glide.asBitmap()
// .load(url)
// .thumbnail(thumbnailRequestBuilder)
// .diskCacheStrategy(DiskCacheStrategy.ALL)
// .into(customTarget);
binding.getRoot().post(() -> {
setDimensions(feedModel);
final String thumbnailUrl = feedModel.getThumbnailUrl();
String url = feedModel.getDisplayUrl();
if (TextUtils.isEmpty(url)) url = thumbnailUrl;
final ImageRequest requestBuilder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url))
// .setLocalThumbnailPreviewsEnabled(true)
// .setProgressiveRenderingEnabled(true)
.build();
binding.imageViewer.setController(Fresco.newDraweeControllerBuilder()
.setImageRequest(requestBuilder)
.setOldController(binding.imageViewer.getController())
.setLowResImageRequest(ImageRequest.fromUri(thumbnailUrl))
.build());
binding.imageViewer.setTapListener(new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapConfirmed(final MotionEvent e) {
if (feedItemCallback != null) {
feedItemCallback.onPostClick(feedModel, binding.itemFeedTop.ivProfilePic, binding.imageViewer);
return true;
}
return false;
}
});
});
}
private void setDimensions(final FeedModel feedModel) {
final float aspectRatio = (float) feedModel.getImageWidth() / feedModel.getImageHeight();
binding.imageViewer.setAspectRatio(aspectRatio);
}
}

View File

@ -1,24 +1,15 @@
package awais.instagrabber.adapters.viewholder.feed;
import android.content.Context;
import android.net.Uri;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.view.ViewTreeObserver;
import android.widget.ViewSwitcher;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import androidx.viewpager2.widget.ViewPager2;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
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;
@ -28,13 +19,16 @@ import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import awais.instagrabber.R;
import java.util.List;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.adapters.SliderCallbackAdapter;
import awais.instagrabber.adapters.SliderItemsAdapter;
import awais.instagrabber.databinding.ItemFeedSliderBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
@ -44,25 +38,20 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
private static final boolean shouldAutoPlay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
private final ItemFeedSliderBinding binding;
private final FeedAdapterV2.FeedItemCallback feedItemCallback;
private final DefaultDataSourceFactory dataSourceFactory;
private final PlayerChangeListener playerChangeListener = (position, player) -> {
pagerPlayer = player;
playerPosition = position;
};
private CacheDataSourceFactory cacheDataSourceFactory;
private SimpleExoPlayer pagerPlayer;
private int playerPosition = 0;
public FeedSliderViewHolder(@NonNull final ItemFeedSliderBinding binding,
final MentionClickListener mentionClickListener,
final View.OnClickListener clickListener,
final View.OnLongClickListener longClickListener) {
super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, mentionClickListener, clickListener, longClickListener);
final FeedAdapterV2.FeedItemCallback feedItemCallback) {
super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, feedItemCallback);
this.binding = binding;
binding.itemFeedBottom.videoViewsContainer.setVisibility(View.GONE);
binding.itemFeedBottom.btnMute.setVisibility(View.GONE);
this.feedItemCallback = feedItemCallback;
binding.itemFeedBottom.tvVideoViews.setVisibility(View.GONE);
// binding.itemFeedBottom.btnMute.setVisibility(View.GONE);
final ViewGroup.LayoutParams layoutParams = binding.mediaList.getLayoutParams();
layoutParams.height = Utils.displayMetrics.widthPixels + 1;
binding.mediaList.setLayoutParams(layoutParams);
@ -76,112 +65,108 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
@Override
public void bindItem(final FeedModel feedModel) {
final ViewerPostModel[] sliderItems = feedModel.getSliderItems();
final int sliderItemLen = sliderItems != null ? sliderItems.length : 0;
if (sliderItemLen <= 0) {
return;
}
final List<PostChild> sliderItems = feedModel.getSliderItems();
final int sliderItemLen = sliderItems != null ? sliderItems.size() : 0;
if (sliderItemLen <= 0) return;
final String text = "1/" + sliderItemLen;
binding.mediaCounter.setText(text);
binding.mediaList.setOffscreenPageLimit(Math.min(5, sliderItemLen));
final PagerAdapter adapter = binding.mediaList.getAdapter();
if (adapter != null) {
final int count = adapter.getCount();
for (int i = 0; i < count; i++) {
adapter.destroyItem(binding.mediaList, i, binding.mediaList.getChildAt(i));
}
}
final ChildMediaItemsAdapter itemsAdapter = new ChildMediaItemsAdapter(sliderItems,
cacheDataSourceFactory != null
? cacheDataSourceFactory
: dataSourceFactory,
playerChangeListener);
binding.mediaList.setAdapter(itemsAdapter);
//noinspection deprecation
binding.mediaList.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
private int prevPos = 0;
binding.mediaList.setOffscreenPageLimit(1);
final SliderItemsAdapter adapter = new SliderItemsAdapter(null, null, false, new SliderCallbackAdapter() {
@Override
public void onPageSelected(final int position) {
ViewerPostModel sliderItem = sliderItems[prevPos];
if (sliderItem != null) {
sliderItem.setSelected(false);
if (sliderItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
// stop playing prev video
final ViewSwitcher prevChild = (ViewSwitcher) binding.mediaList.getChildAt(prevPos);
if (prevChild == null || prevChild.getTag() == null || !(prevChild.getTag() instanceof SimpleExoPlayer)) {
return;
}
((SimpleExoPlayer) prevChild.getTag()).setPlayWhenReady(false);
}
}
sliderItem = sliderItems[position];
if (sliderItem == null) return;
sliderItem.setSelected(true);
final String text = (position + 1) + "/" + sliderItemLen;
binding.mediaCounter.setText(text);
prevPos = position;
if (sliderItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
if (shouldAutoPlay) {
autoPlay(position);
}
} else binding.itemFeedBottom.btnMute.setVisibility(View.GONE);
public void onItemClicked(final int position) {
feedItemCallback.onSliderClick(feedModel, position);
}
});
binding.mediaList.setAdapter(adapter);
binding.mediaList.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(final int position) {
if (position >= sliderItemLen) return;
final String text = (position + 1) + "/" + sliderItemLen;
binding.mediaCounter.setText(text);
setDimensions(binding.mediaList, sliderItems.get(position));
binding.itemFeedBottom.btnDownload.setOnClickListener(v -> feedItemCallback.onDownloadClick(feedModel, position));
}
});
setDimensions(binding.mediaList, sliderItems.get(0));
binding.itemFeedBottom.btnDownload.setOnClickListener(v -> feedItemCallback.onDownloadClick(feedModel, 0));
adapter.submitList(sliderItems);
// final View.OnClickListener muteClickListener = v -> {
// final int currentItem = binding.mediaList.getCurrentItem();
// if (currentItem < 0 || currentItem >= binding.mediaList.getChildCount()) {
// return;
// }
// final PostChild sliderItem = sliderItems.get(currentItem);
// if (sliderItem.getItemType() != MediaItemType.MEDIA_TYPE_VIDEO) {
// return;
// }
// final View currentView = binding.mediaList.getChildAt(currentItem);
// if (!(currentView instanceof ViewSwitcher)) {
// return;
// }
// final ViewSwitcher viewSwitcher = (ViewSwitcher) currentView;
// final Object tag = viewSwitcher.getTag();
// if (!(tag instanceof SimpleExoPlayer)) {
// return;
// }
// final SimpleExoPlayer player = (SimpleExoPlayer) tag;
// final float intVol = player.getVolume() == 0f ? 1f : 0f;
// player.setVolume(intVol);
// // binding.itemFeedBottom.btnMute.setImageResource(intVol == 0f ? R.drawable.ic_volume_up_24 : R.drawable.ic_volume_off_24);
// // Utils.sessionVolumeFull = intVol == 1f;
// };
// final PostChild firstItem = sliderItems.get(0);
// if (firstItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
// binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
// }
// binding.itemFeedBottom.btnMute.setImageResource(Utils.sessionVolumeFull ? R.drawable.ic_volume_off_24 : R.drawable.ic_volume_up_24);
// binding.itemFeedBottom.btnMute.setOnClickListener(muteClickListener);
}
final View.OnClickListener muteClickListener = v -> {
final int currentItem = binding.mediaList.getCurrentItem();
if (currentItem < 0 || currentItem >= binding.mediaList.getChildCount()) {
return;
}
final ViewerPostModel sliderItem = sliderItems[currentItem];
if (sliderItem.getItemType() != MediaItemType.MEDIA_TYPE_VIDEO) {
return;
}
final View currentView = binding.mediaList.getChildAt(currentItem);
if (!(currentView instanceof ViewSwitcher)) {
return;
}
final ViewSwitcher viewSwitcher = (ViewSwitcher) currentView;
final Object tag = viewSwitcher.getTag();
if (!(tag instanceof SimpleExoPlayer)) {
return;
}
final SimpleExoPlayer player = (SimpleExoPlayer) tag;
final float intVol = player.getVolume() == 0f ? 1f : 0f;
player.setVolume(intVol);
binding.itemFeedBottom.btnMute.setImageResource(intVol == 0f ? R.drawable.ic_volume_up_24 : R.drawable.ic_volume_off_24);
Utils.sessionVolumeFull = intVol == 1f;
};
final ViewerPostModel firstItem = sliderItems[0];
if (firstItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
private void setDimensions(final View view, final PostChild model) {
final ViewGroup.LayoutParams layoutParams = binding.mediaList.getLayoutParams();
int requiredWidth = layoutParams.width;
if (requiredWidth <= 0) {
final ViewTreeObserver.OnPreDrawListener preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
view.getViewTreeObserver().removeOnPreDrawListener(this);
setLayoutParamDimens(binding.mediaList, model);
return true;
}
};
view.getViewTreeObserver().addOnPreDrawListener(preDrawListener);
return;
}
binding.itemFeedBottom.btnMute.setImageResource(Utils.sessionVolumeFull ? R.drawable.ic_volume_off_24 : R.drawable.ic_volume_up_24);
binding.itemFeedBottom.btnMute.setOnClickListener(muteClickListener);
setLayoutParamDimens(binding.mediaList, model);
}
private void setLayoutParamDimens(final View view, final PostChild model) {
final int requiredWidth = view.getMeasuredWidth();
final ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
final int spanHeight = NumberUtils.getResultingHeight(requiredWidth, model.getHeight(), model.getWidth());
layoutParams.height = spanHeight == 0 ? requiredWidth + 1 : spanHeight;
view.requestLayout();
}
private void autoPlay(final int position) {
if (!shouldAutoPlay) {
return;
}
final ChildMediaItemsAdapter adapter = (ChildMediaItemsAdapter) binding.mediaList.getAdapter();
if (adapter == null) {
return;
}
final ViewerPostModel sliderItem = adapter.getItemAtPosition(position);
if (sliderItem.getItemType() != MediaItemType.MEDIA_TYPE_VIDEO) {
return;
}
final ViewSwitcher viewSwitcher = (ViewSwitcher) binding.mediaList.getChildAt(position);
loadPlayer(binding.getRoot().getContext(),
position, sliderItem.getDisplayUrl(),
viewSwitcher,
cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory,
playerChangeListener);
// if (!shouldAutoPlay) {
// return;
// }
// final ChildMediaItemsAdapter adapter = (ChildMediaItemsAdapter) binding.mediaList.getAdapter();
// if (adapter == null) {
// return;
// }
// final ViewerPostModel sliderItem = adapter.getItemAtPosition(position);
// if (sliderItem.getItemType() != MediaItemType.MEDIA_TYPE_VIDEO) {
// return;
// }
// final ViewSwitcher viewSwitcher = (ViewSwitcher) binding.mediaList.getChildAt(position);
// loadPlayer(binding.getRoot().getContext(),
// position, sliderItem.getDisplayUrl(),
// viewSwitcher,
// cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory,
// playerChangeListener);
}
public void startPlayingVideo() {
@ -200,7 +185,8 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
}
private static void loadPlayer(final Context context,
final int position, final String displayUrl,
final int position,
final String displayUrl,
final ViewSwitcher viewSwitcher,
final DataSource.Factory factory,
final PlayerChangeListener playerChangeListener) {
@ -223,119 +209,14 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
player.setVolume(vol);
player.setPlayWhenReady(Utils.settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS));
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(factory).createMediaSource(Uri.parse(displayUrl));
final MediaItem mediaItem = MediaItem.fromUri(displayUrl);
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(factory).createMediaSource(mediaItem);
player.setRepeatMode(Player.REPEAT_MODE_ALL);
player.prepare(mediaSource);
player.setMediaSource(mediaSource);
player.prepare();
player.setVolume(vol);
playerChangeListener.playerChanged(position, player);
viewSwitcher.setTag(player);
}
private static final class ChildMediaItemsAdapter extends PagerAdapter {
// private static final String TAG = "ChildMediaItemsAdapter";
private final ViewerPostModel[] sliderItems;
private final DataSource.Factory factory;
private final PlayerChangeListener playerChangeListener;
private final ViewGroup.LayoutParams layoutParams;
private ChildMediaItemsAdapter(final ViewerPostModel[] sliderItems,
final DataSource.Factory factory,
final PlayerChangeListener playerChangeListener) {
this.sliderItems = sliderItems;
this.factory = factory;
this.playerChangeListener = playerChangeListener;
layoutParams = new ViewGroup.LayoutParams(Utils.displayMetrics.widthPixels, Utils.displayMetrics.widthPixels + 1);
}
@NonNull
@Override
public Object instantiateItem(@NonNull final ViewGroup container, final int position) {
final Context context = container.getContext();
final ViewerPostModel sliderItem = sliderItems[position];
final String displayUrl = sliderItem.getDisplayUrl();
if (sliderItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
final ViewSwitcher viewSwitcher = createViewSwitcher(context, position, sliderItem.getSliderDisplayUrl(), displayUrl);
container.addView(viewSwitcher);
return viewSwitcher;
}
final GenericDraweeHierarchy hierarchy = GenericDraweeHierarchyBuilder.newInstance(container.getResources())
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
.build();
final SimpleDraweeView photoView = new SimpleDraweeView(context, hierarchy);
photoView.setLayoutParams(layoutParams);
final ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(displayUrl))
.setLocalThumbnailPreviewsEnabled(true)
.setProgressiveRenderingEnabled(true)
.build();
photoView.setImageRequest(imageRequest);
container.addView(photoView);
return photoView;
}
@NonNull
private ViewSwitcher createViewSwitcher(final Context context, final int position, final String sliderDisplayUrl, final String displayUrl) {
final ViewSwitcher viewSwitcher = new ViewSwitcher(context);
viewSwitcher.setLayoutParams(layoutParams);
final FrameLayout frameLayout = new FrameLayout(context);
frameLayout.setLayoutParams(layoutParams);
final GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(context.getResources())
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
.build();
final SimpleDraweeView simpleDraweeView = new SimpleDraweeView(context, hierarchy);
simpleDraweeView.setLayoutParams(layoutParams);
simpleDraweeView.setImageURI(sliderDisplayUrl);
frameLayout.addView(simpleDraweeView);
final AppCompatImageView imageView = new AppCompatImageView(context);
final int px = Utils.convertDpToPx(50);
final FrameLayout.LayoutParams playButtonLayoutParams = new FrameLayout.LayoutParams(px, px);
playButtonLayoutParams.gravity = Gravity.CENTER;
imageView.setLayoutParams(playButtonLayoutParams);
imageView.setImageResource(R.drawable.exo_icon_play);
frameLayout.addView(imageView);
viewSwitcher.addView(frameLayout);
final PlayerView playerView = new PlayerView(context);
viewSwitcher.addView(playerView);
if (shouldAutoPlay && position == 0) {
loadPlayer(context, position, displayUrl, viewSwitcher, factory, playerChangeListener);
} else
frameLayout.setOnClickListener(v -> loadPlayer(context, position, displayUrl, viewSwitcher, factory, playerChangeListener));
return viewSwitcher;
}
@Override
public void destroyItem(@NonNull final ViewGroup container, final int position, @NonNull final Object object) {
final View view = container.getChildAt(position);
// Log.d(TAG, "destroy position: " + position + ", view: " + view);
if (view instanceof ViewSwitcher) {
final Object tag = view.getTag();
if (tag instanceof SimpleExoPlayer) {
final SimpleExoPlayer player = (SimpleExoPlayer) tag;
player.release();
}
}
container.removeView((View) object);
}
@Override
public int getCount() {
return sliderItems != null ? sliderItems.length : 0;
}
@Override
public boolean isViewFromObject(@NonNull final View view, @NonNull final Object object) {
return view.equals(object);
}
public ViewerPostModel getItemAtPosition(final int position) {
return sliderItems[0];
}
}
}

View File

@ -1,7 +1,6 @@
package awais.instagrabber.adapters.viewholder.feed;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
@ -9,21 +8,14 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
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.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import awais.instagrabber.R;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.customviews.VideoPlayerCallbackAdapter;
import awais.instagrabber.customviews.VideoPlayerViewHelper;
import awais.instagrabber.databinding.ItemFeedVideoBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.NumberUtils;
@ -35,27 +27,26 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
private static final String TAG = "FeedVideoViewHolder";
private final ItemFeedVideoBinding binding;
private final FeedAdapterV2.FeedItemCallback feedItemCallback;
private final Handler handler;
private final DefaultDataSourceFactory dataSourceFactory;
private CacheDataSourceFactory cacheDataSourceFactory;
private FeedModel feedModel;
private SimpleExoPlayer player;
private final Runnable loadRunnable = new Runnable() {
@Override
public void run() {
loadPlayer(feedModel);
}
};
// private final Runnable loadRunnable = new Runnable() {
// @Override
// public void run() {
// // loadPlayer(feedModel);
// }
// };
public FeedVideoViewHolder(@NonNull final ItemFeedVideoBinding binding,
final MentionClickListener mentionClickListener,
final View.OnClickListener clickListener,
final View.OnLongClickListener longClickListener) {
super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, mentionClickListener, clickListener, longClickListener);
final FeedAdapterV2.FeedItemCallback feedItemCallback) {
super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, feedItemCallback);
this.binding = binding;
binding.itemFeedBottom.videoViewsContainer.setVisibility(View.VISIBLE);
this.feedItemCallback = feedItemCallback;
binding.itemFeedBottom.tvVideoViews.setVisibility(View.VISIBLE);
handler = new Handler(Looper.getMainLooper());
final Context context = binding.getRoot().getContext();
dataSourceFactory = new DefaultDataSourceFactory(context, "instagram");
@ -69,90 +60,80 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
public void bindItem(final FeedModel feedModel) {
// Log.d(TAG, "Binding post: " + feedModel.getPostId());
this.feedModel = feedModel;
setThumbnail(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() {
private void setThumbnail(final FeedModel feedModel) {
final ViewGroup.LayoutParams layoutParams = binding.thumbnailParent.getLayoutParams();
final int requiredWidth = Utils.displayMetrics.widthPixels;
layoutParams.width = feedModel.getImageWidth() == 0 ? requiredWidth : feedModel.getImageWidth();
layoutParams.height = feedModel.getImageHeight() == 0 ? requiredWidth + 1 : feedModel.getImageHeight();
binding.thumbnailParent.requestLayout();
final ImageRequest thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(feedModel.getThumbnailUrl()))
.setProgressiveRenderingEnabled(true)
.build();
final DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(thumbnailRequest)
.build();
binding.thumbnail.setController(controller);
binding.thumbnailParent.setOnClickListener(v -> loadPlayer(feedModel));
}
@Override
public void onThumbnailClick() {
feedItemCallback.onPostClick(feedModel, binding.itemFeedTop.ivProfilePic, binding.videoPost.thumbnail);
}
private void loadPlayer(final FeedModel feedModel) {
if (feedModel == null) {
return;
}
// Log.d(TAG, "playing post:" + feedModel.getPostId());
if (binding.viewSwitcher.getDisplayedChild() == 0) {
binding.viewSwitcher.showNext();
}
binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
final ViewGroup.LayoutParams layoutParams = binding.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.playerView.requestLayout();
float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
setMuteIcon(vol);
player = (SimpleExoPlayer) binding.playerView.getPlayer();
if (player != null) {
player.release();
}
player = new SimpleExoPlayer.Builder(itemView.getContext())
.setLooper(Looper.getMainLooper())
.build();
player.setVolume(vol);
player.setPlayWhenReady(true);
final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(Uri.parse(feedModel.getDisplayUrl()));
player.setRepeatMode(Player.REPEAT_MODE_ALL);
player.prepare(mediaSource);
binding.playerView.setPlayer(player);
final SimpleExoPlayer finalPlayer = player;
binding.itemFeedBottom.btnMute.setOnClickListener(v -> {
final float intVol = finalPlayer.getVolume() == 0f ? 1f : 0f;
finalPlayer.setVolume(intVol);
setMuteIcon(intVol);
Utils.sessionVolumeFull = intVol == 1f;
@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,
feedModel.getDisplayUrl(),
vol,
aspectRatio,
feedModel.getThumbnailUrl(),
false,
null,
videoPlayerCallback);
binding.videoPost.thumbnail.post(() -> {
if (feedModel.getImageHeight() > 0.8 * Utils.displayMetrics.heightPixels) {
final ViewGroup.LayoutParams layoutParams = binding.videoPost.thumbnail.getLayoutParams();
layoutParams.height = (int) (0.8 * Utils.displayMetrics.heightPixels);
binding.videoPost.thumbnail.requestLayout();
}
});
binding.playerView.setOnClickListener(v -> finalPlayer.setPlayWhenReady(!finalPlayer.getPlayWhenReady()));
// 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);
// binding.itemFeedBottom.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24 : R.drawable.ic_volume_off_24);
}
public FeedModel getCurrentFeedModel() {
return feedModel;
}
public void stopPlaying() {
// Log.d(TAG, "Stopping post: " + feedModel.getPostId() + ", player: " + player + ", player.isPlaying: " + (player != null && player.isPlaying()));
handler.removeCallbacks(loadRunnable);
if (player != null) {
player.release();
}
if (binding.viewSwitcher.getDisplayedChild() == 1) {
binding.viewSwitcher.showPrevious();
}
}
public void startPlaying() {
handler.removeCallbacks(loadRunnable);
handler.postDelayed(loadRunnable, 800);
}
// public void stopPlaying() {
// // Log.d(TAG, "Stopping post: " + feedModel.getPostId() + ", player: " + player + ", player.isPlaying: " + (player != null && player.isPlaying()));
// handler.removeCallbacks(loadRunnable);
// if (player != null) {
// player.release();
// }
// if (binding.videoPost.root.getDisplayedChild() == 1) {
// binding.videoPost.root.showPrevious();
// }
// }
//
// public void startPlaying() {
// handler.removeCallbacks(loadRunnable);
// handler.postDelayed(loadRunnable, 800);
// }
}

View File

@ -0,0 +1,45 @@
package awais.instagrabber.animations;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
public class ResizeAnimation extends Animation {
private static final String TAG = "ResizeAnimation";
final View view;
final int startHeight;
final int targetHeight;
final int startWidth;
final int targetWidth;
public ResizeAnimation(final View view,
final int startHeight,
final int startWidth,
final int targetHeight,
final int targetWidth) {
this.view = view;
this.startHeight = startHeight;
this.targetHeight = targetHeight;
this.startWidth = startWidth;
this.targetWidth = targetWidth;
}
@Override
protected void applyTransformation(final float interpolatedTime, final Transformation t) {
// Log.d(TAG, "applyTransformation: interpolatedTime: " + interpolatedTime);
view.getLayoutParams().height = (int) (startHeight + (targetHeight - startHeight) * interpolatedTime);
view.getLayoutParams().width = (int) (startWidth + (targetWidth - startWidth) * interpolatedTime);
view.requestLayout();
}
@Override
public void initialize(final int width, final int height, final int parentWidth, final int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
}
@Override
public boolean willChangeBounds() {
return true;
}
}

View File

@ -12,7 +12,7 @@ import org.json.JSONObject;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
@ -25,23 +25,20 @@ import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
public final class CommentsFetcher extends AsyncTask<Void, Void, CommentModel[]> {
private final String shortCode;
private final FetchListener<CommentModel[]> fetchListener;
public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentModel>> {
private static final String TAG = "CommentsFetcher";
/*
* i fucking spent the whole day on this and fixing all the fucking problems in this class.
* DO NO FUCK WITH THIS CODE!
* -AWAiS (The Badak) @the.badak
*/
public CommentsFetcher(final String shortCode, final FetchListener<CommentModel[]> fetchListener) {
private final String shortCode;
private final FetchListener<List<CommentModel>> fetchListener;
public CommentsFetcher(final String shortCode, final FetchListener<List<CommentModel>> fetchListener) {
this.shortCode = shortCode;
this.fetchListener = fetchListener;
}
@NonNull
@Override
protected CommentModel[] doInBackground(final Void... voids) {
protected List<CommentModel> doInBackground(final Void... voids) {
/*
"https://www.instagram.com/graphql/query/?query_hash=97b41c52301f77ce508f55e66d17620e&variables=" + "{\"shortcode\":\"" + shortcode + "\",\"first\":50,\"after\":\"" + endCursor + "\"}";
@ -50,23 +47,20 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, CommentModel[]>
https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables={"comment_id":"18100041898085322","first":50,"after":""}
*/
final ArrayList<CommentModel> commentModels = getParentComments();
final List<CommentModel> commentModels = getParentComments();
for (final CommentModel commentModel : commentModels) {
final CommentModel[] childCommentModels = commentModel.getChildCommentModels();
final List<CommentModel> childCommentModels = commentModel.getChildCommentModels();
if (childCommentModels != null) {
final int childCommentsLen = childCommentModels.length;
final CommentModel lastChild = childCommentModels[childCommentsLen - 1];
final int childCommentsLen = childCommentModels.size();
final CommentModel lastChild = childCommentModels.get(childCommentsLen - 1);
if (lastChild != null && lastChild.hasNextPage() && !TextUtils.isEmpty(lastChild.getEndCursor())) {
final CommentModel[] remoteChildComments = getChildComments(commentModel.getId());
final List<CommentModel> remoteChildComments = getChildComments(commentModel.getId());
commentModel.setChildCommentModels(remoteChildComments);
lastChild.setPageCursor(false, null);
}
}
}
return commentModels.toArray(new CommentModel[0]);
return commentModels;
}
@Override
@ -75,14 +69,13 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, CommentModel[]>
}
@Override
protected void onPostExecute(final CommentModel[] result) {
protected void onPostExecute(final List<CommentModel> result) {
if (fetchListener != null) fetchListener.onResult(result);
}
@NonNull
private synchronized CommentModel[] getChildComments(final String commentId) {
final ArrayList<CommentModel> commentModels = new ArrayList<>();
private synchronized List<CommentModel> getChildComments(final String commentId) {
final List<CommentModel> commentModels = new ArrayList<>();
String endCursor = "";
while (endCursor != null) {
final String url = "https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables=" +
@ -96,7 +89,8 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, CommentModel[]>
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) break;
else {
final JSONObject data = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("data")
.getJSONObject("comment").getJSONObject("edge_threaded_comments");
.getJSONObject("comment")
.getJSONObject("edge_threaded_comments");
final JSONObject pageInfo = data.getJSONObject("page_info");
endCursor = pageInfo.getString("end_cursor");
@ -110,43 +104,54 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, CommentModel[]>
if (childComment != null) {
final JSONObject owner = childComment.getJSONObject("owner");
final ProfileModel profileModel = new ProfileModel(false, false, false,
owner.getString(Constants.EXTRAS_ID),
owner.getString(Constants.EXTRAS_USERNAME),
null, null, null,
owner.getString("profile_pic_url"),
null, 0, 0, 0, false, false, false, false);
final ProfileModel profileModel = new ProfileModel(false,
false,
false,
owner.getString(Constants.EXTRAS_ID),
owner.getString(Constants.EXTRAS_USERNAME),
null,
null,
null,
owner.getString("profile_pic_url"),
null,
0,
0,
0,
false,
false,
false,
false);
final JSONObject likedBy = childComment.optJSONObject("edge_liked_by");
commentModels.add(new CommentModel(childComment.getString(Constants.EXTRAS_ID),
childComment.getString("text"),
childComment.getLong("created_at"),
likedBy != null ? likedBy.optLong("count", 0) : 0,
childComment.getBoolean("viewer_has_liked"),
profileModel));
childComment.getString("text"),
childComment.getLong("created_at"),
likedBy != null ? likedBy.optLong("count", 0) : 0,
childComment.getBoolean("viewer_has_liked"),
profileModel));
}
}
}
}
conn.disconnect();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_COMMENTS_FETCHER, "getChildComments",
new Pair<>("commentModels.size", commentModels.size()));
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
logCollector.appendException(e,
LogCollector.LogFile.ASYNC_COMMENTS_FETCHER,
"getChildComments",
new Pair<>("commentModels.size", commentModels.size()));
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
break;
}
}
return commentModels.toArray(new CommentModel[0]);
return commentModels;
}
@NonNull
private synchronized ArrayList<CommentModel> getParentComments() {
final ArrayList<CommentModel> commentModelsList = new ArrayList<>();
private synchronized List<CommentModel> getParentComments() {
final List<CommentModel> commentModels = new ArrayList<>();
String endCursor = "";
while (endCursor != null) {
final String url = "https://www.instagram.com/graphql/query/?query_hash=bc3296d1ce80a24b1b6e40b1e72903f5&variables=" +
@ -160,7 +165,9 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, CommentModel[]>
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) break;
else {
final JSONObject parentComments = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("data")
.getJSONObject("shortcode_media").getJSONObject("edge_media_to_parent_comment");
.getJSONObject("shortcode_media")
.getJSONObject(
"edge_media_to_parent_comment");
final JSONObject pageInfo = parentComments.getJSONObject("page_info");
endCursor = pageInfo.optString("end_cursor");
@ -182,31 +189,37 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, CommentModel[]>
final JSONArray comments = parentComments.getJSONArray("edges");
final int commentsLen = comments.length();
final CommentModel[] commentModels = new CommentModel[commentsLen];
for (int i = 0; i < commentsLen; ++i) {
final JSONObject comment = comments.getJSONObject(i).getJSONObject("node");
final JSONObject owner = comment.getJSONObject("owner");
final ProfileModel profileModel = new ProfileModel(false, false,
owner.optBoolean("is_verified"),
owner.getString(Constants.EXTRAS_ID),
owner.getString(Constants.EXTRAS_USERNAME),
null, null, null,
owner.getString("profile_pic_url"),
null, 0, 0, 0, false, false, false, false);
final ProfileModel profileModel = new ProfileModel(false,
false,
owner.optBoolean("is_verified"),
owner.getString(Constants.EXTRAS_ID),
owner.getString(Constants.EXTRAS_USERNAME),
null,
null,
null,
owner.getString("profile_pic_url"),
null,
0,
0,
0,
false,
false,
false,
false);
final JSONObject likedBy = comment.optJSONObject("edge_liked_by");
final String commentId = comment.getString(Constants.EXTRAS_ID);
commentModels[i] = new CommentModel(commentId,
comment.getString("text"),
comment.getLong("created_at"),
likedBy != null ? likedBy.optLong("count", 0) : 0,
comment.getBoolean("viewer_has_liked"),
profileModel);
final CommentModel commentModel = new CommentModel(commentId,
comment.getString("text"),
comment.getLong("created_at"),
likedBy != null ? likedBy.optLong("count", 0) : 0,
comment.getBoolean("viewer_has_liked"),
profileModel);
JSONObject tempJsonObject;
final JSONArray childCommentsArray;
final int childCommentsLen;
if ((tempJsonObject = comment.optJSONObject("edge_threaded_comments")) != null &&
@ -223,47 +236,53 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, CommentModel[]>
hasNextPage = false;
}
final CommentModel[] childCommentModels = new CommentModel[childCommentsLen];
final List<CommentModel> childCommentModels = new ArrayList<>();
for (int j = 0; j < childCommentsLen; ++j) {
final JSONObject childComment = childCommentsArray.getJSONObject(j).getJSONObject("node");
tempJsonObject = childComment.getJSONObject("owner");
final ProfileModel childProfileModel = new ProfileModel(false, false,
tempJsonObject.optBoolean("is_verified"),
tempJsonObject.getString(Constants.EXTRAS_ID),
tempJsonObject.getString(Constants.EXTRAS_USERNAME),
null, null, null,
tempJsonObject.getString("profile_pic_url"),
null, 0, 0, 0, false, false, false, false);
final ProfileModel childProfileModel = new ProfileModel(false,
false,
tempJsonObject.optBoolean("is_verified"),
tempJsonObject.getString(Constants.EXTRAS_ID),
tempJsonObject.getString(Constants.EXTRAS_USERNAME),
null,
null,
null,
tempJsonObject.getString("profile_pic_url"),
null,
0,
0,
0,
false,
false,
false,
false);
tempJsonObject = childComment.optJSONObject("edge_liked_by");
childCommentModels[j] = new CommentModel(childComment.getString(Constants.EXTRAS_ID),
childComment.getString("text"),
childComment.getLong("created_at"),
tempJsonObject != null ? tempJsonObject.optLong("count", 0) : 0,
childComment.getBoolean("viewer_has_liked"),
childProfileModel);
childCommentModels.add(new CommentModel(childComment.getString(Constants.EXTRAS_ID),
childComment.getString("text"),
childComment.getLong("created_at"),
tempJsonObject != null ? tempJsonObject.optLong("count", 0) : 0,
childComment.getBoolean("viewer_has_liked"),
childProfileModel));
}
childCommentModels[childCommentsLen - 1].setPageCursor(hasNextPage, childEndCursor);
commentModels[i].setChildCommentModels(childCommentModels);
childCommentModels.get(childCommentsLen - 1).setPageCursor(hasNextPage, childEndCursor);
commentModel.setChildCommentModels(childCommentModels);
}
commentModels.add(commentModel);
}
Collections.addAll(commentModelsList, commentModels);
}
conn.disconnect();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_COMMENTS_FETCHER, "getParentComments",
new Pair<>("commentModelsList.size", commentModelsList.size()));
new Pair<>("commentModelsList.size", commentModels.size()));
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
break;
}
}
return commentModelsList;
return commentModels;
}
}

View File

@ -1,202 +0,0 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.DiscoverItemModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.NetworkUtils;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class DiscoverFetcher extends AsyncTask<Void, Void, DiscoverItemModel[]> {
private final String cluster, maxId, rankToken;
private final FetchListener<DiscoverItemModel[]> fetchListener;
private int lastId = 0;
private boolean isFirst, moreAvailable;
private String nextMaxId;
public DiscoverFetcher(final String cluster, final String maxId, final String rankToken,
final FetchListener<DiscoverItemModel[]> fetchListener, final boolean isFirst) {
this.cluster = cluster == null ? "explore_all%3A0" : cluster.replace(":", "%3A");
this.maxId = maxId == null ? "" : "&max_id=" + maxId;
this.rankToken = rankToken;
this.fetchListener = fetchListener;
this.isFirst = isFirst;
}
@Nullable
@Override
protected final DiscoverItemModel[] doInBackground(final Void... voids) {
DiscoverItemModel[] result = null;
final ArrayList<DiscoverItemModel> discoverItemModels = fetchItems(null, maxId);
if (discoverItemModels != null) {
result = discoverItemModels.toArray(new DiscoverItemModel[0]);
if (result.length > 0) {
final DiscoverItemModel lastModel = result[result.length - 1];
if (lastModel != null && nextMaxId != null) lastModel.setMore(moreAvailable, nextMaxId);
}
}
return result;
}
private ArrayList<DiscoverItemModel> fetchItems(ArrayList<DiscoverItemModel> discoverItemModels, final String maxId) {
try {
final String url = "https://www.instagram.com/explore/grid/?is_prefetch=false&omit_cover_media=true&module=explore_popular" +
"&use_sectional_payload=false&cluster_id=" + cluster + "&include_fixed_destinations=true&session_id=" + rankToken + maxId;
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setUseCaches(false);
urlConnection.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONObject discoverResponse = new JSONObject(NetworkUtils.readFromConnection(urlConnection));
moreAvailable = discoverResponse.getBoolean("more_available");
nextMaxId = discoverResponse.optString("next_max_id");
final JSONArray sectionalItems = discoverResponse.getJSONArray("sectional_items");
if (discoverItemModels == null) discoverItemModels = new ArrayList<>(sectionalItems.length() * 2);
for (int i = 0; i < sectionalItems.length(); ++i) {
final JSONObject sectionItem = sectionalItems.getJSONObject(i);
final String feedType = sectionItem.getString("feed_type");
final String layoutType = sectionItem.getString("layout_type");
if (sectionItem.has("layout_content") && feedType.equals("media")) {
final JSONObject layoutContent = sectionItem.getJSONObject("layout_content");
if ("media_grid".equals(layoutType)) {
final JSONArray medias = layoutContent.getJSONArray("medias");
for (int j = 0; j < medias.length(); ++j)
discoverItemModels.add(makeDiscoverModel(medias.getJSONObject(j).getJSONObject("media")));
} else {
final boolean isOneSide = "one_by_two_left".equals(layoutType);
if (isOneSide || "two_by_two_right".equals(layoutType)) {
final JSONObject layoutItem = layoutContent.getJSONObject(isOneSide ? "one_by_two_item" : "two_by_two_item");
if (layoutItem.has("media"))
discoverItemModels.add(makeDiscoverModel(layoutItem.getJSONObject("media")));
if (layoutContent.has("fill_items")) {
final JSONArray fillItems = layoutContent.getJSONArray("fill_items");
for (int j = 0; j < fillItems.length(); ++j)
discoverItemModels.add(makeDiscoverModel(fillItems.getJSONObject(j).getJSONObject("media")));
}
}
}
}
}
discoverItemModels.trimToSize();
urlConnection.disconnect();
// hack to fetch 50+ items
if (this.isFirst) {
final int size = discoverItemModels.size();
if (size > 50) this.isFirst = false;
discoverItemModels = fetchItems(discoverItemModels, "&max_id=" + (lastId++));
}
} else {
urlConnection.disconnect();
}
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_DISCOVER_FETCHER, "fetchItems",
new Pair<>("maxId", maxId),
new Pair<>("lastId", lastId),
new Pair<>("isFirst", isFirst),
new Pair<>("nextMaxId", nextMaxId));
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
return discoverItemModels;
}
@NonNull
private DiscoverItemModel makeDiscoverModel(@NonNull final JSONObject media) throws Exception {
final JSONObject user = media.getJSONObject(Constants.EXTRAS_USER);
final String username = user.getString(Constants.EXTRAS_USERNAME);
// final ProfileModel userProfileModel = new ProfileModel(user.getBoolean("is_private"),
// user.getBoolean("is_verified"),
// String.valueOf(user.get("pk")),
// username,
// user.getString("full_name"),
// null,
// user.getString("profile_pic_url"), null,
// 0, 0, 0);
// final String comment;
// if (!media.has("caption")) comment = null;
// else {
// final Object caption = media.get("caption");
// comment = caption instanceof JSONObject ? ((JSONObject) caption).getString("text") : null;
// }
final MediaItemType mediaType = ResponseBodyUtils.getMediaItemType(media.getInt("media_type"));
final ResponseBodyUtils.ThumbnailDetails thumbnailUrl = ResponseBodyUtils.getThumbnailUrl(media, mediaType);
final DiscoverItemModel model = new DiscoverItemModel(mediaType,
media.getString("pk"),
media.getString("code"),
thumbnailUrl != null ? thumbnailUrl.url : null);
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" +
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/" + username) : ""));
// to check if file exists
File customDir = null;
if (settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = settingsHelper.getString(FOLDER_PATH);
if (!TextUtils.isEmpty(customPath)) customDir = new File(customPath +
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER)
? "/" + username
: ""));
}
DownloadUtils.checkExistence(downloadDir, customDir, mediaType == MediaItemType.MEDIA_TYPE_SLIDER, model);
return model;
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
}
@Override
protected void onPostExecute(final DiscoverItemModel[] discoverItemModels) {
if (fetchListener != null) fetchListener.onResult(discoverItemModels);
}
}

View File

@ -0,0 +1,56 @@
package awais.instagrabber.asyncs;
import java.util.List;
import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.webservices.DiscoverService;
import awais.instagrabber.webservices.ServiceCallback;
public class DiscoverPostFetchService implements PostFetcher.PostFetchService {
private static final String TAG = "DiscoverPostFetchService";
private final DiscoverService discoverService;
private final DiscoverService.TopicalExploreRequest topicalExploreRequest;
private boolean moreAvailable = false;
public DiscoverPostFetchService(final DiscoverService.TopicalExploreRequest topicalExploreRequest) {
this.topicalExploreRequest = topicalExploreRequest;
discoverService = DiscoverService.getInstance();
}
@Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
discoverService.topicalExplore(topicalExploreRequest, new ServiceCallback<DiscoverService.TopicalExploreResponse>() {
@Override
public void onSuccess(final DiscoverService.TopicalExploreResponse result) {
if (result == null) {
onFailure(new RuntimeException("result is null"));
return;
}
moreAvailable = result.isMoreAvailable();
topicalExploreRequest.setMaxId(result.getNextMaxId());
if (fetchListener != null) {
fetchListener.onResult(result.getItems());
}
}
@Override
public void onFailure(final Throwable t) {
if (fetchListener != null) {
fetchListener.onFailure(t);
}
}
});
}
@Override
public void reset() {
topicalExploreRequest.setMaxId(-1);
}
@Override
public boolean hasNextPage() {
return moreAvailable;
}
}

View File

@ -1,246 +0,0 @@
package awais.instagrabber.asyncs;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaMetadataRetriever;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.FileProvider;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.atomic.AtomicReference;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Constants.NOTIF_GROUP_NAME;
import static awais.instagrabber.utils.Utils.logCollector;
import static awaisomereport.LogCollector.LogFile;
public final class DownloadAsync extends AsyncTask<Void, Float, File> {
private static final String TAG = "DownloadAsync";
private static int lastNotifId = 1;
private final int currentNotifId;
private final AtomicReference<Context> context;
private final File outFile;
private final String url;
private final FetchListener<File> fetchListener;
private final Resources resources;
private final NotificationCompat.Builder downloadNotif;
private String shortCode, username;
private final NotificationManagerCompat notificationManager;
public DownloadAsync(@NonNull final Context context,
final String url,
final File outFile,
final FetchListener<File> fetchListener) {
this.context = new AtomicReference<>(context);
this.resources = context.getResources();
this.url = url;
this.outFile = outFile;
this.fetchListener = fetchListener;
this.shortCode = this.username = resources.getString(R.string.downloader_started);
this.currentNotifId = ++lastNotifId;
if (++lastNotifId + 1 == Integer.MAX_VALUE) lastNotifId = 1;
@StringRes final int titleRes = R.string.downloader_downloading_post;
downloadNotif = new NotificationCompat.Builder(context, Constants.DOWNLOAD_CHANNEL_ID)
.setCategory(NotificationCompat.CATEGORY_STATUS)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentText(shortCode == null ? username : shortCode)
.setOngoing(true)
.setProgress(100, 0, false)
.setAutoCancel(false)
.setOnlyAlertOnce(true)
.setContentTitle(resources.getString(titleRes));
notificationManager = NotificationManagerCompat.from(context.getApplicationContext());
notificationManager.notify(currentNotifId, downloadNotif.build());
}
public DownloadAsync setItems(final String shortCode, final String username) {
this.shortCode = shortCode;
this.username = username;
if (downloadNotif != null) downloadNotif.setContentText(this.shortCode == null ? this.username : this.shortCode);
return this;
}
@Nullable
@Override
protected File doInBackground(final Void... voids) {
try {
final URLConnection urlConnection = new URL(url).openConnection();
final long fileSize = Build.VERSION.SDK_INT >= 24 ? urlConnection.getContentLengthLong() :
urlConnection.getContentLength();
float totalRead = 0;
try (final BufferedInputStream bis = new BufferedInputStream(urlConnection.getInputStream());
final FileOutputStream fos = new FileOutputStream(outFile)) {
final byte[] buffer = new byte[0x2000];
int count;
boolean deletedIPTC = false;
while ((count = bis.read(buffer, 0, 0x2000)) != -1) {
totalRead = totalRead + count;
if (!deletedIPTC) {
int iptcStart = -1;
int fbmdStart = -1;
int fbmdBytesLen = -1;
for (int i = 0; i < buffer.length; ++i) {
if (buffer[i] == (byte) 0xFF && buffer[i + 1] == (byte) 0xED)
iptcStart = i;
else if (buffer[i] == (byte) 'F' && buffer[i + 1] == (byte) 'B'
&& buffer[i + 2] == (byte) 'M' && buffer[i + 3] == (byte) 'D') {
fbmdStart = i;
fbmdBytesLen = buffer[i - 10] << 24 | (buffer[i - 9] & 0xFF) << 16 |
(buffer[i - 8] & 0xFF) << 8 | (buffer[i - 7] & 0xFF) |
(buffer[i - 6] & 0xFF);
break;
}
}
if (iptcStart != -1 && fbmdStart != -1 && fbmdBytesLen != -1) {
final int fbmdDataLen = (iptcStart + (fbmdStart - iptcStart) + (fbmdBytesLen - iptcStart)) - 4;
fos.write(buffer, 0, iptcStart);
fos.write(buffer, fbmdDataLen + iptcStart, count - fbmdDataLen - iptcStart);
publishProgress(totalRead * 100f / fileSize);
deletedIPTC = true;
continue;
}
}
fos.write(buffer, 0, count);
publishProgress(totalRead * 100f / fileSize);
}
fos.flush();
}
return outFile;
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "doInBackground",
new Pair<>("context", context.get()),
new Pair<>("resources", resources),
new Pair<>("lastNotifId", lastNotifId),
new Pair<>("downloadNotif", downloadNotif),
new Pair<>("currentNotifId", currentNotifId),
new Pair<>("notificationManager", notificationManager));
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
return null;
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
}
@Override
protected void onProgressUpdate(@NonNull final Float... values) {
if (downloadNotif != null) {
downloadNotif.setProgress(100, values[0].intValue(), false);
notificationManager.notify(currentNotifId, downloadNotif.build());
}
}
@Override
protected void onPostExecute(final File result) {
if (result != null) {
final Context context = this.context.get();
context.sendBroadcast(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT ?
new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.fromFile(Environment.getExternalStorageDirectory())) :
new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(result.getAbsoluteFile()))
);
MediaScannerConnection.scanFile(context, new String[]{result.getAbsolutePath()}, null, null);
if (notificationManager != null) {
final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", result);
final ContentResolver contentResolver = context.getContentResolver();
Bitmap bitmap = null;
if (Utils.isImage(uri, contentResolver)) {
try (final InputStream inputStream = contentResolver.openInputStream(uri)) {
bitmap = BitmapFactory.decodeStream(inputStream);
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_1");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
}
if (bitmap == null) {
final MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
try {
retriever.setDataSource(context, uri);
} catch (final Exception e) {
retriever.setDataSource(result.getAbsolutePath());
}
bitmap = retriever.getFrameAtTime();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
try {
retriever.close();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_2");
}
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_3");
}
}
final String downloadComplete = resources.getString(R.string.downloader_complete);
downloadNotif.setContentText(null).setContentTitle(downloadComplete).setProgress(0, 0, false)
.setWhen(System.currentTimeMillis()).setOngoing(false).setOnlyAlertOnce(false).setAutoCancel(true)
.setGroup(NOTIF_GROUP_NAME).setGroupSummary(true).setContentIntent(
PendingIntent.getActivity(context, 2020, new Intent(Intent.ACTION_VIEW, uri)
.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_FROM_BACKGROUND | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
.putExtra(Intent.EXTRA_STREAM, uri), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT));
if (bitmap != null)
downloadNotif.setStyle(new NotificationCompat.BigPictureStyle().setBigContentTitle(downloadComplete).bigPicture(bitmap))
.setLargeIcon(bitmap).setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL);
notificationManager.cancel(currentNotifId);
notificationManager.notify(currentNotifId + 1, downloadNotif.build());
}
}
if (fetchListener != null) fetchListener.onResult(result);
}
}

View File

@ -0,0 +1,42 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.utils.DownloadUtils;
public final class DownloadedCheckerAsyncTask extends AsyncTask<FeedModel, Void, Map<String, List<Boolean>>> {
private static final String TAG = "DownloadedCheckerAsyncTask";
private final OnCheckResultListener listener;
public DownloadedCheckerAsyncTask(final OnCheckResultListener listener) {
this.listener = listener;
}
@Override
protected Map<String, List<Boolean>> doInBackground(final FeedModel... feedModels) {
if (feedModels == null) {
return null;
}
final Map<String, List<Boolean>> map = new HashMap<>();
for (final FeedModel feedModel : feedModels) {
map.put(feedModel.getPostId(), DownloadUtils.checkDownloaded(feedModel));
}
return map;
}
@Override
protected void onPostExecute(final Map<String, List<Boolean>> result) {
if (listener == null) return;
listener.onResult(result);
}
public interface OnCheckResultListener {
void onResult(final Map<String, List<Boolean>> result);
}
}

View File

@ -1,243 +0,0 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONObject;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.NetworkUtils;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
public final class FeedFetcher extends AsyncTask<Void, Void, FeedModel[]> {
private static final String TAG = "FeedFetcher";
private static final int maxItemsToLoad = 25; // max is 50, but that's too many posts, setting more than 30 is gay
private final String endCursor;
private final FetchListener<FeedModel[]> fetchListener;
public FeedFetcher(final FetchListener<FeedModel[]> fetchListener) {
this.endCursor = "";
this.fetchListener = fetchListener;
}
public FeedFetcher(final String endCursor, final FetchListener<FeedModel[]> fetchListener) {
this.endCursor = endCursor == null ? "" : endCursor;
this.fetchListener = fetchListener;
}
@Nullable
@Override
protected final FeedModel[] doInBackground(final Void... voids) {
FeedModel[] result = null;
try {
//
// stories: 04334405dbdef91f2c4e207b84c204d7 && https://i.instagram.com/api/v1/feed/reels_tray/
// https://www.instagram.com/graphql/query/?query_hash=04334405dbdef91f2c4e207b84c204d7&variables={"only_stories":true,"stories_prefetch":false,"stories_video_dash_manifest":false}
// ///////////////////////////////////////////////
// feed:
// https://www.instagram.com/graphql/query/?query_hash=6b838488258d7a4820e48d209ef79eb1&variables=
// {"cached_feed_item_ids":[],"fetch_media_item_count":12,"fetch_media_item_cursor":"<end_cursor>","fetch_comment_count":4,"fetch_like":3,"has_stories":false,"has_threaded_comments":true}
// only used: fetch_media_item_cursor, fetch_media_item_count: 100 (max 50), has_threaded_comments = true
// //////////////////////////////////////////////
// more unknowns: https://github.com/qsniyg/rssit/blob/master/rssit/generators/instagram.py
//
final String url = "https://www.instagram.com/graphql/query/?query_hash=6b838488258d7a4820e48d209ef79eb1&variables=" +
"{\"fetch_media_item_count\":" + maxItemsToLoad + ",\"has_threaded_comments\":true,\"fetch_media_item_cursor\":\"" + endCursor + "\"}";
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
final String json = NetworkUtils.readFromConnection(urlConnection);
// Log.d(TAG, json);
final JSONObject timelineFeed = new JSONObject(json).getJSONObject("data")
.getJSONObject(Constants.EXTRAS_USER).getJSONObject("edge_web_feed_timeline");
final String endCursor;
final boolean hasNextPage;
final JSONObject pageInfo = timelineFeed.getJSONObject("page_info");
if (pageInfo.has("has_next_page")) {
hasNextPage = pageInfo.getBoolean("has_next_page");
endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null;
} else {
hasNextPage = false;
endCursor = null;
}
final JSONArray feedItems = timelineFeed.getJSONArray("edges");
final int feedLen = feedItems.length();
final ArrayList<FeedModel> feedModelsList = new ArrayList<>(feedLen);
for (int i = 0; i < feedLen; ++i) {
final JSONObject feedItem = feedItems.getJSONObject(i).getJSONObject("node");
final String mediaType = feedItem.optString("__typename");
if (mediaType.isEmpty() || "GraphSuggestedUserFeedUnit".equals(mediaType))
continue;
final boolean isVideo = feedItem.optBoolean("is_video");
final long videoViews = feedItem.optLong("video_view_count", 0);
final String displayUrl = feedItem.optString("display_url");
if (TextUtils.isEmpty(displayUrl)) continue;
final String resourceUrl;
if (isVideo) resourceUrl = feedItem.getString("video_url");
else
resourceUrl = feedItem.has("display_resources") ? ResponseBodyUtils.getHighQualityImage(feedItem) : displayUrl;
ProfileModel profileModel = null;
if (feedItem.has("owner")) {
final JSONObject owner = feedItem.getJSONObject("owner");
profileModel = new ProfileModel(
owner.optBoolean("is_private"),
false, // if you can see it then you def follow
owner.optBoolean("is_verified"),
owner.getString(Constants.EXTRAS_ID),
owner.getString(Constants.EXTRAS_USERNAME),
owner.optString("full_name"),
null,
null,
owner.getString("profile_pic_url"),
null,
0,
0,
0,
false,
false,
false,
false);
}
JSONObject tempJsonObject = feedItem.optJSONObject("edge_media_preview_comment");
final long commentsCount = tempJsonObject != null ? tempJsonObject.optLong("count") : 0;
tempJsonObject = feedItem.optJSONObject("edge_media_to_caption");
final JSONArray captions = tempJsonObject != null ? tempJsonObject.getJSONArray("edges") : null;
String captionText = null;
if (captions != null && captions.length() > 0) {
if ((tempJsonObject = captions.optJSONObject(0)) != null &&
(tempJsonObject = tempJsonObject.optJSONObject("node")) != null)
captionText = tempJsonObject.getString("text");
}
final JSONObject location = feedItem.optJSONObject("location");
// Log.d(TAG, "location: " + (location == null ? null : location.toString()));
String locationId = null;
String locationName = null;
if (location != null) {
locationName = location.optString("name");
if (location.has("id")) {
locationId = location.getString("id");
} else if (location.has("pk")) {
locationId = location.getString("pk");
}
// Log.d(TAG, "locationId: " + locationId);
}
final FeedModel feedModel = new FeedModel(
profileModel,
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
videoViews,
feedItem.getString(Constants.EXTRAS_ID),
resourceUrl,
displayUrl,
feedItem.getString(Constants.EXTRAS_SHORTCODE),
captionText,
commentsCount,
feedItem.optLong("taken_at_timestamp", -1),
feedItem.getBoolean("viewer_has_liked"),
feedItem.getBoolean("viewer_has_saved"),
feedItem.getJSONObject("edge_media_preview_like").getLong("count"),
locationName,
locationId);
final boolean isSlider = "GraphSidecar".equals(mediaType) && feedItem.has("edge_sidecar_to_children");
if (isSlider) {
final JSONObject sidecar = feedItem.optJSONObject("edge_sidecar_to_children");
if (sidecar != null) {
final JSONArray children = sidecar.optJSONArray("edges");
if (children != null) {
final ViewerPostModel[] sliderItems = new ViewerPostModel[children.length()];
for (int j = 0; j < sliderItems.length; ++j) {
final JSONObject node = children.optJSONObject(j).getJSONObject("node");
final boolean isChildVideo = node.optBoolean("is_video");
sliderItems[j] = new ViewerPostModel(
isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
node.getString(Constants.EXTRAS_ID),
isChildVideo ? node.getString("video_url") : ResponseBodyUtils.getHighQualityImage(node),
null,
null,
null,
node.optLong("video_view_count", -1),
-1,
false,
false,
feedItem.getJSONObject("edge_media_preview_like").getLong("count"),
locationName,
locationId);
sliderItems[j].setSliderDisplayUrl(node.getString("display_url"));
}
feedModel.setSliderItems(sliderItems);
}
}
}
feedModelsList.add(feedModel);
}
feedModelsList.trimToSize();
final FeedModel[] feedModels = feedModelsList.toArray(new FeedModel[0]);
final int length = feedModels.length;
if (length >= 1 && feedModels[length - 1] != null) {
feedModels[length - 1].setPageCursor(hasNextPage, endCursor);
}
result = feedModels;
}
urlConnection.disconnect();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_FEED_FETCHER, "doInBackground");
if (BuildConfig.DEBUG) {
Log.e(TAG, "", e);
}
}
return result;
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
}
@Override
protected void onPostExecute(final FeedModel[] postModels) {
if (fetchListener != null) fetchListener.onResult(postModels);
}
}

View File

@ -0,0 +1,54 @@
package awais.instagrabber.asyncs;
import java.util.List;
import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.webservices.FeedService;
import awais.instagrabber.webservices.ServiceCallback;
public class FeedPostFetchService implements PostFetcher.PostFetchService {
private static final String TAG = "FeedPostFetchService";
private final FeedService feedService;
private String nextCursor;
private boolean hasNextPage;
public FeedPostFetchService() {
feedService = FeedService.getInstance();
}
@Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
feedService.fetch(25, nextCursor, new ServiceCallback<PostsFetchResponse>() {
@Override
public void onSuccess(final PostsFetchResponse result) {
if (result == null) return;
nextCursor = result.getNextCursor();
hasNextPage = result.hasNextPage();
if (fetchListener != null) {
fetchListener.onResult(result.getFeedModels());
}
}
@Override
public void onFailure(final Throwable t) {
// Log.e(TAG, "onFailure: ", t);
if (fetchListener != null) {
fetchListener.onFailure(t);
}
}
});
}
@Override
public void reset() {
nextCursor = null;
}
@Override
public boolean hasNextPage() {
return hasNextPage;
}
}

View File

@ -0,0 +1,56 @@
package awais.instagrabber.asyncs;
import java.util.List;
import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.TagsService;
import awais.instagrabber.webservices.TagsService.TagPostsFetchResponse;
public class HashtagPostFetchService implements PostFetcher.PostFetchService {
private final TagsService tagsService;
private final HashtagModel hashtagModel;
private String nextMaxId;
private boolean moreAvailable;
public HashtagPostFetchService(final HashtagModel hashtagModel) {
this.hashtagModel = hashtagModel;
tagsService = TagsService.getInstance();
}
@Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
tagsService.fetchPosts(hashtagModel.getName().toLowerCase(), nextMaxId, new ServiceCallback<TagPostsFetchResponse>() {
@Override
public void onSuccess(final TagPostsFetchResponse result) {
if (result == null) return;
nextMaxId = result.getNextMaxId();
moreAvailable = result.isMoreAvailable();
if (fetchListener != null) {
fetchListener.onResult(result.getItems());
}
}
@Override
public void onFailure(final Throwable t) {
// Log.e(TAG, "onFailure: ", t);
if (fetchListener != null) {
fetchListener.onFailure(t);
}
}
});
}
@Override
public void reset() {
nextMaxId = null;
}
@Override
public boolean hasNextPage() {
return moreAvailable;
}
}

View File

@ -0,0 +1,56 @@
package awais.instagrabber.asyncs;
import java.util.List;
import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.LocationModel;
import awais.instagrabber.webservices.LocationService;
import awais.instagrabber.webservices.LocationService.LocationPostsFetchResponse;
import awais.instagrabber.webservices.ServiceCallback;
public class LocationPostFetchService implements PostFetcher.PostFetchService {
private final LocationService locationService;
private final LocationModel locationModel;
private String nextMaxId;
private boolean moreAvailable;
public LocationPostFetchService(final LocationModel locationModel) {
this.locationModel = locationModel;
locationService = LocationService.getInstance();
}
@Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
locationService.fetchPosts(locationModel.getId(), nextMaxId, new ServiceCallback<LocationPostsFetchResponse>() {
@Override
public void onSuccess(final LocationPostsFetchResponse result) {
if (result == null) return;
nextMaxId = result.getNextMaxId();
moreAvailable = result.isMoreAvailable();
if (fetchListener != null) {
fetchListener.onResult(result.getItems());
}
}
@Override
public void onFailure(final Throwable t) {
// Log.e(TAG, "onFailure: ", t);
if (fetchListener != null) {
fetchListener.onFailure(t);
}
}
});
}
@Override
public void reset() {
nextMaxId = null;
}
@Override
public boolean hasNextPage() {
return moreAvailable;
}
}

View File

@ -1,51 +1,48 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.NetworkUtils;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector;
public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]> {
public final class PostFetcher extends AsyncTask<Void, Void, FeedModel> {
private static final String TAG = "PostFetcher";
private final String shortCode;
private final FetchListener<ViewerPostModel[]> fetchListener;
private final FetchListener<FeedModel> fetchListener;
public PostFetcher(final String shortCode, final FetchListener<ViewerPostModel[]> fetchListener) {
public PostFetcher(final String shortCode, final FetchListener<FeedModel> fetchListener) {
this.shortCode = shortCode;
this.fetchListener = fetchListener;
}
@Override
protected ViewerPostModel[] doInBackground(final Void... voids) {
ViewerPostModel[] result = null;
protected FeedModel doInBackground(final Void... voids) {
CookieUtils.setupCookies(Utils.settingsHelper.getString(Constants.COOKIE)); // <- direct download
HttpURLConnection conn = null;
try {
final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/p/" + shortCode + "/?__a=1").openConnection();
conn = (HttpURLConnection) new URL("https://www.instagram.com/p/" + shortCode + "/?__a=1").openConnection();
conn.setUseCaches(false);
conn.connect();
@ -53,7 +50,6 @@ public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
final JSONObject media = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("graphql")
.getJSONObject("shortcode_media");
ProfileModel profileModel = null;
if (media.has("owner")) {
final JSONObject owner = media.getJSONObject("owner");
@ -77,19 +73,6 @@ public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
owner.optBoolean("requested_by_viewer")
);
}
final String username = profileModel == null ? "" : profileModel.getUsername();
// to check if file exists
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" +
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/" + username) : ""));
File customDir = null;
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH +
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER)
? ("/" + username)
: ""));
if (!TextUtils.isEmpty(customPath)) customDir = new File(customPath);
}
final long timestamp = media.getLong("taken_at_timestamp");
final boolean isVideo = media.has("is_video") && media.optBoolean("is_video");
@ -111,75 +94,67 @@ public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
JSONObject commentObject = media.optJSONObject("edge_media_to_parent_comment");
final long commentsCount = commentObject != null ? commentObject.optLong("count") : 0;
String endCursor = null;
if (commentObject != null && (commentObject = commentObject.optJSONObject("page_info")) != null) {
endCursor = commentObject.optString("end_cursor");
}
if (mediaItemType != MediaItemType.MEDIA_TYPE_SLIDER) {
final ViewerPostModel postModel = new ViewerPostModel(
mediaItemType,
media.getString(Constants.EXTRAS_ID),
isVideo ? media.getString("video_url") : ResponseBodyUtils.getHighQualityImage(media),
shortCode,
TextUtils.isEmpty(postCaption) ? null : postCaption,
profileModel,
isVideo && media.has("video_view_count") ? media.getLong("video_view_count") : -1,
timestamp, media.getBoolean("viewer_has_liked"), media.getBoolean("viewer_has_saved"),
media.getJSONObject("edge_media_preview_like").getLong("count"),
media.isNull("location") ? null : media.getJSONObject("location").optString("name"),
media.isNull("location") ? null :
(media.getJSONObject("location").optString("id") + "/" +
media.getJSONObject("location").optString("slug")));
postModel.setCommentsCount(commentsCount);
DownloadUtils.checkExistence(downloadDir, customDir, false, postModel);
result = new ViewerPostModel[]{postModel};
} else {
final FeedModel.Builder feedModelBuilder = new FeedModel.Builder()
.setItemType(mediaItemType)
.setPostId(media.getString(Constants.EXTRAS_ID))
.setDisplayUrl(isVideo ? media.getString("video_url")
: ResponseBodyUtils.getHighQualityImage(media))
.setThumbnailUrl(media.getString("display_url"))
.setImageHeight(media.getJSONObject("dimensions").getInt("height"))
.setImageWidth(media.getJSONObject("dimensions").getInt("width"))
.setShortCode(shortCode)
.setPostCaption(TextUtils.isEmpty(postCaption) ? null : postCaption)
.setProfileModel(profileModel)
.setViewCount(isVideo && media.has("video_view_count")
? media.getLong("video_view_count")
: -1)
.setTimestamp(timestamp)
.setLiked(media.getBoolean("viewer_has_liked"))
.setBookmarked(media.getBoolean("viewer_has_saved"))
.setLikesCount(media.getJSONObject("edge_media_preview_like")
.getLong("count"))
.setLocationName(media.isNull("location")
? null
: media.getJSONObject("location").optString("name"))
.setLocationId(media.isNull("location")
? null
: media.getJSONObject("location").optString("id"))
.setCommentsCount(commentsCount);
if (isSlider) {
final JSONArray children = media.getJSONObject("edge_sidecar_to_children").getJSONArray("edges");
final ViewerPostModel[] postModels = new ViewerPostModel[children.length()];
for (int i = 0; i < postModels.length; ++i) {
final JSONObject node = children.getJSONObject(i).getJSONObject("node");
final boolean isChildVideo = node.getBoolean("is_video");
postModels[i] = new ViewerPostModel(
isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
media.getString(Constants.EXTRAS_ID),
isChildVideo ? node.getString("video_url") : ResponseBodyUtils.getHighQualityImage(node),
node.getString(Constants.EXTRAS_SHORTCODE),
postCaption,
profileModel,
isChildVideo && node.has("video_view_count") ? node.getLong("video_view_count") : -1,
timestamp, media.getBoolean("viewer_has_liked"), media.getBoolean("viewer_has_saved"),
media.getJSONObject("edge_media_preview_like").getLong("count"),
media.isNull("location") ? null : media.getJSONObject("location").optString("name"),
media.isNull("location") ? null :
(media.getJSONObject("location").optString("id") + "/" +
media.getJSONObject("location").optString("slug")));
postModels[i].setSliderDisplayUrl(node.getString("display_url"));
DownloadUtils.checkExistence(downloadDir, customDir, true, postModels[i]);
final List<PostChild> postModels = new ArrayList<>();
for (int i = 0; i < children.length(); ++i) {
final JSONObject childNode = children.getJSONObject(i).getJSONObject("node");
final boolean isChildVideo = childNode.getBoolean("is_video");
postModels.add(new PostChild.Builder()
.setItemType(isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO
: MediaItemType.MEDIA_TYPE_IMAGE)
.setDisplayUrl(isChildVideo ? childNode.getString("video_url")
: childNode.getString("display_url"))
.setShortCode(media.getString(Constants.EXTRAS_SHORTCODE))
.setVideoViews(isChildVideo && childNode.has("video_view_count")
? childNode.getLong("video_view_count")
: -1)
.setThumbnailUrl(childNode.getString("display_url"))
.setHeight(childNode.getJSONObject("dimensions").getInt("height"))
.setWidth(childNode.getJSONObject("dimensions").getInt("width"))
.build());
}
postModels[0].setCommentsCount(commentsCount);
result = postModels;
feedModelBuilder.setSliderItems(postModels);
}
return feedModelBuilder.build();
}
conn.disconnect();
} catch (Exception e) {
if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.ASYNC_POST_FETCHER, "doInBackground");
}
Log.e(TAG, "Error fetching post", e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
return result;
return null;
}
@Override
@ -188,7 +163,7 @@ public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
}
@Override
protected void onPostExecute(final ViewerPostModel[] postModels) {
if (fetchListener != null) fetchListener.onResult(postModels);
protected void onPostExecute(final FeedModel feedModel) {
if (fetchListener != null) fetchListener.onResult(feedModel);
}
}

View File

@ -1,182 +0,0 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.NetworkUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector;
public final class PostsFetcher extends AsyncTask<Void, Void, List<PostModel>> {
private static final String TAG = "PostsFetcher";
private final PostItemType type;
private final String endCursor;
private final String id;
private final FetchListener<List<PostModel>> fetchListener;
private String username = null;
public PostsFetcher(final String id,
final PostItemType type,
final String endCursor,
final FetchListener<List<PostModel>> fetchListener) {
this.id = id;
this.type = type;
this.endCursor = endCursor == null ? "" : endCursor;
this.fetchListener = fetchListener;
}
public PostsFetcher setUsername(final String username) {
this.username = username;
return this;
}
@Override
protected List<PostModel> doInBackground(final Void... voids) {
// final boolean isHashTag = id.charAt(0) == '#';
// final boolean isSaved = id.charAt(0) == '$';
// final boolean isTagged = id.charAt(0) == '%';
// final boolean isLocation = id.contains("/");
final String url;
switch (type) {
case HASHTAG:
url = "https://www.instagram.com/graphql/query/?query_hash=9b498c08113f1e09617a1703c22b2f32&variables=" +
"{\"tag_name\":\"" + id.toLowerCase() + "\",\"first\":150,\"after\":\"" + endCursor + "\"}";
break;
case LOCATION:
url = "https://www.instagram.com/graphql/query/?query_hash=36bd0f2bf5911908de389b8ceaa3be6d&variables=" +
"{\"id\":\"" + id + "\",\"first\":150,\"after\":\"" + endCursor + "\"}";
break;
case SAVED:
url = "https://www.instagram.com/graphql/query/?query_hash=8c86fed24fa03a8a2eea2a70a80c7b6b&variables=" +
"{\"id\":\"" + id + "\",\"first\":150,\"after\":\"" + endCursor + "\"}";
break;
case TAGGED:
url = "https://www.instagram.com/graphql/query/?query_hash=31fe64d9463cbbe58319dced405c6206&variables=" +
"{\"id\":\"" + id + "\",\"first\":150,\"after\":\"" + endCursor + "\"}";
break;
default:
url = "https://www.instagram.com/graphql/query/?query_hash=18a7b935ab438c4514b1f742d8fa07a7&variables=" +
"{\"id\":\"" + id + "\",\"first\":150,\"after\":\"" + endCursor + "\"}";
}
List<PostModel> result = new ArrayList<>();
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
// to check if file exists
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" +
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/" + username) : ""));
File customDir = null;
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH +
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER)
? ("/" + username)
: ""));
if (!TextUtils.isEmpty(customPath)) customDir = new File(customPath);
}
final boolean isHashtag = type == PostItemType.HASHTAG;
final boolean isLocation = type == PostItemType.LOCATION;
final boolean isSaved = type == PostItemType.SAVED;
final boolean isTagged = type == PostItemType.TAGGED;
final JSONObject mediaPosts = new JSONObject(NetworkUtils.readFromConnection(conn))
.getJSONObject("data")
.getJSONObject(isHashtag
? Constants.EXTRAS_HASHTAG
: (isLocation ? Constants.EXTRAS_LOCATION
: Constants.EXTRAS_USER))
.getJSONObject(isHashtag ? "edge_hashtag_to_media" :
isLocation ? "edge_location_to_media" : isSaved ? "edge_saved_media"
: isTagged ? "edge_user_to_photos_of_you"
: "edge_owner_to_timeline_media");
final String endCursor;
final boolean hasNextPage;
final JSONObject pageInfo = mediaPosts.getJSONObject("page_info");
if (pageInfo.has("has_next_page")) {
hasNextPage = pageInfo.getBoolean("has_next_page");
endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null;
} else {
hasNextPage = false;
endCursor = null;
}
final JSONArray edges = mediaPosts.getJSONArray("edges");
for (int i = 0; i < edges.length(); ++i) {
final JSONObject mediaNode = edges.getJSONObject(i).getJSONObject("node");
final JSONArray captions = mediaNode.getJSONObject("edge_media_to_caption").getJSONArray("edges");
final boolean isSlider = mediaNode.has("__typename") && mediaNode.getString("__typename").equals("GraphSidecar");
final boolean isVideo = mediaNode.getBoolean("is_video");
final MediaItemType itemType;
if (isSlider) itemType = MediaItemType.MEDIA_TYPE_SLIDER;
else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO;
else itemType = MediaItemType.MEDIA_TYPE_IMAGE;
final PostModel model = new PostModel(
itemType,
mediaNode.getString(Constants.EXTRAS_ID),
mediaNode.getString("display_url"),
mediaNode.getString("thumbnail_src"),
mediaNode.getString(Constants.EXTRAS_SHORTCODE),
captions.length() > 0 ? captions.getJSONObject(0)
.getJSONObject("node")
.getString("text")
: null,
mediaNode.getLong("taken_at_timestamp"),
mediaNode.optBoolean("viewer_has_liked"),
mediaNode.optBoolean("viewer_has_saved"),
mediaNode.isNull("edge_liked_by") ? 0 : mediaNode.getJSONObject("edge_liked_by").getLong("count")
);
result.add(model);
DownloadUtils.checkExistence(downloadDir, customDir, isSlider, model);
}
if (!result.isEmpty() && result.get(result.size() - 1) != null)
result.get(result.size() - 1).setPageCursor(hasNextPage, endCursor);
}
conn.disconnect();
} catch (Exception e) {
if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground");
}
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error fetching posts", e);
}
}
return result;
}
@Override
protected void onPostExecute(final List<PostModel> postModels) {
if (fetchListener != null) fetchListener.onResult(postModels);
}
}

View File

@ -0,0 +1,57 @@
package awais.instagrabber.asyncs;
import java.util.List;
import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.webservices.ProfileService;
import awais.instagrabber.webservices.ServiceCallback;
public class ProfilePostFetchService implements PostFetcher.PostFetchService {
private static final String TAG = "ProfilePostFetchService";
private final ProfileService profileService;
private final ProfileModel profileModel;
private String nextCursor;
private boolean hasNextPage;
public ProfilePostFetchService(final ProfileModel profileModel) {
this.profileModel = profileModel;
profileService = ProfileService.getInstance();
}
@Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
profileService.fetchPosts(profileModel, 30, nextCursor, new ServiceCallback<PostsFetchResponse>() {
@Override
public void onSuccess(final PostsFetchResponse result) {
if (result == null) return;
nextCursor = result.getNextCursor();
hasNextPage = result.hasNextPage();
if (fetchListener != null) {
fetchListener.onResult(result.getFeedModels());
}
}
@Override
public void onFailure(final Throwable t) {
// Log.e(TAG, "onFailure: ", t);
if (fetchListener != null) {
fetchListener.onFailure(t);
}
}
});
}
@Override
public void reset() {
nextCursor = null;
}
@Override
public boolean hasNextPage() {
return hasNextPage;
}
}

View File

@ -0,0 +1,71 @@
package awais.instagrabber.asyncs;
import java.util.List;
import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.webservices.ProfileService;
import awais.instagrabber.webservices.ProfileService.SavedPostsFetchResponse;
import awais.instagrabber.webservices.ServiceCallback;
public class SavedPostFetchService implements PostFetcher.PostFetchService {
private final ProfileService profileService;
private final String profileId;
private final PostItemType type;
private String nextMaxId;
private boolean moreAvailable;
public SavedPostFetchService(final String profileId, final PostItemType type) {
this.profileId = profileId;
this.type = type;
profileService = ProfileService.getInstance();
}
@Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
final ServiceCallback<SavedPostsFetchResponse> callback = new ServiceCallback<SavedPostsFetchResponse>() {
@Override
public void onSuccess(final SavedPostsFetchResponse result) {
if (result == null) return;
nextMaxId = result.getNextMaxId();
moreAvailable = result.isMoreAvailable();
if (fetchListener != null) {
fetchListener.onResult(result.getItems());
}
}
@Override
public void onFailure(final Throwable t) {
// Log.e(TAG, "onFailure: ", t);
if (fetchListener != null) {
fetchListener.onFailure(t);
}
}
};
switch (type) {
case LIKED:
profileService.fetchLiked(nextMaxId, callback);
break;
case TAGGED:
profileService.fetchTagged(profileId, nextMaxId, callback);
break;
case SAVED:
default:
profileService.fetchSaved(nextMaxId, callback);
break;
}
}
@Override
public void reset() {
nextMaxId = null;
}
@Override
public boolean hasNextPage() {
return moreAvailable;
}
}

View File

@ -1,139 +0,0 @@
package awais.instagrabber.asyncs.i;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.NetworkUtils;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector;
public final class iLikedFetcher extends AsyncTask<Void, Void, List<PostModel>> {
private static final String TAG = "iLikedFetcher";
private final String endCursor;
private final FetchListener<List<PostModel>> fetchListener;
public iLikedFetcher(final FetchListener<List<PostModel>> fetchListener) {
this.endCursor = "";
this.fetchListener = fetchListener;
}
public iLikedFetcher(final String endCursor, final FetchListener<List<PostModel>> fetchListener) {
this.endCursor = endCursor == null ? "" : endCursor;
this.fetchListener = fetchListener;
}
@Override
protected List<PostModel> doInBackground(final Void... voids) {
final String url = "https://i.instagram.com/api/v1/feed/liked/?max_id=" + endCursor;
List<PostModel> result = new ArrayList<>();
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false);
conn.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONObject body = new JSONObject(NetworkUtils.readFromConnection(conn));
final String endCursor;
final boolean hasNextPage;
if (body.has("more_available")) {
hasNextPage = body.optBoolean("more_available");
endCursor = hasNextPage ? body.optString("next_max_id") : null;
} else {
hasNextPage = false;
endCursor = null;
}
final JSONArray edges = body.getJSONArray("items");
for (int i = 0; i < edges.length(); ++i) {
final JSONObject mediaNode = edges.getJSONObject(i);
final boolean isSlider = mediaNode.has("carousel_media_count");
final boolean isVideo = mediaNode.has("video_duration");
final MediaItemType itemType;
if (isSlider) itemType = MediaItemType.MEDIA_TYPE_SLIDER;
else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO;
else itemType = MediaItemType.MEDIA_TYPE_IMAGE;
final PostModel model = new PostModel(
itemType,
mediaNode.getString(Constants.EXTRAS_ID),
isSlider ? ResponseBodyUtils.getHighQualityImage(mediaNode.getJSONArray("carousel_media")
.getJSONObject(0))
: ResponseBodyUtils.getHighQualityImage(mediaNode),
isSlider ? ResponseBodyUtils.getLowQualityImage(mediaNode.getJSONArray("carousel_media")
.getJSONObject(0))
: ResponseBodyUtils.getLowQualityImage(mediaNode),
mediaNode.getString("code"),
mediaNode.isNull("caption") ? null : mediaNode.getJSONObject("caption").optString("text"),
mediaNode.getLong("taken_at"),
true,
mediaNode.optBoolean("has_viewer_saved"),
mediaNode.getLong("like_count"));
result.add(model);
String username = mediaNode.getJSONObject("user").getString("username");
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" +
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/" + username) : ""));
File customDir = null;
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH +
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER)
? ("/" + username)
: ""));
if (!TextUtils.isEmpty(customPath)) customDir = new File(customPath);
}
DownloadUtils.checkExistence(downloadDir, customDir, isSlider, model);
}
final int length = result.size();
if (length >= 1 && result.get(length - 1) != null) {
result.get(length - 1).setPageCursor(hasNextPage, endCursor);
}
}
conn.disconnect();
} catch (Exception e) {
if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground");
}
if (BuildConfig.DEBUG) {
Log.e(TAG, "", e);
}
}
return result;
}
@Override
protected void onPostExecute(final List<PostModel> postModels) {
if (fetchListener != null) fetchListener.onResult(postModels);
}
}

View File

@ -1,207 +0,0 @@
package awais.instagrabber.asyncs.i;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.NetworkUtils;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector;
public final class iPostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]> {
private static final String TAG = "iPostFetcher";
private final String id;
private final FetchListener<ViewerPostModel[]> fetchListener;
public iPostFetcher(final String id, final FetchListener<ViewerPostModel[]> fetchListener) {
this.id = id;
this.fetchListener = fetchListener;
}
@Override
protected ViewerPostModel[] doInBackground(final Void... voids) {
ViewerPostModel[] result = null;
try {
final HttpURLConnection conn = (HttpURLConnection) new URL("https://i.instagram.com/api/v1/media/" + id + "/info").openConnection();
conn.setUseCaches(false);
conn.setRequestProperty("User-Agent", Constants.USER_AGENT);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONObject media = new JSONObject(NetworkUtils.readFromConnection(conn))
.getJSONArray("items")
.getJSONObject(0);
ProfileModel profileModel = null;
if (media.has("user")) {
final JSONObject user = media.getJSONObject("user");
final JSONObject friendshipStatus = user.optJSONObject("friendship_status");
boolean following = false;
boolean isRestricted = false;
boolean outgoingRequest = false;
if (friendshipStatus != null) {
following = friendshipStatus.optBoolean("following");
isRestricted = friendshipStatus.optBoolean("is_restricted");
outgoingRequest = friendshipStatus.optBoolean("outgoing_request");
}
profileModel = new ProfileModel(
user.optBoolean("is_private"),
user.optBoolean("is_private"),
user.optBoolean("is_verified"),
user.optString("pk"),
user.getString(Constants.EXTRAS_USERNAME),
user.optString("fullname"),
null,
null,
user.getString("profile_pic_url"),
null,
-1,
-1,
-1,
following,
isRestricted,
false,
outgoingRequest
);
}
if (profileModel == null) {
return new ViewerPostModel[]{};
}
// to check if file exists
final boolean shouldDownloadToUserFolder = Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER);
final File downloadDir = new File(
Environment.getExternalStorageDirectory(),
"Download" + (shouldDownloadToUserFolder ? "/" + profileModel.getUsername() : ""));
File customDir = null;
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH)
+ (shouldDownloadToUserFolder ? "/" + profileModel.getUsername() : "");
if (!TextUtils.isEmpty(customPath)) customDir = new File(customPath);
}
final long timestamp = media.getLong("taken_at");
final boolean isVideo = media.has("has_audio") && media.optBoolean("has_audio");
final boolean isSlider = !media.isNull("carousel_media_count");
final MediaItemType mediaItemType;
if (isSlider) mediaItemType = MediaItemType.MEDIA_TYPE_SLIDER;
else if (isVideo) mediaItemType = MediaItemType.MEDIA_TYPE_VIDEO;
else mediaItemType = MediaItemType.MEDIA_TYPE_IMAGE;
final String postCaption;
final JSONObject mediaToCaption = media.optJSONObject("caption");
if (mediaToCaption == null) postCaption = null;
else postCaption = mediaToCaption.optString("text");
final long commentsCount = media.optLong("comment_count");
final JSONObject location = media.optJSONObject("location");
String locationId = null;
String locationName = null;
if (location != null) {
locationName = location.optString("name");
if (location.has("id")) {
locationId = location.getString("id");
} else if (location.has("pk")) {
locationId = location.getString("pk");
}
}
// final String locationString = location.optString("id") + "/" + location.optString("slug");
if (mediaItemType != MediaItemType.MEDIA_TYPE_SLIDER) {
final ViewerPostModel postModel = new ViewerPostModel(
mediaItemType,
media.getString(Constants.EXTRAS_ID),
isVideo ? ResponseBodyUtils.getHighQualityPost(media.optJSONArray("video_versions"), true, true, false)
: ResponseBodyUtils.getHighQualityImage(media),
media.getString("code"),
TextUtils.isEmpty(postCaption) ? null : postCaption,
profileModel,
isVideo && media.has("view_count") ? media.getLong("view_count") : -1,
timestamp, media.optBoolean("has_liked"),
media.optBoolean("has_viewer_saved"),
media.getLong("like_count"),
locationName,
locationId);
postModel.setCommentsCount(commentsCount);
DownloadUtils.checkExistence(downloadDir, customDir, false, postModel);
result = new ViewerPostModel[]{postModel};
} else {
final JSONArray children = media.getJSONArray("carousel_media");
final ViewerPostModel[] postModels = new ViewerPostModel[children.length()];
for (int i = 0; i < postModels.length; ++i) {
final JSONObject node = children.getJSONObject(i);
final boolean isChildVideo = node.has("video_duration");
postModels[i] = new ViewerPostModel(
isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO
: MediaItemType.MEDIA_TYPE_IMAGE,
media.getString(Constants.EXTRAS_ID),
isChildVideo ? ResponseBodyUtils.getHighQualityPost(node.optJSONArray("video_versions"), true, true, false)
: ResponseBodyUtils.getHighQualityImage(node),
media.getString("code"),
postCaption,
profileModel,
-1,
timestamp, media.optBoolean("has_liked"),
media.optBoolean("has_viewer_saved"),
media.getLong("like_count"),
locationName,
locationId);
postModels[i].setSliderDisplayUrl(ResponseBodyUtils.getHighQualityImage(node));
DownloadUtils.checkExistence(downloadDir, customDir, true, postModels[i]);
}
postModels[0].setCommentsCount(commentsCount);
result = postModels;
}
}
conn.disconnect();
} catch (Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_POST_FETCHER, "doInBackground (i)");
if (BuildConfig.DEBUG) {
Log.e(TAG, "", e);
}
}
return result;
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
}
@Override
protected void onPostExecute(final ViewerPostModel[] postModels) {
if (fetchListener != null) fetchListener.onResult(postModels);
}
}

View File

@ -1,69 +0,0 @@
package awais.instagrabber.asyncs.i;
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
import java.net.HttpURLConnection;
import java.net.URL;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.DiscoverTopicModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.LocaleUtils;
import awais.instagrabber.utils.NetworkUtils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
public final class iTopicFetcher extends AsyncTask<Void, Void, DiscoverTopicModel> {
private final FetchListener<DiscoverTopicModel> fetchListener;
public iTopicFetcher(final FetchListener<DiscoverTopicModel> fetchListener) {
this.fetchListener = fetchListener;
}
@Override
protected DiscoverTopicModel doInBackground(final Void... voids) {
final String url = "https://i.instagram.com/api/v1/discover/topical_explore/";
DiscoverTopicModel result = null;
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false);
conn.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
conn.setRequestProperty("Accept-Language", LocaleUtils.getCurrentLocale().getLanguage() + ",en-US;q=0.8");
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONObject body = new JSONObject(NetworkUtils.readFromConnection(conn));
final JSONArray edges = body.getJSONArray("clusters");
String[] names = new String[edges.length()], ids = new String[edges.length()];
for (int i = 0; i < names.length; ++i) {
final JSONObject mediaNode = edges.getJSONObject(i);
ids[i] = mediaNode.getString("id");
names[i] = mediaNode.getString("title");
}
result = new DiscoverTopicModel(ids, names, body.getString("rank_token"));
}
conn.disconnect();
} catch (Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_DISCOVER_TOPICS_FETCHER, "doInBackground");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
return result;
}
@Override
protected void onPostExecute(final DiscoverTopicModel discoverTopicModel) {
if (fetchListener != null) fetchListener.onResult(discoverTopicModel);
}
}

View File

@ -2,10 +2,7 @@ package awais.instagrabber.customviews;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
@ -17,36 +14,27 @@ import com.facebook.drawee.generic.GenericDraweeHierarchyInflater;
import com.facebook.drawee.generic.RoundingParams;
import com.facebook.drawee.view.SimpleDraweeView;
public final class CircularImageView extends SimpleDraweeView {
private final int borderSize = 8;
private int color = Color.TRANSPARENT;
private final Paint paint = new Paint();
private final Paint paintBorder = new Paint();
private BitmapShader shader;
private Bitmap bitmap;
import awais.instagrabber.R;
public class CircularImageView extends SimpleDraweeView {
public CircularImageView(Context context, GenericDraweeHierarchy hierarchy) {
super(context);
setHierarchy(hierarchy);
setup();
}
public CircularImageView(final Context context) {
super(context);
inflateHierarchy(context, null);
setup();
}
public CircularImageView(final Context context, final AttributeSet attrs) {
super(context, attrs);
inflateHierarchy(context, attrs);
setup();
}
public CircularImageView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
inflateHierarchy(context, attrs);
setup();
}
protected void inflateHierarchy(Context context, @Nullable AttributeSet attrs) {
@ -58,77 +46,12 @@ public final class CircularImageView extends SimpleDraweeView {
GenericDraweeHierarchyInflater.updateBuilder(builder, context, attrs);
setAspectRatio(builder.getDesiredAspectRatio());
setHierarchy(builder.build());
setBackgroundResource(R.drawable.shape_oval_light);
}
private void setup() {
// paint.setAntiAlias(true);
// paintBorder.setColor(color);
// paintBorder.setAntiAlias(true);
//
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// setOutlineProvider(new ViewOutlineProvider() {
// private int viewHeight;
// private int viewWidth;
//
// @Override
// public void getOutline(final View view, final Outline outline) {
// if (viewHeight == 0) viewHeight = getHeight();
// if (viewWidth == 0) viewWidth = getWidth();
// outline.setRoundRect(borderSize, borderSize, viewWidth - borderSize, viewHeight - borderSize, viewHeight >> 1);
// }
// });
// }
// final GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources())
// .setRoundingParams(RoundingParams.)
// .build();
// setHierarchy(hierarchy);
// invalidate();
}
// @Override
// public void onDraw(final Canvas canvas) {
// final BitmapDrawable bitmapDrawable = (BitmapDrawable) getDrawable();
// if (bitmapDrawable != null) {
// final Bitmap prevBitmap = bitmap;
// bitmap = bitmapDrawable.getBitmap();
// final boolean changed = prevBitmap != bitmap;
// if (bitmap != null) {
// final int width = getWidth();
// final int height = getHeight();
//
// if (shader == null || changed) {
// shader = new BitmapShader(Bitmap.createScaledBitmap(bitmap, width, height, true), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
// paint.setShader(shader);
// }
//
// if (changed) color = 0;
// paintBorder.setColor(color);
//
// final int circleCenter = (width - borderSize) / 2;
// final int position = circleCenter + (borderSize / 2);
// canvas.drawCircle(position, position, position - 4.0f, paintBorder);
// canvas.drawCircle(position, position, circleCenter - 4.0f, paint);
// }
// }
// }
//
// @Override
// protected void onAttachedToWindow() {
// super.onAttachedToWindow();
// setLayerType(LAYER_TYPE_HARDWARE, null);
// }
//
// @Override
// protected void onDetachedFromWindow() {
// setLayerType(LAYER_TYPE_NONE, null);
// super.onDetachedFromWindow();
// }
public void setStoriesBorder() {
this.color = Color.GREEN;
// invalidate();
// final RoundingParams roundingParams = RoundingParams.fromCornersRadius(5f);
//
// private final int borderSize = 8;
final int color = Color.GREEN;
RoundingParams roundingParams = getHierarchy().getRoundingParams();
if (roundingParams == null) {
roundingParams = RoundingParams.asCircle().setRoundingMethod(RoundingParams.RoundingMethod.BITMAP_ONLY);

View File

@ -0,0 +1,330 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import androidx.transition.ChangeBounds;
import androidx.transition.Transition;
import androidx.transition.TransitionManager;
import androidx.work.Data;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
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.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.FeedViewModel;
import awais.instagrabber.workers.DownloadWorker;
public class PostsRecyclerView extends RecyclerView {
private static final String TAG = "PostsRecyclerView";
private StaggeredGridLayoutManager layoutManager;
private PostsLayoutPreferences layoutPreferences;
private PostFetcher.PostFetchService postFetchService;
private Transition transition;
private PostFetcher postFetcher;
private ViewModelStoreOwner viewModelStoreOwner;
private FeedAdapterV2 feedAdapter;
private LifecycleOwner lifeCycleOwner;
private FeedViewModel feedViewModel;
private boolean initCalled = false;
private GridSpacingItemDecoration gridSpacingItemDecoration;
private RecyclerLazyLoaderAtBottom lazyLoader;
private FeedAdapterV2.FeedItemCallback feedItemCallback;
private boolean shouldScrollToTop;
private FeedAdapterV2.SelectionModeCallback selectionModeCallback;
private final List<FetchStatusChangeListener> fetchStatusChangeListeners = new ArrayList<>();
private final FetchListener<List<FeedModel>> fetchListener = new FetchListener<List<FeedModel>>() {
@Override
public void onResult(final List<FeedModel> result) {
final int currentPage = lazyLoader.getCurrentPage();
if (currentPage == 0) {
feedViewModel.getList().postValue(result);
shouldScrollToTop = true;
dispatchFetchStatus();
return;
}
final List<FeedModel> models = feedViewModel.getList().getValue();
final List<FeedModel> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models);
modelsCopy.addAll(result);
feedViewModel.getList().postValue(modelsCopy);
dispatchFetchStatus();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "onFailure: ", t);
}
};
private final RecyclerView.SmoothScroller smoothScroller = new LinearSmoothScroller(getContext()) {
@Override
protected int getVerticalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_START;
}
};
public PostsRecyclerView(@NonNull final Context context) {
super(context);
}
public PostsRecyclerView(@NonNull final Context context, @Nullable final AttributeSet attrs) {
super(context, attrs);
}
public PostsRecyclerView(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public PostsRecyclerView setViewModelStoreOwner(final ViewModelStoreOwner owner) {
if (initCalled) {
throw new IllegalArgumentException("init already called!");
}
this.viewModelStoreOwner = owner;
return this;
}
public PostsRecyclerView setLifeCycleOwner(final LifecycleOwner lifeCycleOwner) {
if (initCalled) {
throw new IllegalArgumentException("init already called!");
}
this.lifeCycleOwner = lifeCycleOwner;
return this;
}
public PostsRecyclerView setPostFetchService(final PostFetcher.PostFetchService postFetchService) {
if (initCalled) {
throw new IllegalArgumentException("init already called!");
}
this.postFetchService = postFetchService;
return this;
}
public PostsRecyclerView setFeedItemCallback(@NonNull final FeedAdapterV2.FeedItemCallback feedItemCallback) {
this.feedItemCallback = feedItemCallback;
return this;
}
public PostsRecyclerView setSelectionModeCallback(@NonNull final FeedAdapterV2.SelectionModeCallback selectionModeCallback) {
this.selectionModeCallback = selectionModeCallback;
return this;
}
public PostsRecyclerView setLayoutPreferences(final PostsLayoutPreferences layoutPreferences) {
this.layoutPreferences = layoutPreferences;
if (initCalled) {
if (layoutPreferences == null) return this;
feedAdapter.setLayoutPreferences(layoutPreferences);
updateLayout();
}
return this;
}
public void init() {
initCalled = true;
if (viewModelStoreOwner == null) {
throw new IllegalArgumentException("ViewModelStoreOwner cannot be null");
} else if (lifeCycleOwner == null) {
throw new IllegalArgumentException("LifecycleOwner cannot be null");
} else if (postFetchService == null) {
throw new IllegalArgumentException("PostFetchService cannot be null");
}
if (layoutPreferences == null) {
layoutPreferences = PostsLayoutPreferences.builder().build();
// Utils.settingsHelper.putString(Constants.PREF_POSTS_LAYOUT, layoutPreferences.getJson());
}
gridSpacingItemDecoration = new GridSpacingItemDecoration(Utils.convertDpToPx(2));
initTransition();
initAdapter();
initLayoutManager();
initSelf();
initDownloadWorkerListener();
}
private void initTransition() {
transition = new ChangeBounds();
transition.setDuration(300);
}
private void initLayoutManager() {
layoutManager = new StaggeredGridLayoutManager(layoutPreferences.getColCount(), StaggeredGridLayoutManager.VERTICAL);
setLayoutManager(layoutManager);
}
private void initAdapter() {
feedAdapter = new FeedAdapterV2(layoutPreferences, feedItemCallback, selectionModeCallback);
feedAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
setAdapter(feedAdapter);
}
private void initSelf() {
feedViewModel = new ViewModelProvider(viewModelStoreOwner).get(FeedViewModel.class);
feedViewModel.getList().observe(lifeCycleOwner, list -> feedAdapter.submitList(list, () -> {
if (!shouldScrollToTop) return;
smoothScrollToPosition(0);
shouldScrollToTop = false;
}));
postFetcher = new PostFetcher(postFetchService, fetchListener);
if (layoutPreferences.getHasGap()) {
addItemDecoration(gridSpacingItemDecoration);
}
setHasFixedSize(true);
setNestedScrollingEnabled(true);
lazyLoader = new RecyclerLazyLoaderAtBottom(layoutManager, (page) -> {
if (postFetcher.hasMore()) {
postFetcher.fetch();
dispatchFetchStatus();
}
});
addOnScrollListener(lazyLoader);
postFetcher.fetch();
dispatchFetchStatus();
}
private void initDownloadWorkerListener() {
WorkManager.getInstance(getContext())
.getWorkInfosByTagLiveData("download")
.observe(lifeCycleOwner, workInfoList -> {
for (final WorkInfo workInfo : workInfoList) {
if (workInfo == null) continue;
final Data progress = workInfo.getProgress();
final float progressPercent = progress.getFloat(DownloadWorker.PROGRESS, 0);
if (progressPercent != 100) continue;
final String url = progress.getString(DownloadWorker.URL);
final List<FeedModel> feedModels = feedViewModel.getList().getValue();
for (int i = 0; i < feedModels.size(); i++) {
final FeedModel feedModel = feedModels.get(i);
final List<String> displayUrls = getDisplayUrl(feedModel);
if (displayUrls.contains(url)) {
feedAdapter.notifyItemChanged(i);
break;
}
}
}
});
}
private List<String> getDisplayUrl(final FeedModel feedModel) {
List<String> urls = Collections.emptyList();
switch (feedModel.getItemType()) {
case MEDIA_TYPE_IMAGE:
case MEDIA_TYPE_VIDEO:
urls = Collections.singletonList(feedModel.getDisplayUrl());
break;
case MEDIA_TYPE_SLIDER:
final List<PostChild> sliderItems = feedModel.getSliderItems();
if (sliderItems != null) {
final ImmutableList.Builder<String> builder = ImmutableList.builder();
for (final PostChild child : sliderItems) {
builder.add(child.getDisplayUrl());
}
urls = builder.build();
}
break;
default:
}
return urls;
}
private void updateLayout() {
post(() -> {
TransitionManager.beginDelayedTransition(this, transition);
feedAdapter.notifyDataSetChanged();
if (!layoutPreferences.getHasGap()) {
removeItemDecoration(gridSpacingItemDecoration);
} else {
addItemDecoration(gridSpacingItemDecoration);
}
if (layoutPreferences.getType() == PostsLayoutPreferences.PostsLayoutType.LINEAR) {
if (layoutManager.getSpanCount() != 1) {
layoutManager.setSpanCount(1);
setAdapter(null);
setAdapter(feedAdapter);
}
} else {
boolean shouldRedraw = layoutManager.getSpanCount() == 1;
layoutManager.setSpanCount(layoutPreferences.getColCount());
if (shouldRedraw) {
setAdapter(null);
setAdapter(feedAdapter);
}
}
});
}
public void refresh() {
lazyLoader.resetState();
postFetcher.reset();
postFetcher.fetch();
dispatchFetchStatus();
}
public boolean isFetching() {
return postFetcher != null && postFetcher.isFetching();
}
public PostsRecyclerView addFetchStatusChangeListener(final FetchStatusChangeListener fetchStatusChangeListener) {
if (fetchStatusChangeListener == null) return this;
fetchStatusChangeListeners.add(fetchStatusChangeListener);
return this;
}
public void removeFetchStatusListener(final FetchStatusChangeListener fetchStatusChangeListener) {
if (fetchStatusChangeListener == null) return;
fetchStatusChangeListeners.remove(fetchStatusChangeListener);
}
private void dispatchFetchStatus() {
for (final FetchStatusChangeListener listener : fetchStatusChangeListeners) {
listener.onFetchStatusChange(isFetching());
}
}
public PostsLayoutPreferences getLayoutPreferences() {
return layoutPreferences;
}
public void endSelection() {
feedAdapter.endSelection();
}
public interface FetchStatusChangeListener {
void onFetchStatusChange(boolean fetching);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
lifeCycleOwner = null;
}
@Override
public void smoothScrollToPosition(final int position) {
smoothScroller.setTargetPosition(position);
layoutManager.startSmoothScroll(smoothScroller);
}
}

View File

@ -0,0 +1,141 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.ViewGroup;
import androidx.annotation.DimenRes;
import androidx.annotation.NonNull;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.RoundingParams;
import java.util.HashMap;
import java.util.Map;
import awais.instagrabber.R;
public final class ProfilePicView extends CircularImageView {
private static final String TAG = "ProfilePicView";
private Size size;
private int dimensionPixelSize;
public ProfilePicView(Context context, GenericDraweeHierarchy hierarchy) {
super(context);
setHierarchy(hierarchy);
size = Size.REGULAR;
updateLayout();
}
public ProfilePicView(final Context context) {
super(context);
size = Size.REGULAR;
updateLayout();
}
public ProfilePicView(final Context context, final AttributeSet attrs) {
super(context, attrs);
parseAttrs(context, attrs);
updateLayout();
}
public ProfilePicView(final Context context,
final AttributeSet attrs,
final int defStyleAttr) {
super(context, attrs, defStyleAttr);
parseAttrs(context, attrs);
}
private void parseAttrs(final Context context, final AttributeSet attrs) {
final TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.ProfilePicView,
0,
0);
try {
final int sizeValue = a.getInt(R.styleable.ProfilePicView_size, Size.REGULAR.getValue());
size = Size.valueOf(sizeValue);
} finally {
a.recycle();
}
}
private void updateLayout() {
@DimenRes final int dimenRes;
switch (size) {
case SMALL:
dimenRes = R.dimen.profile_pic_size_small;
break;
case TINY:
dimenRes = R.dimen.profile_pic_size_tiny;
break;
case LARGE:
dimenRes = R.dimen.profile_pic_size_large;
break;
default:
case REGULAR:
dimenRes = R.dimen.profile_pic_size_regular;
break;
}
ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams == null) {
layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
dimensionPixelSize = getResources().getDimensionPixelSize(dimenRes);
layoutParams.width = dimensionPixelSize;
layoutParams.height = dimensionPixelSize;
// invalidate();
// requestLayout();
}
public void setStoriesBorder() {
// private final int borderSize = 8;
final int color = Color.GREEN;
RoundingParams roundingParams = getHierarchy().getRoundingParams();
if (roundingParams == null) {
roundingParams = RoundingParams.asCircle().setRoundingMethod(RoundingParams.RoundingMethod.BITMAP_ONLY);
}
roundingParams.setBorder(color, 5.0f);
getHierarchy().setRoundingParams(roundingParams);
}
public enum Size {
TINY(0),
SMALL(1),
REGULAR(2),
LARGE(3);
private final int value;
private static Map<Integer, Size> map = new HashMap<>();
static {
for (Size size : Size.values()) {
map.put(size.value, size);
}
}
Size(final int value) {
this.value = value;
}
@NonNull
public static Size valueOf(final int value) {
final Size size = map.get(value);
return size != null ? size : Size.REGULAR;
}
public int getValue() {
return value;
}
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(dimensionPixelSize, dimensionPixelSize);
}
}

View File

@ -34,7 +34,6 @@ public final class RamboTextView extends AppCompatTextView {
private ClickableSpan clickableSpanUnderTouchOnActionDown;
private MentionClickListener mentionClickListener;
private boolean isUrlHighlighted;
private boolean isExpandable;
private boolean isExpanded;
private OnLongClickListener longClickListener;
@ -59,13 +58,13 @@ public final class RamboTextView extends AppCompatTextView {
this.mentionClickListener = mentionClickListener;
}
public void setCaptionIsExpandable(final boolean isExpandable) {
this.isExpandable = isExpandable;
}
// public void setCaptionIsExpandable(final boolean isExpandable) {
// this.isExpandable = isExpandable;
// }
public void setCaptionIsExpanded(final boolean isExpanded) {
this.isExpanded = isExpanded;
}
// public void setCaptionIsExpanded(final boolean isExpanded) {
// this.isExpanded = isExpanded;
// }
@Override
public void setOnLongClickListener(@Nullable final OnLongClickListener l) {
@ -98,7 +97,7 @@ public final class RamboTextView extends AppCompatTextView {
case MotionEvent.ACTION_DOWN:
final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
handler.postDelayed(longPressRunnable, longPressTimeout);
if (feedModel != null) feedModel.setMentionClicked(false);
// if (feedModel != null) feedModel.setMentionClicked(false);
if (clickableSpanUnderTouch != null) {
highlightUrl(clickableSpanUnderTouch, spanText);
}
@ -107,19 +106,19 @@ public final class RamboTextView extends AppCompatTextView {
handler.removeCallbacks(longPressRunnable);
if (touchStartedOverAClickableSpan && clickableSpanUnderTouch == clickableSpanUnderTouchOnActionDown) {
dispatchUrlClick(spanText, clickableSpanUnderTouch);
if (feedModel != null) feedModel.setMentionClicked(true);
// if (feedModel != null) feedModel.setMentionClicked(true);
}
cleanupOnTouchUp(spanText);
return super.onTouchEvent(event);
case MotionEvent.ACTION_MOVE:
// handler.removeCallbacks(longPressRunnable);
if (feedModel != null) feedModel.setMentionClicked(false);
// if (feedModel != null) feedModel.setMentionClicked(false);
if (clickableSpanUnderTouch != null) highlightUrl(clickableSpanUnderTouch, spanText);
else removeUrlHighlightColor(spanText);
return super.onTouchEvent(event);
case MotionEvent.ACTION_CANCEL:
handler.removeCallbacks(longPressRunnable);
if (feedModel != null) feedModel.setMentionClicked(false);
// if (feedModel != null) feedModel.setMentionClicked(false);
cleanupOnTouchUp(spanText);
return super.onTouchEvent(event);
}

View File

@ -0,0 +1,149 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import io.github.armcha.autolink.AutoLinkItem;
import io.github.armcha.autolink.AutoLinkTextView;
import io.github.armcha.autolink.MODE_EMAIL;
import io.github.armcha.autolink.MODE_HASHTAG;
import io.github.armcha.autolink.MODE_MENTION;
import io.github.armcha.autolink.MODE_URL;
import io.github.armcha.autolink.Mode;
public class RamboTextViewV2 extends AutoLinkTextView {
private final List<OnMentionClickListener> onMentionClickListeners = new ArrayList<>();
private final List<OnHashtagClickListener> onHashtagClickListeners = new ArrayList<>();
private final List<OnURLClickListener> onURLClickListeners = new ArrayList<>();
private final List<OnEmailClickListener> onEmailClickListeners = new ArrayList<>();
public RamboTextViewV2(@NonNull final Context context,
@Nullable final AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
addAutoLinkMode(MODE_HASHTAG.INSTANCE, MODE_MENTION.INSTANCE, MODE_EMAIL.INSTANCE, MODE_URL.INSTANCE);
onAutoLinkClick(autoLinkItem -> {
final Mode mode = autoLinkItem.getMode();
if (mode.equals(MODE_MENTION.INSTANCE)) {
for (final OnMentionClickListener onMentionClickListener : onMentionClickListeners) {
onMentionClickListener.onMentionClick(autoLinkItem);
}
return;
}
if (mode.equals(MODE_HASHTAG.INSTANCE)) {
for (final OnHashtagClickListener onHashtagClickListener : onHashtagClickListeners) {
onHashtagClickListener.onHashtagClick(autoLinkItem);
}
return;
}
if (mode.equals(MODE_URL.INSTANCE)) {
for (final OnURLClickListener onURLClickListener : onURLClickListeners) {
onURLClickListener.onURLClick(autoLinkItem);
}
return;
}
if (mode.equals(MODE_EMAIL.INSTANCE)) {
for (final OnEmailClickListener onEmailClickListener : onEmailClickListeners) {
onEmailClickListener.onEmailClick(autoLinkItem);
}
}
});
}
public void addOnMentionClickListener(final OnMentionClickListener onMentionClickListener) {
if (onMentionClickListener == null) {
return;
}
onMentionClickListeners.add(onMentionClickListener);
}
public void removeOnMentionClickListener(final OnMentionClickListener onMentionClickListener) {
if (onMentionClickListener == null) {
return;
}
onMentionClickListeners.remove(onMentionClickListener);
}
public void clearOnMentionClickListeners() {
onMentionClickListeners.clear();
}
public void addOnHashtagListener(final OnHashtagClickListener onHashtagClickListener) {
if (onHashtagClickListener == null) {
return;
}
onHashtagClickListeners.add(onHashtagClickListener);
}
public void removeOnHashtagListener(final OnHashtagClickListener onHashtagClickListener) {
if (onHashtagClickListener == null) {
return;
}
onHashtagClickListeners.remove(onHashtagClickListener);
}
public void clearOnHashtagClickListeners() {
onHashtagClickListeners.clear();
}
public void addOnURLClickListener(final OnURLClickListener onURLClickListener) {
if (onURLClickListener == null) {
return;
}
onURLClickListeners.add(onURLClickListener);
}
public void removeOnURLClickListener(final OnURLClickListener onURLClickListener) {
if (onURLClickListener == null) {
return;
}
onURLClickListeners.remove(onURLClickListener);
}
public void clearOnURLClickListeners() {
onURLClickListeners.clear();
}
public void addOnEmailClickListener(final OnEmailClickListener onEmailClickListener) {
if (onEmailClickListener == null) {
return;
}
onEmailClickListeners.add(onEmailClickListener);
}
public void removeOnEmailClickListener(final OnEmailClickListener onEmailClickListener) {
if (onEmailClickListener == null) {
return;
}
onEmailClickListeners.remove(onEmailClickListener);
}
public void clearOnEmailClickListeners() {
onEmailClickListeners.clear();
}
public interface OnMentionClickListener {
void onMentionClick(final AutoLinkItem autoLinkItem);
}
public interface OnHashtagClickListener {
void onHashtagClick(final AutoLinkItem autoLinkItem);
}
public interface OnURLClickListener {
void onURLClick(final AutoLinkItem autoLinkItem);
}
public interface OnEmailClickListener {
void onEmailClick(final AutoLinkItem autoLinkItem);
}
}

View File

@ -1,199 +0,0 @@
package awais.instagrabber.customviews;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import awais.instagrabber.R;
public final class RemixDrawerLayout extends MouseDrawer implements MouseDrawer.DrawerListener {
private final FrameLayout frameLayout;
private View drawerView;
private RecyclerView highlightsList, feedPosts, feedStories;
private float startX;
public RemixDrawerLayout(@NonNull final Context context) {
this(context, null);
}
public RemixDrawerLayout(@NonNull final Context context, @Nullable final AttributeSet attrs) {
this(context, attrs, 0);
}
public RemixDrawerLayout(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
super.setDrawerElevation(getDrawerElevation());
addDrawerListener(this);
frameLayout = new FrameLayout(context);
frameLayout.setPadding(0, 0, 0, 0);
super.addView(frameLayout);
}
@Override
public void addView(@NonNull final View child, final ViewGroup.LayoutParams params) {
child.setLayoutParams(params);
addView(child);
}
@Override
public void addView(@NonNull final View child) {
if (child.getTag() != null) super.addView(child);
else frameLayout.addView(child);
}
@Override
public boolean onInterceptTouchEvent(@NonNull final MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
// another one of my own weird hack thingies to make this app work
if (feedPosts == null) feedPosts = findViewById(R.id.feedPosts);
if (feedPosts != null) {
for (int i = 0; i < feedPosts.getChildCount(); ++i) {
final View viewHolder = feedPosts.getChildAt(i);
final View mediaList = viewHolder.findViewById(R.id.media_list);
if (mediaList instanceof ViewPager) {
final ViewPager viewPager = (ViewPager) mediaList;
final Rect rect = new Rect();
viewPager.getGlobalVisibleRect(rect);
final boolean touchIsInMediaList = rect.contains((int) x, (int) y);
if (touchIsInMediaList) {
final PagerAdapter adapter = viewPager.getAdapter();
final int count = adapter != null ? adapter.getCount() : 0;
if (count < 1 || viewPager.getCurrentItem() != count - 1) return false;
break;
}
}
}
}
// thanks to Fede @ https://stackoverflow.com/questions/6920137/android-viewpager-and-horizontalscrollview/7258579#7258579
if (highlightsList == null) highlightsList = findViewById(R.id.highlightsList);
if (highlightsList != null) {
final Boolean result = handleHorizontalRecyclerView(ev, highlightsList);
if (result != null) {
return result;
}
}
if (feedStories == null) feedStories = findViewById(R.id.feedStories);
if (feedStories != null) {
final Boolean result = handleHorizontalRecyclerView(ev, feedStories);
if (result != null) {
return result;
}
}
return super.onInterceptTouchEvent(ev);
}
private Boolean handleHorizontalRecyclerView(@NonNull final MotionEvent ev, final RecyclerView view) {
final float x = ev.getX();
final float y = ev.getY();
final boolean touchIsInRecycler = x >= view.getLeft() && x < view.getRight()
&& y >= view.getTop() && view.getBottom() > y;
if (touchIsInRecycler) {
final int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_CANCEL) return super.onInterceptTouchEvent(ev);
if (action == MotionEvent.ACTION_DOWN) startX = x;
else if (action == MotionEvent.ACTION_MOVE) {
final int scrollRange = view.computeHorizontalScrollRange();
final int scrollOffset = view.computeHorizontalScrollOffset();
final boolean scrollable = scrollRange > view.getWidth();
final boolean draggingFromRight = startX > x;
if (scrollOffset < 1) {
if (!scrollable) return super.onInterceptTouchEvent(ev);
else if (!draggingFromRight) return super.onInterceptTouchEvent(ev);
} else if (scrollable && draggingFromRight && scrollRange - scrollOffset == view.computeHorizontalScrollExtent()) {
return super.onInterceptTouchEvent(ev);
}
return false;
}
}
return null;
}
@Override
public void onDrawerSlide(@NonNull final View view, @EdgeGravity final int gravity, final float slideOffset) {
drawerView = view;
final int absHorizGravity = getDrawerViewAbsoluteGravity(GravityCompat.START);
final int childAbsGravity = getDrawerViewAbsoluteGravity(drawerView);
final Window window = getActivity(getContext()).getWindow();
final boolean isRtl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
|| window.getDecorView().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
|| getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
final int drawerViewWidth = drawerView.getWidth();
// for (int i = 0; i < frameLayout.getChildCount(); i++) {
// final View child = frameLayout.getChildAt(i);
//
// final boolean isLeftDrawer = isRtl == (childAbsGravity != absHorizGravity);
// float width = isLeftDrawer ? drawerViewWidth : -drawerViewWidth;
//
// child.setX(width * slideOffset);
// }
final boolean isLeftDrawer = isRtl == (childAbsGravity != absHorizGravity);
float width = isLeftDrawer ? drawerViewWidth : -drawerViewWidth;
frameLayout.setX(width * (isRtl ? -slideOffset : slideOffset));
}
@Override
public void openDrawer(@NonNull final View drawerView, final boolean animate) {
super.openDrawer(drawerView, animate);
post(() -> onDrawerSlide(drawerView, Gravity.NO_GRAVITY, isDrawerOpen(drawerView) ? 1f : 0f));
}
@Override
protected void onConfigurationChanged(final Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (drawerView != null) onDrawerSlide(drawerView, Gravity.NO_GRAVITY, isDrawerOpen(drawerView) ? 1f : 0f);
}
private static Activity getActivity(final Context context) {
if (context != null) {
if (context instanceof Activity) return (Activity) context;
if (context instanceof ContextWrapper)
return getActivity(((ContextWrapper) context).getBaseContext());
}
return null;
}
final int getDrawerViewAbsoluteGravity(final int gravity) {
return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this)) & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK;
}
final int getDrawerViewAbsoluteGravity(@NonNull final View drawerView) {
final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
return getDrawerViewAbsoluteGravity(gravity);
}
}

View File

@ -0,0 +1,304 @@
package awais.instagrabber.customviews;
import android.animation.Animator;
import android.graphics.Rect;
import android.os.Handler;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateDecelerateInterpolator;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.transition.ChangeBounds;
import androidx.transition.ChangeTransform;
import androidx.transition.Transition;
import androidx.transition.TransitionListenerAdapter;
import androidx.transition.TransitionManager;
import androidx.transition.TransitionSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import awais.instagrabber.utils.Utils;
public abstract class SharedElementTransitionDialogFragment extends DialogFragment {
// private static final String TAG = "SETDialogFragment";
private static final int DURATION = 200;
private final Map<Integer, View> startViews = new HashMap<>();
private final Map<Integer, View> destViews = new HashMap<>();
private final Map<Integer, ViewBounds> viewBoundsMap = new HashMap<>();
private final List<Animator> additionalAnimators = new ArrayList<>();
private final Handler initialBoundsHandler = new Handler();
private boolean startCalled = false;
private boolean startInitiated = false;
private int boundsCalculatedCount = 0;
protected int getAnimationDuration() {
return DURATION;
}
public void addSharedElement(@NonNull final View startView, @NonNull final View destView) {
final int key = destView.hashCode();
startViews.put(key, startView);
destViews.put(key, destView);
initialBoundsHandler.post(() -> setupInitialBounds(startView, destView));
}
public void startPostponedEnterTransition() {
startCalled = true;
if (startInitiated) return;
if (boundsCalculatedCount < startViews.size()) return;
startInitiated = true;
final Set<Integer> keySet = startViews.keySet();
final View view = getView();
if (!(view instanceof ViewGroup)) return;
final TransitionSet transitionSet = new TransitionSet()
.setDuration(DURATION)
.setInterpolator(new AccelerateDecelerateInterpolator())
.addTransition(new ChangeBounds())
.addTransition(new ChangeTransform())
.addListener(new TransitionListenerAdapter() {
@Override
public void onTransitionStart(@NonNull final Transition transition) {
for (Animator animator : additionalAnimators) {
animator.start();
}
}
@Override
public void onTransitionEnd(@NonNull final Transition transition) {
for (final Integer key : keySet) {
final View startView = startViews.get(key);
final View destView = destViews.get(key);
final ViewBounds viewBounds = viewBoundsMap.get(key);
if (startView == null || destView == null || viewBounds == null) return;
onEndSharedElementAnimation(startView, destView, viewBounds);
}
}
});
view.post(() -> {
TransitionManager.beginDelayedTransition((ViewGroup) view, transitionSet);
for (final Integer key : keySet) {
final View startView = startViews.get(key);
final View destView = destViews.get(key);
final ViewBounds viewBounds = viewBoundsMap.get(key);
if (startView == null || destView == null || viewBounds == null) return;
onBeforeSharedElementAnimation(startView, destView, viewBounds);
setDestBounds(key);
}
});
}
private void setDestBounds(final int key) {
final View startView = startViews.get(key);
if (startView == null) return;
final View destView = destViews.get(key);
if (destView == null) return;
final ViewBounds viewBounds = viewBoundsMap.get(key);
if (viewBounds == null) return;
destView.setX((int) viewBounds.getDestX());
destView.setY((int) viewBounds.getDestY());
destView.setTranslationX(0);
destView.setTranslationY(0);
final ViewGroup.LayoutParams layoutParams = destView.getLayoutParams();
layoutParams.height = viewBounds.getDestHeight();
layoutParams.width = viewBounds.getDestWidth();
destView.requestLayout();
}
protected void onBeforeSharedElementAnimation(@NonNull final View startView,
@NonNull final View destView,
@NonNull final ViewBounds viewBounds) {}
protected void onEndSharedElementAnimation(@NonNull final View startView,
@NonNull final View destView,
@NonNull final ViewBounds viewBounds) {}
private void setupInitialBounds(@NonNull final View startView, @NonNull final View destView) {
final ViewTreeObserver.OnPreDrawListener preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
private boolean firstPassDone;
@Override
public boolean onPreDraw() {
destView.getViewTreeObserver().removeOnPreDrawListener(this);
if (!firstPassDone) {
getViewBounds(startView, destView, this);
firstPassDone = true;
return false;
}
final int[] location = new int[2];
startView.getLocationOnScreen(location);
final int initX = location[0];
final int initY = location[1];
destView.setX(initX);
destView.setY(initY - Utils.getStatusBarHeight(getContext()));
destView.requestLayout();
boundsCalculatedCount++;
if (startCalled) {
startPostponedEnterTransition();
}
return true;
}
};
destView.getViewTreeObserver().addOnPreDrawListener(preDrawListener);
}
private void getViewBounds(@NonNull final View startView,
@NonNull final View destView,
@NonNull final ViewTreeObserver.OnPreDrawListener preDrawListener) {
final ViewBounds viewBounds = new ViewBounds();
viewBounds.setDestWidth(destView.getWidth());
viewBounds.setDestHeight(destView.getHeight());
viewBounds.setDestX(destView.getX());
viewBounds.setDestY(destView.getY());
final Rect destBounds = new Rect();
destView.getDrawingRect(destBounds);
viewBounds.setDestBounds(destBounds);
final ViewGroup.LayoutParams layoutParams = destView.getLayoutParams();
final Rect startBounds = new Rect();
startView.getDrawingRect(startBounds);
viewBounds.setStartBounds(startBounds);
final int key = destView.hashCode();
viewBoundsMap.put(key, viewBounds);
layoutParams.height = startView.getHeight();
layoutParams.width = startView.getWidth();
destView.getViewTreeObserver().addOnPreDrawListener(preDrawListener);
destView.requestLayout();
}
// private void animateBounds(@NonNull final View startView,
// @NonNull final View destView,
// @NonNull final ViewBounds viewBounds) {
// final ValueAnimator heightAnimator = ObjectAnimator.ofInt(startView.getHeight(), viewBounds.getDestHeight());
// final ValueAnimator widthAnimator = ObjectAnimator.ofInt(startView.getWidth(), viewBounds.getDestWidth());
// heightAnimator.setDuration(DURATION);
// widthAnimator.setDuration(DURATION);
// additionalAnimators.add(heightAnimator);
// additionalAnimators.add(widthAnimator);
// heightAnimator.addUpdateListener(animation -> {
// ViewGroup.LayoutParams params = destView.getLayoutParams();
// params.height = (int) animation.getAnimatedValue();
// destView.requestLayout();
// });
// widthAnimator.addUpdateListener(animation -> {
// ViewGroup.LayoutParams params = destView.getLayoutParams();
// params.width = (int) animation.getAnimatedValue();
// destView.requestLayout();
// });
// onBeforeSharedElementAnimation(startView, destView, viewBounds);
// final float destX = viewBounds.getDestX();
// final float destY = viewBounds.getDestY();
// final AnimatorSet animatorSet = new AnimatorSet();
// animatorSet.addListener(new AnimatorListenerAdapter() {
// @Override
// public void onAnimationEnd(final Animator animation) {
// animationEnded(startView, destView, viewBounds);
// }
// });
//
// destView.animate()
// .x(destX)
// .y(destY)
// .setDuration(DURATION)
// .withStartAction(() -> {
// if (!additionalAnimatorsStarted && additionalAnimators.size() > 0) {
// additionalAnimatorsStarted = true;
// animatorSet.playTogether(additionalAnimators);
// animatorSet.start();
// }
// })
// .withEndAction(() -> animationEnded(startView, destView, viewBounds))
// .start();
// }
// private int endCount = 0;
// private void animationEnded(final View startView, final View destView, final ViewBounds viewBounds) {
// ++endCount;
// if (endCount != startViews.size() + 1) return;
// onEndSharedElementAnimation(startView, destView, viewBounds);
// }
protected void addAnimator(@NonNull final Animator animator) {
additionalAnimators.add(animator);
}
protected static class ViewBounds {
private float destY;
private float destX;
private int destHeight;
private int destWidth;
private Rect startBounds;
private Rect destBounds;
public ViewBounds() {}
public float getDestY() {
return destY;
}
public void setDestY(final float destY) {
this.destY = destY;
}
public float getDestX() {
return destX;
}
public void setDestX(final float destX) {
this.destX = destX;
}
public int getDestHeight() {
return destHeight;
}
public void setDestHeight(final int destHeight) {
this.destHeight = destHeight;
}
public int getDestWidth() {
return destWidth;
}
public void setDestWidth(final int destWidth) {
this.destWidth = destWidth;
}
public Rect getStartBounds() {
return startBounds;
}
public void setStartBounds(final Rect startBounds) {
this.startBounds = startBounds;
}
public Rect getDestBounds() {
return destBounds;
}
public void setDestBounds(final Rect destBounds) {
this.destBounds = destBounds;
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
startViews.clear();
destViews.clear();
viewBoundsMap.clear();
additionalAnimators.clear();
}
}

View File

@ -0,0 +1,203 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
import androidx.annotation.NonNull;
public class VerticalDragHelper {
private static final String TAG = "VerticalDragHelper";
private static final float PIXELS_PER_SECOND = 10;
private final View view;
private GestureDetector gestureDetector;
private Context context;
private float flingVelocity;
private OnVerticalDragListener onVerticalDragListener;
private final GestureDetector.OnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapConfirmed(final MotionEvent e) {
view.performClick();
return true;
}
@Override
public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY) {
float maxFlingVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();
float velocityPercentY = velocityY / maxFlingVelocity;
float normalizedVelocityY = velocityPercentY * PIXELS_PER_SECOND;
if (Math.abs(normalizedVelocityY) > 4) {
flingVelocity = normalizedVelocityY;
}
return super.onFling(e1, e2, velocityX, velocityY);
}
};
private final GestureDetector.OnGestureListener dragPreventionGestureListener = new GestureDetector.SimpleOnGestureListener() {
float prevDistanceY = 0;
@Override
public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX, final float distanceY) {
Log.d(TAG, "onScroll: distanceX: " + distanceX + ", distanceY: " + distanceY);
return super.onScroll(e1, e2, distanceX, distanceY);
}
@Override
public boolean onSingleTapUp(final MotionEvent e) {
Log.d(TAG, "onSingleTapUp");
return super.onSingleTapUp(e);
}
};
private float prevRawY;
private boolean isDragging;
private float prevRawX;
private float dX;
private float prevDY;
private GestureDetector dragPreventionGestureDetector;
public VerticalDragHelper(@NonNull final View view) {
this.view = view;
final Context context = view.getContext();
if (context == null) return;
this.context = context;
init();
}
public void setOnVerticalDragListener(@NonNull final OnVerticalDragListener onVerticalDragListener) {
this.onVerticalDragListener = onVerticalDragListener;
}
protected void init() {
gestureDetector = new GestureDetector(context, gestureListener);
dragPreventionGestureDetector = new GestureDetector(context, dragPreventionGestureListener);
}
public boolean onDragTouch(final MotionEvent event) {
if (onVerticalDragListener == null) {
return false;
}
// dragPreventionGestureDetector.onTouchEvent(event);
if (gestureDetector.onTouchEvent(event)) {
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_MOVE:
boolean handled = false;
final float rawY = event.getRawY();
final float dY = rawY - prevRawY;
if (!isDragging) {
final float rawX = event.getRawX();
if (prevRawX != 0) {
dX = rawX - prevRawX;
}
prevRawX = rawX;
if (prevRawY != 0) {
final float dYAbs = Math.abs(dY - prevDY);
if (!isDragging && dYAbs < 50) {
final float abs = Math.abs(dY) - Math.abs(dX);
if (abs > 0) {
isDragging = true;
}
}
}
}
if (isDragging) {
final ViewParent parent = view.getParent();
parent.requestDisallowInterceptTouchEvent(true);
onVerticalDragListener.onDrag(dY);
handled = true;
}
prevDY = dY;
prevRawY = rawY;
return handled;
case MotionEvent.ACTION_UP:
// Log.d(TAG, "onDragTouch: reset prevRawY");
prevRawY = 0;
if (flingVelocity != 0) {
onVerticalDragListener.onFling(flingVelocity);
flingVelocity = 0;
isDragging = false;
return true;
}
if (isDragging) {
onVerticalDragListener.onDragEnd();
isDragging = false;
return true;
}
return false;
default:
return false;
}
}
public boolean isDragging() {
return isDragging;
}
public boolean onGestureTouchEvent(final MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
private final static int DIRECTION_UP = 0;
private final static int DIRECTION_DOWN = 1;
float prevY = -1;
int edgeHitCount = 0;
float prevDirection = -1;
// private boolean shouldPreventDrag(final MotionEvent event) {
// switch (event.getAction()) {
// case MotionEvent.ACTION_DOWN:
// if (!firstDrag) {
// firstDrag = true;
// }
// return false;
// case MotionEvent.ACTION_MOVE:
// float y = event.getY();
// int direction = -2;
// if (prevY != -1) {
// final float dy = y - prevY;
// // Log.d(TAG, "shouldPreventDrag: dy: " + dy);
// if (dy > 0) {
// direction = DIRECTION_DOWN;
// // move direction is down
// } else {
// direction = DIRECTION_UP;
// // move direction is up
// }
// }
// prevY = y;
// if (prevDirection == direction) {
// edgeHitCount++;
// } else {
// edgeHitCount = 1;
// }
// if (edgeHitCount >= 2) {
// return false;
// }
// return true;
// break;
// }
// }
public interface OnVerticalDragListener {
void onDrag(final float dY);
void onDragEnd();
void onFling(final float flingVelocity);
}
}

View File

@ -0,0 +1,18 @@
package awais.instagrabber.customviews;
public class VideoPlayerCallbackAdapter implements VideoPlayerViewHelper.VideoPlayerCallback {
@Override
public void onThumbnailLoaded() {}
@Override
public void onThumbnailClick() {}
@Override
public void onPlayerViewLoaded() {}
@Override
public void onPlay() {}
@Override
public void onPause() {}
}

View File

@ -0,0 +1,371 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.graphics.drawable.Animatable;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.appcompat.widget.PopupMenu;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.audio.AudioListener;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import awais.instagrabber.R;
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding;
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
import awais.instagrabber.utils.TextUtils;
import static com.google.android.exoplayer2.C.TIME_UNSET;
import static com.google.android.exoplayer2.Player.STATE_ENDED;
import static com.google.android.exoplayer2.Player.STATE_IDLE;
import static com.google.android.exoplayer2.Player.STATE_READY;
public class VideoPlayerViewHelper implements Player.EventListener {
private static final String TAG = "VideoPlayerViewHelper";
private final Context context;
private final awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding binding;
private final float initialVolume;
private final float thumbnailAspectRatio;
private final String thumbnailUrl;
private final boolean loadPlayerOnClick;
private final awais.instagrabber.databinding.LayoutExoCustomControlsBinding controlsBinding;
private final VideoPlayerCallback videoPlayerCallback;
private final String videoUrl;
private final DefaultDataSourceFactory dataSourceFactory;
private SimpleExoPlayer player;
private PopupMenu speedPopup;
public VideoPlayerViewHelper(@NonNull final Context context,
@NonNull final LayoutVideoPlayerWithThumbnailBinding binding,
@NonNull final String videoUrl,
final float initialVolume,
final float thumbnailAspectRatio,
final String thumbnailUrl,
final boolean loadPlayerOnClick,
final LayoutExoCustomControlsBinding controlsBinding,
final VideoPlayerCallback videoPlayerCallback) {
this.context = context;
this.binding = binding;
this.initialVolume = initialVolume;
this.thumbnailAspectRatio = thumbnailAspectRatio;
this.thumbnailUrl = thumbnailUrl;
this.loadPlayerOnClick = loadPlayerOnClick;
this.controlsBinding = controlsBinding;
this.videoPlayerCallback = videoPlayerCallback;
this.videoUrl = videoUrl;
this.dataSourceFactory = new DefaultDataSourceFactory(binding.getRoot().getContext(), "instagram");
bind();
}
private void bind() {
binding.thumbnailParent.setOnClickListener(v -> {
if (videoPlayerCallback != null) {
videoPlayerCallback.onThumbnailClick();
}
if (loadPlayerOnClick) {
loadPlayer();
}
});
setThumbnail();
setupControls();
}
private void setThumbnail() {
binding.thumbnail.setAspectRatio(thumbnailAspectRatio);
final ImageRequest thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(thumbnailUrl))
.build();
final DraweeController controller = Fresco.newDraweeControllerBuilder()
.setControllerListener(new BaseControllerListener<ImageInfo>() {
@Override
public void onFailure(final String id, final Throwable throwable) {
if (videoPlayerCallback != null) {
videoPlayerCallback.onThumbnailLoaded();
}
}
@Override
public void onFinalImageSet(final String id,
final ImageInfo imageInfo,
final Animatable animatable) {
if (videoPlayerCallback != null) {
videoPlayerCallback.onThumbnailLoaded();
}
}
})
.setImageRequest(thumbnailRequest)
.build();
binding.thumbnail.setController(controller);
}
private void loadPlayer() {
if (videoUrl == null) return;
if (binding.root.getDisplayedChild() == 0) {
binding.root.showNext();
}
if (videoPlayerCallback != null) {
videoPlayerCallback.onPlayerViewLoaded();
}
player = (SimpleExoPlayer) binding.playerView.getPlayer();
if (player != null) {
player.release();
}
player = new SimpleExoPlayer.Builder(context)
.setLooper(Looper.getMainLooper())
.build();
player.addListener(this);
player.setVolume(initialVolume);
player.setPlayWhenReady(true);
player.setRepeatMode(Player.REPEAT_MODE_ALL);
final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(dataSourceFactory);
final MediaItem mediaItem = MediaItem.fromUri(videoUrl);
final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(mediaItem);
player.setMediaSource(mediaSource);
setupControls();
player.prepare();
binding.playerView.setPlayer(player);
}
private void setupControls() {
if (controlsBinding == null) return;
binding.playerView.setUseController(false);
if (player == null) {
enableControls(false);
controlsBinding.playPause.setEnabled(true);
controlsBinding.playPause.setOnClickListener(v -> binding.thumbnailParent.performClick());
return;
}
enableControls(true);
final Handler handler = new Handler();
final long initialDelay = 0;
final long recurringDelay = 60;
final Runnable positionChecker = new Runnable() {
@Override
public void run() {
handler.removeCallbacks(this);
if (player == null) return;
final long currentPosition = player.getCurrentPosition();
final long duration = player.getDuration();
if (duration == TIME_UNSET) {
controlsBinding.timeline.setValueFrom(0);
controlsBinding.timeline.setValueTo(0);
controlsBinding.timeline.setEnabled(false);
return;
}
controlsBinding.timeline.setValue(Math.min(currentPosition, duration));
controlsBinding.fromTime.setText(TextUtils.millisToTimeString(currentPosition));
handler.postDelayed(this, recurringDelay);
}
};
updatePlayPauseDrawable(player.getPlayWhenReady());
updateMuteIcon(player.getVolume());
player.addListener(new Player.EventListener() {
@Override
public void onPlaybackStateChanged(final int state) {
switch (state) {
case Player.STATE_BUFFERING:
case STATE_IDLE:
case STATE_ENDED:
handler.removeCallbacks(positionChecker);
return;
case STATE_READY:
setupTimeline();
handler.postDelayed(positionChecker, initialDelay);
break;
}
}
@Override
public void onPlayWhenReadyChanged(final boolean playWhenReady, final int reason) {
updatePlayPauseDrawable(playWhenReady);
}
});
player.addAudioListener(new AudioListener() {
@Override
public void onVolumeChanged(final float volume) {
updateMuteIcon(volume);
}
});
controlsBinding.timeline.addOnChangeListener((slider, value, fromUser) -> {
if (!fromUser) return;
long actualValue = (long) value;
if (actualValue < 0) {
actualValue = 0;
} else if (actualValue > player.getDuration()) {
actualValue = player.getDuration();
}
player.seekTo(actualValue);
});
controlsBinding.timeline.setLabelFormatter(value -> TextUtils.millisToTimeString((long) value));
controlsBinding.playPause.setOnClickListener(v -> player.setPlayWhenReady(!player.getPlayWhenReady()));
controlsBinding.mute.setOnClickListener(v -> toggleMute());
controlsBinding.rewWithAmount.setOnClickListener(v -> {
final long positionMs = player.getCurrentPosition() - 5000;
player.seekTo(positionMs < 0 ? 0 : positionMs);
});
controlsBinding.ffWithAmount.setOnClickListener(v -> {
long positionMs = player.getCurrentPosition() + 5000;
long duration = player.getDuration();
if (duration == TIME_UNSET) {
duration = 0;
}
player.seekTo(Math.min(positionMs, duration));
});
controlsBinding.speed.setOnClickListener(this::showMenu);
}
private void setupTimeline() {
final long duration = player.getDuration();
controlsBinding.timeline.setEnabled(true);
controlsBinding.timeline.setValueFrom(0);
controlsBinding.timeline.setValueTo(duration);
controlsBinding.fromTime.setText(TextUtils.millisToTimeString(0));
controlsBinding.toTime.setText(TextUtils.millisToTimeString(duration));
}
private void enableControls(final boolean enable) {
controlsBinding.speed.setEnabled(enable);
controlsBinding.mute.setEnabled(enable);
controlsBinding.ffWithAmount.setEnabled(enable);
controlsBinding.rewWithAmount.setEnabled(enable);
controlsBinding.fromTime.setEnabled(enable);
controlsBinding.toTime.setEnabled(enable);
controlsBinding.playPause.setEnabled(enable);
}
public void showMenu(View anchor) {
PopupMenu popup = getPopupMenu(anchor);
popup.show();
}
@NonNull
private PopupMenu getPopupMenu(final View anchor) {
if (speedPopup != null) {
return speedPopup;
}
final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, R.style.popupMenuStyle);
// final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, R.style.Widget_MaterialComponents_PopupMenu_Exoplayer);
speedPopup = new PopupMenu(themeWrapper, anchor);
speedPopup.getMenuInflater().inflate(R.menu.speed_menu, speedPopup.getMenu());
speedPopup.setOnMenuItemClickListener(item -> {
float nextSpeed;
int textResId;
int itemId = item.getItemId();
if (itemId == R.id.pt_two_five_x) {
nextSpeed = 0.25f;
textResId = R.string.pt_two_five_x;
} else if (itemId == R.id.pt_five_x) {
nextSpeed = 0.5f;
textResId = R.string.pt_five_x;
} else if (itemId == R.id.pt_seven_five_x) {
nextSpeed = 0.75f;
textResId = R.string.pt_seven_five_x;
} else if (itemId == R.id.one_x) {
nextSpeed = 1f;
textResId = R.string.one_x;
} else if (itemId == R.id.one_pt_two_five_x) {
nextSpeed = 1.25f;
textResId = R.string.one_pt_two_five_x;
} else if (itemId == R.id.one_pt_five_x) {
nextSpeed = 1.5f;
textResId = R.string.one_pt_five_x;
} else if (itemId == R.id.two_x) {
nextSpeed = 2f;
textResId = R.string.two_x;
} else {
nextSpeed = 1;
textResId = R.string.one_x;
}
player.setPlaybackParameters(new PlaybackParameters(nextSpeed));
controlsBinding.speed.setText(textResId);
return true;
});
return speedPopup;
}
private void updateMuteIcon(final float volume) {
if (volume == 0) {
controlsBinding.mute.setIconResource(R.drawable.ic_volume_off_24);
return;
}
controlsBinding.mute.setIconResource(R.drawable.ic_volume_up_24);
}
private void updatePlayPauseDrawable(final boolean playWhenReady) {
if (playWhenReady) {
controlsBinding.playPause.setIconResource(R.drawable.ic_pause_24);
return;
}
controlsBinding.playPause.setIconResource(R.drawable.ic_play_arrow_24);
}
@Override
public void onPlayWhenReadyChanged(final boolean playWhenReady, final int reason) {
if (videoPlayerCallback == null) return;
if (playWhenReady) {
videoPlayerCallback.onPlay();
return;
}
videoPlayerCallback.onPause();
}
@Override
public void onPlayerError(final ExoPlaybackException error) {
Log.e(TAG, "onPlayerError", error);
}
public float toggleMute() {
if (player == null) return 0;
final float vol = player.getVolume() == 0f ? 1f : 0f;
player.setVolume(vol);
return vol;
}
public void togglePlayback() {
if (player == null) return;
final int playbackState = player.getPlaybackState();
if (playbackState == STATE_IDLE || playbackState == STATE_ENDED) return;
final boolean playWhenReady = player.getPlayWhenReady();
player.setPlayWhenReady(!playWhenReady);
}
public void releasePlayer() {
if (player == null) return;
player.release();
player = null;
}
public void pause() {
if (player == null) return;
player.pause();
}
public interface VideoPlayerCallback {
void onThumbnailLoaded();
void onThumbnailClick();
void onPlayerViewLoaded();
void onPlay();
void onPause();
}
}

View File

@ -572,16 +572,16 @@ public class DefaultZoomableController
RectF b = mTempRect;
b.set(mImageBounds);
transform.mapRect(b);
float offsetLeft =
shouldLimit(limitTypes, LIMIT_TRANSLATION_X)
? getOffset(
b.left, b.right, mViewBounds.left, mViewBounds.right, mImageBounds.centerX())
: 0;
float offsetTop =
shouldLimit(limitTypes, LIMIT_TRANSLATION_Y)
? getOffset(
b.top, b.bottom, mViewBounds.top, mViewBounds.bottom, mImageBounds.centerY())
: 0;
final boolean shouldLimitX = shouldLimit(limitTypes, LIMIT_TRANSLATION_X);
float offsetLeft = shouldLimitX
? getOffset(b.left, b.right, mViewBounds.left, mViewBounds.right, mImageBounds.centerX())
: 0;
float offsetTop = shouldLimit(limitTypes, LIMIT_TRANSLATION_Y)
? getOffset(b.top, b.bottom, mViewBounds.top, mViewBounds.bottom, mImageBounds.centerY())
: 0;
if (mListener != null) {
mListener.onTranslationLimited(offsetLeft, offsetTop);
}
if (offsetLeft != 0 || offsetTop != 0) {
transform.postTranslate(offsetLeft, offsetTop);
return true;

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package awais.instagrabber.customviews.drawee;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import awais.instagrabber.customviews.VerticalDragHelper;
import awais.instagrabber.customviews.VerticalDragHelper.OnVerticalDragListener;
public class DraggableZoomableDraweeView extends ZoomableDraweeView {
private static final String TAG = "DraggableZoomableDV";
private VerticalDragHelper verticalDragHelper;
public DraggableZoomableDraweeView(final Context context, final GenericDraweeHierarchy hierarchy) {
super(context, hierarchy);
verticalDragHelper = new VerticalDragHelper(this);
}
public DraggableZoomableDraweeView(final Context context) {
super(context);
verticalDragHelper = new VerticalDragHelper(this);
}
public DraggableZoomableDraweeView(final Context context, final AttributeSet attrs) {
super(context, attrs);
verticalDragHelper = new VerticalDragHelper(this);
}
public DraggableZoomableDraweeView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
verticalDragHelper = new VerticalDragHelper(this);
}
public void setOnVerticalDragListener(@NonNull final OnVerticalDragListener onVerticalDragListener) {
verticalDragHelper.setOnVerticalDragListener(onVerticalDragListener);
}
private int lastPointerCount;
private int lastNewPointerCount;
private boolean wasTransformCorrected;
@Override
protected void onTransformEnd(final Matrix transform) {
super.onTransformEnd(transform);
final AnimatedZoomableController zoomableController = (AnimatedZoomableController) getZoomableController();
final TransformGestureDetector detector = zoomableController.getDetector();
lastNewPointerCount = detector.getNewPointerCount();
lastPointerCount = detector.getPointerCount();
}
@Override
protected void onTranslationLimited(final float offsetLeft, final float offsetTop) {
super.onTranslationLimited(offsetLeft, offsetTop);
wasTransformCorrected = offsetTop != 0;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(final MotionEvent event) {
boolean superResult = false;
if (verticalDragHelper.isDragging()) {
final boolean onDragTouch = verticalDragHelper.onDragTouch(event);
if (onDragTouch) {
return true;
}
}
if (!verticalDragHelper.isDragging()) {
superResult = super.onTouchEvent(event);
if (wasTransformCorrected
&& (lastPointerCount == 1 || lastPointerCount == 0)
&& (lastNewPointerCount == 1 || lastNewPointerCount == 0)) {
final boolean onDragTouch = verticalDragHelper.onDragTouch(event);
if (onDragTouch) {
return true;
}
}
}
final boolean gestureListenerResult = verticalDragHelper.onGestureTouchEvent(event);
if (gestureListenerResult) {
return true;
}
return superResult;
}
}

View File

@ -41,6 +41,13 @@ public class MultiZoomableControllerListener implements ZoomableController.Liste
}
}
@Override
public void onTranslationLimited(final float offsetLeft, final float offsetTop) {
for (ZoomableController.Listener listener : mListeners) {
listener.onTranslationLimited(offsetLeft, offsetTop);
}
}
public synchronized void addListener(ZoomableController.Listener listener) {
mListeners.add(listener);
}

View File

@ -42,6 +42,8 @@ public interface ZoomableController {
* @param transform the current transform matrix
*/
void onTransformEnd(Matrix transform);
void onTranslationLimited(float offsetLeft, float offsetTop);
}
/**

View File

@ -33,8 +33,6 @@ import com.facebook.drawee.generic.GenericDraweeHierarchyInflater;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.DraweeView;
import awais.instagrabber.customviews.helpers.SwipeGestureListener;
/**
* DraweeView that has zoomable capabilities.
@ -54,7 +52,7 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
private DraweeController mHugeImageController;
private ZoomableController mZoomableController;
private GestureDetector mTapGestureDetector;
private boolean mAllowTouchInterceptionWhileZoomed = true;
private boolean mAllowTouchInterceptionWhileZoomed = false;
private boolean mIsDialtoneEnabled = false;
private boolean mZoomingEnabled = true;
@ -76,7 +74,9 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
private final ZoomableController.Listener mZoomableListener =
new ZoomableController.Listener() {
@Override
public void onTransformBegin(Matrix transform) {}
public void onTransformBegin(Matrix transform) {
ZoomableDraweeView.this.onTransformBegin(transform);
}
@Override
public void onTransformChanged(Matrix transform) {
@ -84,7 +84,14 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
}
@Override
public void onTransformEnd(Matrix transform) {}
public void onTransformEnd(Matrix transform) {
ZoomableDraweeView.this.onTransformEnd(transform);
}
@Override
public void onTranslationLimited(final float offsetLeft, final float offsetTop) {
ZoomableDraweeView.this.onTranslationLimited(offsetLeft, offsetTop);
}
};
private final GestureListenerWrapper mTapListenerWrapper = new GestureListenerWrapper();
@ -302,11 +309,10 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
int a = event.getActionMasked();
FLog.v(getLogTag(), "onTouchEvent: %d, view %x, received", a, this.hashCode());
if (!mIsDialtoneEnabled && mTapGestureDetector.onTouchEvent(event)) {
FLog.v(
getLogTag(),
"onTouchEvent: %d, view %x, handled by tap gesture detector",
a,
this.hashCode());
FLog.v(getLogTag(),
"onTouchEvent: %d, view %x, handled by tap gesture detector",
a,
this.hashCode());
return true;
}
@ -389,23 +395,29 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
mZoomableController.setEnabled(false);
}
protected void onTransformBegin(final Matrix transform) {}
protected void onTransformChanged(Matrix transform) {
FLog.v(getLogTag(), "onTransformChanged: view %x, transform: %s", this.hashCode(), transform);
maybeSetHugeImageController();
invalidate();
}
protected void onTransformEnd(final Matrix transform) {}
protected void onTranslationLimited(final float offsetLeft, final float offsetTop) {}
protected void updateZoomableControllerBounds() {
getImageBounds(mImageBounds);
getLimitBounds(mViewBounds);
// Log.d(TAG.getSimpleName(), "updateZoomableControllerBounds: mImageBounds: " + mImageBounds);
mZoomableController.setImageBounds(mImageBounds);
mZoomableController.setViewBounds(mViewBounds);
FLog.v(
getLogTag(),
"updateZoomableControllerBounds: view %x, view bounds: %s, image bounds: %s",
this.hashCode(),
mViewBounds,
mImageBounds);
FLog.v(getLogTag(),
"updateZoomableControllerBounds: view %x, view bounds: %s, image bounds: %s",
this.hashCode(),
mViewBounds,
mImageBounds);
}
protected Class<?> getLogTag() {

View File

@ -4,7 +4,6 @@ import android.graphics.Rect;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
@ -16,16 +15,14 @@ public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
final RecyclerView.LayoutManager manager = parent.getLayoutManager();
if (manager instanceof GridLayoutManager) {
final int spanCount = ((GridLayoutManager) manager).getSpanCount();
final int position = parent.getChildAdapterPosition(view);
final int column = position % spanCount;
outRect.left = column * spacing / spanCount;
outRect.right = spacing - (column + 1) * spacing / spanCount;
if (position < spanCount) outRect.top = spacing;
outRect.bottom = spacing;
final int halfSpace = spacing / 2;
if (parent.getPaddingLeft() != halfSpace) {
parent.setPadding(halfSpace, halfSpace, halfSpace, halfSpace);
parent.setClipToPadding(false);
}
outRect.top = halfSpace;
outRect.bottom = halfSpace;
outRect.left = halfSpace;
outRect.right = halfSpace;
}
}

View File

@ -0,0 +1,48 @@
package awais.instagrabber.customviews.helpers;
import java.util.List;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
public class PostFetcher {
private final PostFetchService postFetchService;
private final FetchListener<List<FeedModel>> fetchListener;
private boolean fetching;
public PostFetcher(final PostFetchService postFetchService,
final FetchListener<List<FeedModel>> fetchListener) {
this.postFetchService = postFetchService;
this.fetchListener = fetchListener;
}
public void fetch() {
if (!fetching) {
fetching = true;
postFetchService.fetch(result -> {
fetching = false;
fetchListener.onResult(result);
});
}
}
public void reset() {
postFetchService.reset();
}
public boolean isFetching() {
return fetching;
}
public boolean hasMore() {
return postFetchService.hasNextPage();
}
public interface PostFetchService {
void fetch(FetchListener<List<FeedModel>> fetchListener);
void reset();
boolean hasNextPage();
}
}

View File

@ -1,23 +1,41 @@
package awais.instagrabber.customviews.helpers;
import android.os.Handler;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import awais.instagrabber.interfaces.LazyLoadListener;
// thanks to nesquena's EndlessRecyclerViewScrollListener
// https://gist.github.com/nesquena/d09dc68ff07e845cc622
/**
* thanks to nesquena's <a href="https://gist.github.com/nesquena/d09dc68ff07e845cc622">EndlessRecyclerViewScrollListener</a>
*/
public final class RecyclerLazyLoader extends RecyclerView.OnScrollListener {
private int currentPage = 0; // The current offset index of data you have loaded
private int previousTotalItemCount = 0; // The total number of items in the dataset after the last load
private boolean loading = true; // True if we are still waiting for the last set of data to load.
private final int visibleThreshold; // The minimum amount of items to have below your current scroll position before loading more.
/**
* The current offset index of data you have loaded
*/
private int currentPage = 0;
/**
* The total number of items in the data set after the last load
*/
private int previousTotalItemCount = 0;
/**
* <code>true</code> if we are still waiting for the last set of data to load.
*/
private boolean loading = true;
/**
* The minimum amount of items to have below your current scroll position before loading more.
*/
private final int visibleThreshold;
private final LazyLoadListener lazyLoadListener;
private final RecyclerView.LayoutManager layoutManager;
public RecyclerLazyLoader(@NonNull final RecyclerView.LayoutManager layoutManager, final LazyLoadListener lazyLoadListener, final int threshold) {
public RecyclerLazyLoader(@NonNull final RecyclerView.LayoutManager layoutManager,
final LazyLoadListener lazyLoadListener,
final int threshold) {
this.layoutManager = layoutManager;
this.lazyLoadListener = lazyLoadListener;
if (threshold > 0) {
@ -26,6 +44,8 @@ public final class RecyclerLazyLoader extends RecyclerView.OnScrollListener {
}
if (layoutManager instanceof GridLayoutManager) {
this.visibleThreshold = 5 * Math.max(3, ((GridLayoutManager) layoutManager).getSpanCount());
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
this.visibleThreshold = 4 * Math.max(3, ((StaggeredGridLayoutManager) layoutManager).getSpanCount());
} else if (layoutManager instanceof LinearLayoutManager) {
this.visibleThreshold = ((LinearLayoutManager) layoutManager).getReverseLayout() ? 4 : 8;
} else {
@ -33,7 +53,8 @@ public final class RecyclerLazyLoader extends RecyclerView.OnScrollListener {
}
}
public RecyclerLazyLoader(@NonNull final RecyclerView.LayoutManager layoutManager, final LazyLoadListener lazyLoadListener) {
public RecyclerLazyLoader(@NonNull final RecyclerView.LayoutManager layoutManager,
final LazyLoadListener lazyLoadListener) {
this(layoutManager, lazyLoadListener, -1);
}
@ -52,22 +73,37 @@ public final class RecyclerLazyLoader extends RecyclerView.OnScrollListener {
previousTotalItemCount = totalItemCount;
}
final int lastVisibleItemPosition;
int lastVisibleItemPosition;
if (layoutManager instanceof GridLayoutManager) {
final GridLayoutManager layoutManager = (GridLayoutManager) this.layoutManager;
lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
final StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) this.layoutManager;
final int spanCount = layoutManager.getSpanCount();
final int[] lastVisibleItemPositions = layoutManager.findLastVisibleItemPositions(null);
lastVisibleItemPosition = 0;
for (final int itemPosition : lastVisibleItemPositions) {
if (itemPosition > lastVisibleItemPosition) {
lastVisibleItemPosition = itemPosition;
}
}
} else {
final LinearLayoutManager layoutManager = (LinearLayoutManager) this.layoutManager;
lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
}
if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) {
if (lazyLoadListener != null)
lazyLoadListener.onLoadMore(++currentPage, totalItemCount);
loading = true;
if (lazyLoadListener != null) {
new Handler().postDelayed(() -> lazyLoadListener.onLoadMore(++currentPage, totalItemCount), 200);
}
}
}
public int getCurrentPage() {
return currentPage;
}
public void resetState() {
this.currentPage = 0;
this.previousTotalItemCount = 0;

View File

@ -0,0 +1,51 @@
package awais.instagrabber.customviews.helpers;
import android.os.Handler;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
public final class RecyclerLazyLoaderAtBottom extends RecyclerView.OnScrollListener {
@NonNull
private final RecyclerView.LayoutManager layoutManager;
private final LazyLoadListener lazyLoadListener;
private int currentPage;
private int previousItemCount;
private boolean loading;
public RecyclerLazyLoaderAtBottom(@NonNull final RecyclerView.LayoutManager layoutManager,
final LazyLoadListener lazyLoadListener) {
this.layoutManager = layoutManager;
this.lazyLoadListener = lazyLoadListener;
}
@Override
public void onScrollStateChanged(@NonNull final RecyclerView recyclerView, final int newState) {
super.onScrollStateChanged(recyclerView, newState);
final int itemCount = layoutManager.getItemCount();
if (itemCount > previousItemCount) {
loading = false;
}
if (!recyclerView.canScrollVertically(RecyclerView.SCROLL_AXIS_HORIZONTAL) && newState == RecyclerView.SCROLL_STATE_IDLE) {
if (!loading && lazyLoadListener != null) {
loading = true;
new Handler().postDelayed(() -> lazyLoadListener.onLoadMore(++currentPage), 500);
}
}
}
public int getCurrentPage() {
return currentPage;
}
public void resetState() {
currentPage = 0;
previousItemCount = 0;
loading = true;
}
public interface LazyLoadListener {
void onLoadMore(final int page);
}
}

View File

@ -70,7 +70,7 @@ public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
final FeedVideoViewHolder videoHolder = getFirstVideoHolder(recyclerView, firstVisibleItemPos, lastVisibleItemPos);
if (videoHolder == null || videoHolder.getCurrentFeedModel() == null) {
if (currentlyPlayingViewHolder != null) {
currentlyPlayingViewHolder.stopPlaying();
// currentlyPlayingViewHolder.stopPlaying();
currentlyPlayingViewHolder = null;
}
return;
@ -80,9 +80,9 @@ public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
return;
}
if (currentlyPlayingViewHolder != null) {
currentlyPlayingViewHolder.stopPlaying();
// currentlyPlayingViewHolder.stopPlaying();
}
videoHolder.startPlaying();
// videoHolder.startPlaying();
currentlyPlayingViewHolder = videoHolder;
}
// boolean processFirstItem = false, processLastItem = false;
@ -196,7 +196,7 @@ public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
for (int pos = firstVisibleItemPos; pos <= lastVisibleItemPos; pos++) {
final View view = layoutManager.findViewByPosition(pos);
if (view != null && view.getId() == R.id.videoHolder) {
final View viewSwitcher = view.findViewById(R.id.view_switcher);
final View viewSwitcher = view.findViewById(R.id.root);
if (viewSwitcher == null) {
continue;
}
@ -220,113 +220,113 @@ public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
if (currentlyPlayingViewHolder == null) {
return;
}
currentlyPlayingViewHolder.startPlaying();
// currentlyPlayingViewHolder.startPlaying();
}
public void stopPlaying() {
if (currentlyPlayingViewHolder == null) {
return;
}
currentlyPlayingViewHolder.stopPlaying();
// currentlyPlayingViewHolder.stopPlaying();
}
// private synchronized void attachVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// synchronized (LOCK) {
// if (recyclerView != null) {
// final RecyclerView.Adapter<?> adapter = recyclerView.getAdapter();
// if (adapter instanceof FeedAdapter) {
// final SimpleExoPlayer pagerPlayer = ((FeedAdapter) adapter).pagerPlayer;
// if (pagerPlayer != null) pagerPlayer.setPlayWhenReady(false);
// }
// }
// if (itemView == null) {
// return;
// }
// final boolean shouldAutoplay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
// final FeedModel feedModel = feedModels.get(itemPos);
// // loadVideo(itemPos, itemView, shouldAutoplay, feedModel);
// }
// }
//
// private void loadVideo(final int itemPos, final View itemView, final boolean shouldAutoplay, final FeedModel feedModel) {
// final PlayerView playerView = itemView.findViewById(R.id.playerView);
// if (playerView == null) {
// return;
// }
// if (player != null) {
// player.stop(true);
// player.release();
// player = null;
// }
//
// player = new SimpleExoPlayer.Builder(context)
// .setUseLazyPreparation(!shouldAutoplay)
// .build();
// player.setPlayWhenReady(shouldAutoplay);
//
// final View btnComments = itemView.findViewById(R.id.btnComments);
// if (btnComments != null) {
// if (feedModel.getCommentsCount() <= 0) btnComments.setEnabled(false);
// else {
// btnComments.setTag(feedModel);
// btnComments.setEnabled(true);
// btnComments.setOnClickListener(commentClickListener);
// }
// }
// playerView.setPlayer(player);
// btnMute = itemView.findViewById(R.id.btnMute);
// float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
// if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
// player.setVolume(vol);
//
// if (btnMute != null) {
// btnMute.setVisibility(View.VISIBLE);
// btnMute.setImageResource(vol == 0f ? R.drawable.vol : R.drawable.mute);
// btnMute.setOnClickListener(muteClickListener);
// }
// final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
// final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
// final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(Uri.parse(feedModel.getDisplayUrl()));
//
// player.setRepeatMode(Player.REPEAT_MODE_ALL);
// player.prepare(mediaSource);
// player.setVolume(vol);
//
// playerView.setOnClickListener(v -> player.setPlayWhenReady(!player.getPlayWhenReady()));
//
// if (videoChangeCallback != null) videoChangeCallback.playerChanged(itemPos, player);
// }
//
// private void releaseVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// // Log.d("AWAISKING_APP", "release: " + itemPos);
// // if (player != null) {
// // player.stop(true);
// // player.release();
// // }
// // player = null;
// }
//
// private void playVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// // if (player != null) {
// // final int playbackState = player.getPlaybackState();
// // if (!player.isPlaying()
// // || playbackState == Player.STATE_READY || playbackState == Player.STATE_ENDED
// // ) {
// // player.setPlayWhenReady(true);
// // }
// // }
// // if (player != null) {
// // player.setPlayWhenReady(true);
// // player.getPlaybackState();
// // }
// }
//
// private void stopVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// if (player != null) {
// player.setPlayWhenReady(false);
// player.getPlaybackState();
// }
// }
// private synchronized void attachVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// synchronized (LOCK) {
// if (recyclerView != null) {
// final RecyclerView.Adapter<?> adapter = recyclerView.getAdapter();
// if (adapter instanceof FeedAdapter) {
// final SimpleExoPlayer pagerPlayer = ((FeedAdapter) adapter).pagerPlayer;
// if (pagerPlayer != null) pagerPlayer.setPlayWhenReady(false);
// }
// }
// if (itemView == null) {
// return;
// }
// final boolean shouldAutoplay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
// final FeedModel feedModel = feedModels.get(itemPos);
// // loadVideo(itemPos, itemView, shouldAutoplay, feedModel);
// }
// }
//
// private void loadVideo(final int itemPos, final View itemView, final boolean shouldAutoplay, final FeedModel feedModel) {
// final PlayerView playerView = itemView.findViewById(R.id.playerView);
// if (playerView == null) {
// return;
// }
// if (player != null) {
// player.stop(true);
// player.release();
// player = null;
// }
//
// player = new SimpleExoPlayer.Builder(context)
// .setUseLazyPreparation(!shouldAutoplay)
// .build();
// player.setPlayWhenReady(shouldAutoplay);
//
// final View btnComments = itemView.findViewById(R.id.btnComments);
// if (btnComments != null) {
// if (feedModel.getCommentsCount() <= 0) btnComments.setEnabled(false);
// else {
// btnComments.setTag(feedModel);
// btnComments.setEnabled(true);
// btnComments.setOnClickListener(commentClickListener);
// }
// }
// playerView.setPlayer(player);
// btnMute = itemView.findViewById(R.id.btnMute);
// float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
// if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
// player.setVolume(vol);
//
// if (btnMute != null) {
// btnMute.setVisibility(View.VISIBLE);
// btnMute.setImageResource(vol == 0f ? R.drawable.vol : R.drawable.mute);
// btnMute.setOnClickListener(muteClickListener);
// }
// final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
// final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
// final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(Uri.parse(feedModel.getDisplayUrl()));
//
// player.setRepeatMode(Player.REPEAT_MODE_ALL);
// player.prepare(mediaSource);
// player.setVolume(vol);
//
// playerView.setOnClickListener(v -> player.setPlayWhenReady(!player.getPlayWhenReady()));
//
// if (videoChangeCallback != null) videoChangeCallback.playerChanged(itemPos, player);
// }
//
// private void releaseVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// // Log.d("AWAISKING_APP", "release: " + itemPos);
// // if (player != null) {
// // player.stop(true);
// // player.release();
// // }
// // player = null;
// }
//
// private void playVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// // if (player != null) {
// // final int playbackState = player.getPlaybackState();
// // if (!player.isPlaying()
// // || playbackState == Player.STATE_READY || playbackState == Player.STATE_ENDED
// // ) {
// // player.setPlayWhenReady(true);
// // }
// // }
// // if (player != null) {
// // player.setPlayWhenReady(true);
// // player.getPlaybackState();
// // }
// }
//
// private void stopVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// if (player != null) {
// player.setPlayWhenReady(false);
// player.getPlaybackState();
// }
// }
public interface VideoChangeCallback {
void playerChanged(final int itemPos, final SimpleExoPlayer player);

View File

@ -0,0 +1,216 @@
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.Window;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import awais.instagrabber.R;
import awais.instagrabber.databinding.DialogPostLayoutPreferencesBinding;
import awais.instagrabber.models.PostsLayoutPreferences;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class PostsLayoutPreferencesDialogFragment extends DialogFragment {
private final OnApplyListener onApplyListener;
private final PostsLayoutPreferences.Builder preferencesBuilder;
private final String layoutPreferenceKey;
private DialogPostLayoutPreferencesBinding binding;
private Context context;
public PostsLayoutPreferencesDialogFragment(final String layoutPreferenceKey,
@NonNull final OnApplyListener onApplyListener) {
this.layoutPreferenceKey = layoutPreferenceKey;
final PostsLayoutPreferences preferences = PostsLayoutPreferences.fromJson(settingsHelper.getString(layoutPreferenceKey));
this.preferencesBuilder = PostsLayoutPreferences.builder().mergeFrom(preferences);
this.onApplyListener = onApplyListener;
}
@Override
public void onAttach(@NonNull final Context context) {
super.onAttach(context);
this.context = context;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
binding = DialogPostLayoutPreferencesBinding.inflate(LayoutInflater.from(context), null, false);
init();
return new MaterialAlertDialogBuilder(context)
.setView(binding.getRoot())
.setPositiveButton(R.string.apply, (dialog, which) -> {
final PostsLayoutPreferences preferences = preferencesBuilder.build();
final String json = preferences.getJson();
settingsHelper.putString(layoutPreferenceKey, json);
onApplyListener.onApply(preferences);
})
.create();
}
@Override
public void onStart() {
super.onStart();
final Dialog dialog = getDialog();
if (dialog == null) return;
final Window window = dialog.getWindow();
if (window == null) return;
window.setWindowAnimations(R.style.dialog_window_animation);
}
private void init() {
initLayoutToggle();
if (preferencesBuilder.getType() != PostsLayoutPreferences.PostsLayoutType.LINEAR) {
initStaggeredOrGridOptions();
}
}
private void initStaggeredOrGridOptions() {
initColCountToggle();
initNamesToggle();
initAvatarsToggle();
initCornersToggle();
initGapToggle();
}
private void initLayoutToggle() {
final int selectedLayoutId = getSelectedLayoutId();
binding.layoutToggle.check(selectedLayoutId);
if (selectedLayoutId == R.id.layout_linear) {
binding.staggeredOrGridOptions.setVisibility(View.GONE);
}
binding.layoutToggle.addOnButtonCheckedListener((group, checkedId, isChecked) -> {
if (isChecked) {
if (checkedId == R.id.layout_linear) {
preferencesBuilder.setType(PostsLayoutPreferences.PostsLayoutType.LINEAR);
preferencesBuilder.setColCount(1);
binding.staggeredOrGridOptions.setVisibility(View.GONE);
} else if (checkedId == R.id.layout_staggered) {
preferencesBuilder.setType(PostsLayoutPreferences.PostsLayoutType.STAGGERED_GRID);
if (preferencesBuilder.getColCount() == 1) {
preferencesBuilder.setColCount(2);
}
binding.staggeredOrGridOptions.setVisibility(View.VISIBLE);
initStaggeredOrGridOptions();
} else {
preferencesBuilder.setType(PostsLayoutPreferences.PostsLayoutType.GRID);
if (preferencesBuilder.getColCount() == 1) {
preferencesBuilder.setColCount(2);
}
binding.staggeredOrGridOptions.setVisibility(View.VISIBLE);
initStaggeredOrGridOptions();
}
}
});
}
private void initColCountToggle() {
binding.colCountToggle.check(getSelectedColCountId());
binding.colCountToggle.addOnButtonCheckedListener((group, checkedId, isChecked) -> {
if (!isChecked) return;
if (checkedId == R.id.col_count_two) {
preferencesBuilder.setColCount(2);
} else {
preferencesBuilder.setColCount(3);
}
});
}
private void initAvatarsToggle() {
binding.showAvatarToggle.setChecked(preferencesBuilder.isAvatarVisible());
binding.avatarSizeToggle.check(getSelectedAvatarSizeId());
binding.showAvatarToggle.setOnCheckedChangeListener((buttonView, isChecked) -> {
preferencesBuilder.setAvatarVisible(isChecked);
binding.labelAvatarSize.setVisibility(isChecked ? View.VISIBLE : View.GONE);
binding.avatarSizeToggle.setVisibility(isChecked ? View.VISIBLE : View.GONE);
});
binding.labelAvatarSize.setVisibility(preferencesBuilder.isAvatarVisible() ? View.VISIBLE : View.GONE);
binding.avatarSizeToggle.setVisibility(preferencesBuilder.isAvatarVisible() ? View.VISIBLE : View.GONE);
binding.avatarSizeToggle.addOnButtonCheckedListener((group, checkedId, isChecked) -> {
if (!isChecked) return;
if (checkedId == R.id.avatar_size_tiny) {
preferencesBuilder.setProfilePicSize(PostsLayoutPreferences.ProfilePicSize.TINY);
} else if (checkedId == R.id.avatar_size_small) {
preferencesBuilder.setProfilePicSize(PostsLayoutPreferences.ProfilePicSize.SMALL);
} else {
preferencesBuilder.setProfilePicSize(PostsLayoutPreferences.ProfilePicSize.REGULAR);
}
});
}
private void initNamesToggle() {
binding.showNamesToggle.setChecked(preferencesBuilder.isNameVisible());
binding.showNamesToggle.setOnCheckedChangeListener((buttonView, isChecked) -> preferencesBuilder.setNameVisible(isChecked));
}
private void initCornersToggle() {
binding.cornersToggle.check(getSelectedCornersId());
binding.cornersToggle.addOnButtonCheckedListener((group, checkedId, isChecked) -> {
if (!isChecked) return;
if (checkedId == R.id.corners_round) {
preferencesBuilder.setHasRoundedCorners(true);
return;
}
preferencesBuilder.setHasRoundedCorners(false);
});
}
private void initGapToggle() {
binding.showGapToggle.setChecked(preferencesBuilder.getHasGap());
binding.showGapToggle.setOnCheckedChangeListener((buttonView, isChecked) -> preferencesBuilder.setHasGap(isChecked));
}
private int getSelectedLayoutId() {
switch (preferencesBuilder.getType()) {
case STAGGERED_GRID:
return R.id.layout_staggered;
case LINEAR:
return R.id.layout_linear;
default:
case GRID:
return R.id.layout_grid;
}
}
private int getSelectedColCountId() {
switch (preferencesBuilder.getColCount()) {
case 2:
return R.id.col_count_two;
case 3:
default:
return R.id.col_count_three;
}
}
private int getSelectedCornersId() {
if (preferencesBuilder.getHasRoundedCorners()) {
return R.id.corners_round;
}
return R.id.corners_square;
}
private int getSelectedAvatarSizeId() {
switch (preferencesBuilder.getProfilePicSize()) {
case TINY:
return R.id.avatar_size_tiny;
case SMALL:
return R.id.avatar_size_small;
case REGULAR:
default:
return R.id.avatar_size_regular;
}
}
public interface OnApplyListener {
void onApply(final PostsLayoutPreferences preferences);
}
}

View File

@ -28,7 +28,6 @@ import com.facebook.imagepipeline.image.ImageInfo;
import java.io.File;
import awais.instagrabber.R;
import awais.instagrabber.asyncs.DownloadAsync;
import awais.instagrabber.asyncs.ProfilePictureFetcher;
import awais.instagrabber.databinding.DialogProfilepicBinding;
import awais.instagrabber.interfaces.FetchListener;
@ -147,54 +146,9 @@ public class ProfilePicDialogFragment extends DialogFragment {
if (context == null) return;
if (dir.exists() || dir.mkdirs()) {
final File saveFile = new File(dir, name + '_' + System.currentTimeMillis() + ".jpg");
new DownloadAsync(context,
url,
saveFile,
result -> {
final int toastRes = result != null && result.exists() ?
R.string.downloader_downloaded_in_folder : R.string.downloader_error_download_file;
Toast.makeText(context, toastRes, Toast.LENGTH_SHORT).show();
})
.setItems(null, name)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
DownloadUtils.download(context, url, saveFile.getAbsolutePath());
return;
}
Toast.makeText(context, R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show();
}
// private void showImageInfo() {
// final Drawable drawable = profileBinding.imageViewer.getDrawable();
// if (drawable != null) {
// final StringBuilder info = new StringBuilder(
// getString(R.string.profile_viewer_imageinfo, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()));
// if (drawable instanceof BitmapDrawable) {
// final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
// if (bitmap != null) {
// final String colorDepthPrefix = getString(R.string.profile_viewer_colordepth_prefix);
// switch (bitmap.getConfig()) {
// case ALPHA_8:
// info.append(colorDepthPrefix).append(" 8-bits(A)");
// break;
// case RGB_565:
// info.append(colorDepthPrefix).append(" 16-bits-A");
// break;
// case ARGB_4444:
// info.append(colorDepthPrefix).append(" 16-bits+A");
// break;
// case ARGB_8888:
// info.append(colorDepthPrefix).append(" 32-bits+A");
// break;
// case RGBA_F16:
// info.append(colorDepthPrefix).append(" 64-bits+A");
// break;
// case HARDWARE:
// info.append(colorDepthPrefix).append(" auto");
// break;
// }
// }
// }
// profileBinding.imageInfo.setText(info);
// profileBinding.imageInfo.setVisibility(View.VISIBLE);
// }
// }
}

View File

@ -1,146 +0,0 @@
package awais.instagrabber.directdownload;
import android.Manifest;
import android.app.Activity;
import android.app.Notification;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import java.util.Arrays;
import awais.instagrabber.R;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.IntentModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.IntentModelType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.IntentUtils;
import awais.instagrabber.utils.TextUtils;
public final class DirectDownload extends Activity {
private boolean isFound = false;
private Intent intent;
private Context context;
private NotificationManagerCompat notificationManager;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
notificationManager = NotificationManagerCompat.from(getApplicationContext());
}
@Override
public void onWindowAttributesChanged(final WindowManager.LayoutParams params) {
super.onWindowAttributesChanged(params);
if (!isFound) {
intent = getIntent();
context = getApplicationContext();
if (intent != null && context != null) {
isFound = true;
checkIntent();
}
}
}
@Override
public Resources getResources() {
if (!isFound) {
intent = getIntent();
context = getApplicationContext();
if (intent != null && context != null) {
isFound = true;
checkIntent();
}
}
return super.getResources();
}
private synchronized void checkIntent() {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
doDownload();
else {
final Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(context, R.string.direct_download_perms_ask, Toast.LENGTH_LONG).show();
handler.removeCallbacks(this);
}
});
ActivityCompat.requestPermissions(this, DownloadUtils.PERMS, 8020);
}
finish();
}
private synchronized void doDownload() {
final String action = intent.getAction();
if (TextUtils.isEmpty(action) || Intent.ACTION_MAIN.equals(action)) return;
boolean error = true;
String data = null;
final Bundle extras = intent.getExtras();
if (extras != null) {
final Object extraData = extras.get(Intent.EXTRA_TEXT);
if (extraData != null) {
error = false;
data = extraData.toString();
}
}
if (error) {
final Uri intentData = intent.getData();
if (intentData != null) data = intentData.toString();
}
if (data != null && !TextUtils.isEmpty(data)) {
final IntentModel model = IntentUtils.parseUrl(data);
if (model != null && model.getType() == IntentModelType.POST) {
final String text = model.getText();
new PostFetcher(text, new FetchListener<ViewerPostModel[]>() {
@Override
public void doBefore() {
final Notification fetchingPostNotif = new NotificationCompat.Builder(context, Constants.DOWNLOAD_CHANNEL_ID)
.setCategory(NotificationCompat.CATEGORY_STATUS).setSmallIcon(R.mipmap.ic_launcher)
.setAutoCancel(false).setPriority(NotificationCompat.PRIORITY_MIN)
.setContentText(context.getString(R.string.direct_download_loading)).build();
notificationManager.notify(1900000000, fetchingPostNotif);
}
@Override
public void onResult(final ViewerPostModel[] result) {
if (notificationManager != null) notificationManager.cancel(1900000000);
if (result != null) {
if (result.length == 1) {
DownloadUtils.batchDownload(context, result[0].getProfileModel().getUsername(), DownloadMethod.DOWNLOAD_DIRECT,
Arrays.asList(result));
} else if (result.length > 1) {
context.startActivity(new Intent(context, MultiDirectDialog.class)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
.putExtra(Constants.EXTRAS_POST, result));
}
}
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
}
}

View File

@ -1,118 +0,0 @@
package awais.instagrabber.directdownload;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import awais.instagrabber.R;
import awais.instagrabber.activities.BaseLanguageActivity;
import awais.instagrabber.adapters.PostsAdapter;
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.Utils;
public final class MultiDirectDialog extends BaseLanguageActivity {
public final ArrayList<BasePostModel> selectedItems = new ArrayList<>();
private PostsAdapter postsAdapter;
private MenuItem btnDownload;
private String username = null;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_direct);
final Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
final ViewerPostModel[] postModels;
final Intent intent = getIntent();
if (intent == null || !intent.hasExtra(Constants.EXTRAS_POST)
|| (postModels = (ViewerPostModel[]) intent.getSerializableExtra(Constants.EXTRAS_POST)) == null) {
Utils.errorFinish(this);
return;
}
username = postModels[0].getProfileModel().getUsername();
toolbar.setTitle(username);
toolbar.setSubtitle(postModels[0].getShortCode());
final RecyclerView recyclerView = findViewById(R.id.mainPosts);
recyclerView.setNestedScrollingEnabled(false);
recyclerView.setLayoutManager(new GridAutofitLayoutManager(this, Utils.convertDpToPx(130)));
recyclerView.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
final ArrayList<PostModel> models = new ArrayList<>(postModels.length - 1);
for (final ViewerPostModel postModel : postModels)
models.add(new PostModel(postModel.getItemType(), postModel.getPostId(), postModel.getDisplayUrl(),
postModel.getSliderDisplayUrl(), postModel.getShortCode(), postModel.getPostCaption(), postModel.getTimestamp(),
postModel.getLike(), postModel.getBookmark(), postModel.getLikes()));
// postsAdapter = new PostsAdapter(models, v -> {
// final Object tag = v.getTag();
// if (tag instanceof PostModel) {
// final PostModel postModel = (PostModel) tag;
// if (postsAdapter.isSelecting) toggleSelection(postModel);
// else {
// Utils.batchDownload(this, username, DownloadMethod.DOWNLOAD_DIRECT, Collections.singletonList(postModel));
// finish();
// }
// }
// }, v -> {
// final Object tag = v.getTag();
// if (tag instanceof PostModel) {
// postsAdapter.isSelecting = true;
// toggleSelection((PostModel) tag);
// }
// return true;
// });
recyclerView.setAdapter(postsAdapter);
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
DownloadUtils.batchDownload(this, username, DownloadMethod.DOWNLOAD_DIRECT, selectedItems);
finish();
return true;
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
btnDownload = menu.findItem(R.id.action_download);
menu.findItem(R.id.action_search).setVisible(false);
return true;
}
private void toggleSelection(final PostModel postModel) {
if (postModel != null && postsAdapter != null) {
if (postModel.isSelected()) selectedItems.remove(postModel);
else selectedItems.add(postModel);
postModel.setSelected(!postModel.isSelected());
notifyAdapter(postModel);
}
}
private void notifyAdapter(final PostModel postModel) {
// if (selectedItems.size() < 1) postsAdapter.isSelecting = false;
// if (postModel.getPosition() < 0) postsAdapter.notifyDataSetChanged();
// else postsAdapter.notifyItemChanged(postModel.getPosition(), postModel);
//
// if (btnDownload != null) btnDownload.setVisible(postsAdapter.isSelecting);
}
}

View File

@ -12,69 +12,129 @@ import android.text.TextWatcher;
import android.text.style.RelativeSizeSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.Fragment;
import androidx.appcompat.widget.LinearLayoutCompat;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import awais.instagrabber.R;
import awais.instagrabber.adapters.CommentsAdapter;
import awais.instagrabber.asyncs.CommentsFetcher;
import awais.instagrabber.databinding.FragmentCommentsBinding;
import awais.instagrabber.dialogs.ProfilePicDialogFragment;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.CommentModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.CommentsViewModel;
import awais.instagrabber.webservices.MediaService;
import awais.instagrabber.webservices.ServiceCallback;
import static android.content.Context.INPUT_METHOD_SERVICE;
public final class CommentsViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
public final class CommentsViewerFragment extends BottomSheetDialogFragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "CommentsViewerFragment";
private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
private CommentsAdapter commentsAdapter;
private CommentModel commentModel;
private FragmentCommentsBinding binding;
private String shortCode;
private String userId;
private Resources resources;
private InputMethodManager imm;
private AppCompatActivity fragmentActivity;
private LinearLayout root;
private LinearLayoutCompat root;
private boolean shouldRefresh = true;
private MediaService mediaService;
private String postId;
private CommentsViewModel commentsViewModel;
private final CommentsAdapter.CommentCallback commentCallback = new CommentsAdapter.CommentCallback() {
@Override
public void onClick(final CommentModel comment) {
onCommentClick(comment);
}
@Override
public void onHashtagClick(final String hashtag) {
final NavDirections action = CommentsViewerFragmentDirections.actionGlobalHashTagFragment(hashtag);
NavHostFragment.findNavController(CommentsViewerFragment.this).navigate(action);
}
@Override
public void onMentionClick(final String mention) {
openProfile(mention);
}
@Override
public void onURLClick(final String url) {
Utils.openURL(getContext(), url);
}
@Override
public void onEmailClick(final String emailAddress) {
Utils.openEmailAddress(getContext(), emailAddress);
}
};
private final View.OnClickListener newCommentListener = v -> {
final Editable text = binding.commentText.getText();
final Context context = getContext();
if (context == null) return;
if (text == null || TextUtils.isEmpty(text.toString())) {
Toast.makeText(context, R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show();
return;
}
final String userId = CookieUtils.getUserIdFromCookie(cookie);
if (userId == null) return;
String replyToId = null;
final CommentModel commentModel = commentsAdapter.getSelected();
if (commentModel != null) {
replyToId = commentModel.getId();
}
mediaService.comment(postId, text.toString(), userId, replyToId, CookieUtils.getCsrfTokenFromCookie(cookie), new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
commentsAdapter.clearSelection();
binding.commentText.setText("");
if (!result) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
onRefresh();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error during comment", t);
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (AppCompatActivity) getActivity();
mediaService = MediaService.getInstance();
setHasOptionsMenu(true);
// setHasOptionsMenu(true);
}
@NonNull
@ -85,6 +145,8 @@ public final class CommentsViewerFragment extends Fragment implements SwipeRefre
return root;
}
binding = FragmentCommentsBinding.inflate(getLayoutInflater());
binding.swipeRefreshLayout.setEnabled(false);
binding.swipeRefreshLayout.setNestedScrollingEnabled(false);
root = binding.getRoot();
return root;
}
@ -96,34 +158,33 @@ public final class CommentsViewerFragment extends Fragment implements SwipeRefre
shouldRefresh = false;
}
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.follow, menu);
menu.findItem(R.id.action_compare).setVisible(false);
final MenuItem menuSearch = menu.findItem(R.id.action_search);
final SearchView searchView = (SearchView) menuSearch.getActionView();
searchView.setQueryHint(getResources().getString(R.string.action_search));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(final String query) {
return false;
}
@Override
public boolean onQueryTextChange(final String query) {
if (commentsAdapter != null) commentsAdapter.getFilter().filter(query);
return true;
}
});
}
// @Override
// public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
// inflater.inflate(R.menu.follow, menu);
// menu.findItem(R.id.action_compare).setVisible(false);
// final MenuItem menuSearch = menu.findItem(R.id.action_search);
// final SearchView searchView = (SearchView) menuSearch.getActionView();
// searchView.setQueryHint(getResources().getString(R.string.action_search));
// searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
// @Override
// public boolean onQueryTextSubmit(final String query) {
// return false;
// }
//
// @Override
// public boolean onQueryTextChange(final String query) {
// // if (commentsAdapter != null) commentsAdapter.getFilter().filter(query);
// return true;
// }
// });
// }
@Override
public void onRefresh() {
binding.swipeRefreshLayout.setRefreshing(true);
new CommentsFetcher(shortCode, commentModels -> {
commentsViewModel.getList().postValue(commentModels);
binding.swipeRefreshLayout.setRefreshing(false);
commentsAdapter = new CommentsAdapter(commentModels, true, clickListener, mentionClickListener);
binding.rvComments.setAdapter(commentsAdapter);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@ -133,9 +194,14 @@ public final class CommentsViewerFragment extends Fragment implements SwipeRefre
shortCode = fragmentArgs.getShortCode();
postId = fragmentArgs.getPostId();
userId = fragmentArgs.getPostUserId();
setTitle();
// setTitle();
binding.swipeRefreshLayout.setOnRefreshListener(this);
binding.swipeRefreshLayout.setRefreshing(true);
commentsViewModel = new ViewModelProvider(this).get(CommentsViewModel.class);
binding.rvComments.setLayoutManager(new LinearLayoutManager(getContext()));
commentsAdapter = new CommentsAdapter(commentCallback);
binding.rvComments.setAdapter(commentsAdapter);
commentsViewModel.getList().observe(getViewLifecycleOwner(), commentsAdapter::submitList);
resources = getResources();
if (!TextUtils.isEmpty(cookie)) {
binding.commentField.setStartIconVisible(false);
@ -155,121 +221,107 @@ public final class CommentsViewerFragment extends Fragment implements SwipeRefre
public void afterTextChanged(final Editable s) {}
});
binding.commentField.setStartIconOnClickListener(v -> {
if (commentModel != null) {
final View focus = binding.rvComments.findViewWithTag(commentModel);
focus.setBackgroundColor(0x00000000);
commentModel = null;
}
commentsAdapter.clearSelection();
binding.commentText.setText("");
});
binding.commentField.setEndIconOnClickListener(newCommentListener);
}
new CommentsFetcher(this.shortCode, commentModels -> {
commentsAdapter = new CommentsAdapter(commentModels, true, clickListener, mentionClickListener);
binding.rvComments.setAdapter(commentsAdapter);
binding.swipeRefreshLayout.setRefreshing(false);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
onRefresh();
}
private void setTitle() {
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar == null) return;
actionBar.setTitle(R.string.title_comments);
// private void setTitle() {
// final ActionBar actionBar = fragmentActivity.getSupportActionBar();
// if (actionBar == null) return;
// actionBar.setTitle(R.string.title_comments);
// actionBar.setSubtitle(shortCode);
}
// }
final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> {
private void onCommentClick(final CommentModel commentModel) {
final String username = commentModel.getProfileModel().getUsername();
final SpannableString title = new SpannableString(username + ":\n" + commentModel.getText());
title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
String[] commentDialogList;
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
if (!TextUtils.isEmpty(cookie)
&& userIdFromCookie != null
&& (userIdFromCookie.equals(commentModel.getProfileModel().getId()) || userIdFromCookie.equals(userId))) {
commentDialogList = new String[]{
resources.getString(R.string.open_profile),
resources.getString(R.string.view_pfp),
resources.getString(R.string.comment_viewer_copy_user),
// resources.getString(R.string.comment_viewer_copy_comment),
resources.getString(R.string.comment_viewer_reply_comment),
commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_comment)
: resources.getString(R.string.comment_viewer_like_comment),
resources.getString(R.string.comment_viewer_delete_comment)
};
} else if (!TextUtils.isEmpty(cookie)) {
commentDialogList = new String[]{
resources.getString(R.string.open_profile),
resources.getString(R.string.view_pfp),
resources.getString(R.string.comment_viewer_copy_user),
// resources.getString(R.string.comment_viewer_copy_comment),
resources.getString(R.string.comment_viewer_reply_comment),
commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_comment)
: resources.getString(R.string.comment_viewer_like_comment),
};
} else {
commentDialogList = new String[]{
resources.getString(R.string.open_profile),
resources.getString(R.string.view_pfp),
resources.getString(R.string.comment_viewer_copy_user),
// resources.getString(R.string.comment_viewer_copy_comment)
};
}
final Context context = getContext();
if (context == null) return;
if (commentModel == null) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
final ProfileModel profileModel = commentModel.getProfileModel();
switch (which) {
case 0: // open profile
openProfile(profileModel.getUsername());
break;
case 1: // view profile pic
final FragmentManager fragmentManager = getParentFragmentManager();
final ProfilePicDialogFragment fragment = new ProfilePicDialogFragment(profileModel.getId(),
profileModel.getUsername(),
profileModel.getHdProfilePic());
final FragmentTransaction ft = fragmentManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.add(fragment, "profilePicDialog")
.commit();
break;
case 2: // copy username
Utils.copyText(context, profileModel.getUsername());
break;
case 3: // copy comment
Utils.copyText(context, commentModel.getText().toString());
break;
case 4: // reply to comment
final View focus = binding.rvComments.findViewWithTag(commentModel);
focus.setBackgroundColor(0x80888888);
String mention = "@" + profileModel.getUsername() + " ";
binding.commentText.setText(mention);
binding.commentText.requestFocus();
binding.commentText.setSelection(mention.length());
binding.commentText.postDelayed(new Runnable() {
@Override
public void run() {
final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> {
final ProfileModel profileModel = commentModel.getProfileModel();
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
switch (which) {
case 0: // open profile
openProfile("@" + profileModel.getUsername());
break;
case 1: // view profile pic
final FragmentManager fragmentManager = getParentFragmentManager();
final ProfilePicDialogFragment fragment = new ProfilePicDialogFragment(profileModel.getId(),
profileModel.getUsername(),
profileModel.getHdProfilePic());
final FragmentTransaction ft = fragmentManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.add(fragment, "profilePicDialog")
.commit();
break;
case 2: // copy username
Utils.copyText(context, profileModel.getUsername());
break;
// case 3: // copy comment
// Utils.copyText(context, commentModel.getText().toString());
// break;
case 3: // reply to comment
// final View focus = binding.rvComments.findViewWithTag(commentModel);
// focus.setBackgroundColor(0x80888888);
commentsAdapter.setSelected(commentModel);
String mention = "@" + profileModel.getUsername() + " ";
binding.commentText.setText(mention);
binding.commentText.requestFocus();
binding.commentText.setSelection(mention.length());
binding.commentText.postDelayed(() -> {
imm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE);
if (imm == null) return;
imm.showSoftInput(binding.commentText, 0);
}, 200);
break;
case 4: // like/unlike comment
if (csrfToken == null) {
return;
}
}, 200);
break;
case 5: // like/unlike comment
if (!commentModel.getLiked()) {
mediaService.commentLike(commentModel.getId(), CookieUtils.getCsrfTokenFromCookie(cookie), new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
commentModel = null;
if (!result) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
onRefresh();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error liking comment", t);
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
return;
}
mediaService.commentUnlike(commentModel.getId(), CookieUtils.getCsrfTokenFromCookie(cookie), new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
commentModel = null;
if (!result) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
onRefresh();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error unliking comment", t);
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
break;
case 6: // delete comment
final String userId = CookieUtils.getUserIdFromCookie(cookie);
if (userId == null) return;
mediaService.deleteComment(
postId, userId, commentModel.getId(), CookieUtils.getCsrfTokenFromCookie(cookie),
new ServiceCallback<Boolean>() {
if (!commentModel.getLiked()) {
mediaService.commentLike(commentModel.getId(), csrfToken, new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
commentModel = null;
if (!result) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
@ -279,112 +331,62 @@ public final class CommentsViewerFragment extends Fragment implements SwipeRefre
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error deleting comment", t);
Log.e(TAG, "Error liking comment", t);
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
break;
}
};
return;
}
mediaService.commentUnlike(commentModel.getId(), csrfToken, new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
if (!result) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
onRefresh();
}
private final View.OnClickListener clickListener = v -> {
final Object tag = v.getTag();
if (tag instanceof CommentModel) {
commentModel = (CommentModel) tag;
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error unliking comment", t);
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
break;
case 5: // delete comment
final String userId = CookieUtils.getUserIdFromCookie(cookie);
if (userId == null) return;
mediaService.deleteComment(
postId, userId, commentModel.getId(), csrfToken,
new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
if (!result) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
onRefresh();
}
final String username = commentModel.getProfileModel().getUsername();
final SpannableString title = new SpannableString(username + ":\n" + commentModel.getText());
title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
String[] commentDialogList;
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
if (!TextUtils.isEmpty(cookie)
&& userIdFromCookie != null
&& (userIdFromCookie.equals(commentModel.getProfileModel().getId()) || userIdFromCookie.equals(userId))) {
commentDialogList = new String[]{
resources.getString(R.string.open_profile),
resources.getString(R.string.view_pfp),
resources.getString(R.string.comment_viewer_copy_user),
resources.getString(R.string.comment_viewer_copy_comment),
resources.getString(R.string.comment_viewer_reply_comment),
commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_comment)
: resources.getString(R.string.comment_viewer_like_comment),
resources.getString(R.string.comment_viewer_delete_comment)
};
} else if (!TextUtils.isEmpty(cookie)) {
commentDialogList = new String[]{
resources.getString(R.string.open_profile),
resources.getString(R.string.view_pfp),
resources.getString(R.string.comment_viewer_copy_user),
resources.getString(R.string.comment_viewer_copy_comment),
resources.getString(R.string.comment_viewer_reply_comment),
commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_comment)
: resources.getString(R.string.comment_viewer_like_comment),
};
} else {
commentDialogList = new String[]{
resources.getString(R.string.open_profile),
resources.getString(R.string.view_pfp),
resources.getString(R.string.comment_viewer_copy_user),
resources.getString(R.string.comment_viewer_copy_comment)
};
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error deleting comment", t);
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
break;
}
final Context context = getContext();
if (context == null) return;
new AlertDialog.Builder(context)
.setTitle(title)
.setItems(commentDialogList, profileDialogListener)
.setNegativeButton(R.string.cancel, null)
.show();
}
};
private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> {
if (isHashtag) {
final NavDirections action = CommentsViewerFragmentDirections.actionGlobalHashTagFragment(text);
NavHostFragment.findNavController(this).navigate(action);
return;
}
openProfile(text);
};
private final View.OnClickListener newCommentListener = v -> {
final Editable text = binding.commentText.getText();
final Context context = getContext();
if (context == null) return;
if (text == null || TextUtils.isEmpty(text.toString())) {
Toast.makeText(context, R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show();
return;
}
final String userId = CookieUtils.getUserIdFromCookie(cookie);
if (userId == null) return;
String replyToId = null;
if (commentModel != null) {
replyToId = commentModel.getId();
}
mediaService.comment(postId, text.toString(), userId, replyToId, CookieUtils.getCsrfTokenFromCookie(cookie), new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
commentModel = null;
binding.commentText.setText("");
if (!result) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
onRefresh();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error during comment", t);
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
};
};
new AlertDialog.Builder(context)
.setTitle(title)
.setItems(commentDialogList, profileDialogListener)
.setNegativeButton(R.string.cancel, null)
.show();
}
private void openProfile(final String username) {
final NavDirections action = CommentsViewerFragmentDirections.actionGlobalProfileFragment("@" + username);
final NavDirections action = CommentsViewerFragmentDirections.actionGlobalProfileFragment(username);
NavHostFragment.findNavController(this).navigate(action);
}
}

View File

@ -1,7 +1,7 @@
package awais.instagrabber.fragments;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
@ -12,6 +12,8 @@ import android.text.style.StyleSpan;
import android.util.Log;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@ -22,81 +24,79 @@ import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.PostsAdapter;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.asyncs.HashtagFetcher;
import awais.instagrabber.asyncs.PostsFetcher;
import awais.instagrabber.asyncs.HashtagPostFetchService;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.customviews.helpers.NestedCoordinatorLayout;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentHashtagBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.PostsViewModel;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService;
import awais.instagrabber.webservices.TagsService;
import awaisomereport.LogCollector;
import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "HashTagFragment";
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030;
public static final String ARG_HASHTAG = "hashtag";
private MainActivity fragmentActivity;
private FragmentHashtagBinding binding;
private NestedCoordinatorLayout root;
private boolean shouldRefresh = true, hasStories = false;
private boolean shouldRefresh = true;
private boolean hasStories = false;
private String hashtag;
private HashtagModel hashtagModel;
private PostsViewModel postsViewModel;
private PostsAdapter postsAdapter;
private ActionMode actionMode;
private StoriesService storiesService;
private boolean hasNextPage;
private String endCursor;
private AsyncTask<?, ?, ?> currentlyExecuting;
private boolean isLoggedIn;
private TagsService tagsService;
private boolean isPullToRefresh;
private boolean storiesFetching;
private Set<FeedModel> selectedFeedModels;
private FeedModel downloadFeedModel;
private int downloadChildPosition = -1;
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
setEnabled(false);
remove();
if (postsAdapter == null) return;
postsAdapter.clearSelection();
binding.posts.endSelection();
}
};
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
@ -104,53 +104,146 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
new PrimaryActionModeCallback.CallbacksHelper() {
@Override
public void onDestroy(final ActionMode mode) {
onBackPressedCallback.handleOnBackPressed();
binding.posts.endSelection();
}
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
if (item.getItemId() == R.id.action_download) {
if (postsAdapter == null || hashtag == null) {
return false;
}
if (HashTagFragment.this.selectedFeedModels == null) return false;
final Context context = getContext();
if (context == null) return false;
DownloadUtils.batchDownload(context,
hashtag,
DownloadMethod.DOWNLOAD_MAIN,
postsAdapter.getSelectedModels());
checkAndResetAction();
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
DownloadUtils.download(context, ImmutableList.copyOf(HashTagFragment.this.selectedFeedModels));
binding.posts.endSelection();
return true;
}
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
return true;
}
return false;
}
});
private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
@Override
public void onResult(final List<PostModel> result) {
binding.swipeRefreshLayout.setRefreshing(false);
if (result == null) return;
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
final List<PostModel> postModels = postsViewModel.getList().getValue();
List<PostModel> finalList = postModels == null || postModels.isEmpty()
? new ArrayList<>()
: new ArrayList<>(postModels);
if (isPullToRefresh) {
finalList = result;
isPullToRefresh = false;
} else {
finalList.addAll(result);
public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {
openPostDialog(feedModel, profilePicView, mainPostImage, -1);
}
@Override
public void onSliderClick(final FeedModel feedModel, final int position) {
openPostDialog(feedModel, null, null, position);
}
@Override
public void onCommentsClick(final FeedModel feedModel) {
final NavDirections commentsAction = HashTagFragmentDirections.actionGlobalCommentsViewerFragment(
feedModel.getShortCode(),
feedModel.getPostId(),
feedModel.getProfileModel().getId()
);
NavHostFragment.findNavController(HashTagFragment.this).navigate(commentsAction);
}
@Override
public void onDownloadClick(final FeedModel feedModel, final int childPosition) {
final Context context = getContext();
if (context == null) return;
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
return;
}
finalList.addAll(result);
postsViewModel.getList().postValue(finalList);
PostModel model = null;
if (!result.isEmpty()) {
model = result.get(result.size() - 1);
downloadFeedModel = feedModel;
downloadChildPosition = childPosition;
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
}
@Override
public void onHashtagClick(final String hashtag) {
final NavDirections action = HashTagFragmentDirections.actionGlobalHashTagFragment(hashtag);
NavHostFragment.findNavController(HashTagFragment.this).navigate(action);
}
@Override
public void onLocationClick(final FeedModel feedModel) {
final NavDirections action = HashTagFragmentDirections.actionGlobalLocationFragment(feedModel.getLocationId());
NavHostFragment.findNavController(HashTagFragment.this).navigate(action);
}
@Override
public void onMentionClick(final String mention) {
navigateToProfile(mention.trim());
}
@Override
public void onNameClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onProfilePicClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onURLClick(final String url) {
Utils.openURL(getContext(), url);
}
@Override
public void onEmailClick(final String emailId) {
Utils.openEmailAddress(getContext(), emailId);
}
private void openPostDialog(final FeedModel feedModel,
final View profilePicView,
final View mainPostImage,
final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(feedModel);
if (position >= 0) {
builder.setPosition(position);
}
final PostViewV2Fragment fragment = builder
.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage)
.build();
fragment.show(getChildFragmentManager(), "post_view");
}
};
private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() {
@Override
public void onSelectionStart() {
if (!onBackPressedCallback.isEnabled()) {
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
onBackPressedCallback.setEnabled(true);
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
}
if (actionMode == null) {
actionMode = fragmentActivity.startActionMode(multiSelectAction);
}
}
@Override
public void onSelectionChange(final Set<FeedModel> selectedFeedModels) {
final String title = getString(R.string.number_selected, selectedFeedModels.size());
if (actionMode != null) {
actionMode.setTitle(title);
}
HashTagFragment.this.selectedFeedModels = selectedFeedModels;
}
@Override
public void onSelectionEnd() {
if (onBackPressedCallback.isEnabled()) {
onBackPressedCallback.setEnabled(false);
onBackPressedCallback.remove();
}
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
if (model == null) return;
endCursor = model.getEndCursor();
hasNextPage = model.hasNextPage();
model.setPageCursor(false, null);
}
};
@ -160,6 +253,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
fragmentActivity = (MainActivity) requireActivity();
tagsService = TagsService.getInstance();
storiesService = StoriesService.getInstance();
setHasOptionsMenu(true);
}
@Nullable
@ -184,9 +278,8 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override
public void onRefresh() {
isPullToRefresh = true;
endCursor = null;
fetchHashtagModel();
binding.posts.refresh();
fetchStories();
}
@Override
@ -196,10 +289,35 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
@Override
public void onDestroy() {
super.onDestroy();
if (postsViewModel != null) {
postsViewModel.getList().postValue(Collections.emptyList());
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.topic_posts_menu, menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == R.id.layout) {
showPostsLayoutPreferences();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
final Context context = getContext();
if (context == null) return;
if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) {
if (downloadFeedModel == null) return;
DownloadUtils.showDownloadDialog(context, downloadFeedModel, downloadChildPosition);
downloadFeedModel = null;
downloadChildPosition = -1;
return;
}
if (requestCode == STORAGE_PERM_REQUEST_CODE_FOR_SELECTION && granted) {
DownloadUtils.download(context, ImmutableList.copyOf(selectedFeedModels));
binding.posts.endSelection();
}
}
@ -209,113 +327,42 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null;
final HashTagFragmentArgs fragmentArgs = HashTagFragmentArgs.fromBundle(getArguments());
hashtag = fragmentArgs.getHashtag();
setTitle();
setupPosts();
fetchHashtagModel();
}
private void setupPosts() {
postsViewModel = new ViewModelProvider(this).get(PostsViewModel.class);
final Context context = getContext();
if (context == null) return;
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110));
binding.mainPosts.setLayoutManager(layoutManager);
binding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
postsAdapter = new PostsAdapter((postModel, position) -> {
if (postsAdapter.isSelecting()) {
if (actionMode == null) return;
final String title = getString(R.string.number_selected, postsAdapter.getSelectedModels().size());
actionMode.setTitle(title);
return;
}
if (checkAndResetAction()) return;
final List<PostModel> postModels = postsViewModel.getList().getValue();
if (postModels == null || postModels.size() == 0) return;
if (postModels.get(0) == null) return;
final String postId = postModels.get(0).getPostId();
final boolean isId = postId != null && isLoggedIn;
final String[] idsOrShortCodes = new String[postModels.size()];
for (int i = 0; i < postModels.size(); i++) {
idsOrShortCodes[i] = isId ? postModels.get(i).getPostId()
: postModels.get(i).getShortCode();
}
final NavDirections action = HashTagFragmentDirections.actionGlobalPostViewFragment(
position,
idsOrShortCodes,
isId);
NavHostFragment.findNavController(this).navigate(action);
}, (model, position) -> {
if (!postsAdapter.isSelecting()) {
checkAndResetAction();
return true;
}
if (onBackPressedCallback.isEnabled()) {
return true;
}
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
onBackPressedCallback.setEnabled(true);
actionMode = fragmentActivity.startActionMode(multiSelectAction);
final String title = getString(R.string.number_selected, 1);
actionMode.setTitle(title);
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
return true;
});
postsViewModel.getList().observe(fragmentActivity, postsAdapter::submitList);
binding.mainPosts.setAdapter(postsAdapter);
final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!hasNextPage || getContext() == null) return;
binding.swipeRefreshLayout.setRefreshing(true);
fetchPosts();
endCursor = null;
});
binding.mainPosts.addOnScrollListener(lazyLoader);
}
private void fetchHashtagModel() {
stopCurrentExecutor();
binding.swipeRefreshLayout.setRefreshing(true);
currentlyExecuting = new HashtagFetcher(hashtag.substring(1), result -> {
final Context context = getContext();
if (context == null) return;
hashtagModel = result;
binding.swipeRefreshLayout.setRefreshing(false);
final Context context = getContext();
if (context == null) return;
if (hashtagModel == null) {
Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show();
return;
}
fetchPosts();
setTitle();
setHashtagDetails();
setupPosts();
fetchStories();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void fetchPosts() {
stopCurrentExecutor();
private void setupPosts() {
binding.posts.setViewModelStoreOwner(this)
.setLifeCycleOwner(this)
.setPostFetchService(new HashtagPostFetchService(hashtagModel))
.setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_HASHTAG_POSTS_LAYOUT)))
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
.setFeedItemCallback(feedItemCallback)
.setSelectionModeCallback(selectionModeCallback)
.init();
binding.swipeRefreshLayout.setRefreshing(true);
if (TextUtils.isEmpty(hashtag)) return;
currentlyExecuting = new PostsFetcher(hashtag.substring(1), PostItemType.HASHTAG, endCursor, postsFetchListener)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
final Context context = getContext();
if (context == null) return;
if (isLoggedIn) {
storiesService.getUserStory(hashtagModel.getName(),
null,
false,
true,
false,
new ServiceCallback<List<StoryModel>>() {
@Override
public void onSuccess(final List<StoryModel> storyModels) {
if (storyModels != null && !storyModels.isEmpty()) {
binding.mainHashtagImage.setStoriesBorder();
hasStories = true;
}
}
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
}
});
private void setHashtagDetails() {
if (isLoggedIn) {
binding.btnFollowTag.setVisibility(View.VISIBLE);
binding.btnFollowTag.setText(hashtagModel.getFollowing() ? R.string.unfollow : R.string.follow);
binding.btnFollowTag.setChipIconResource(hashtagModel.getFollowing()
@ -324,9 +371,35 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
binding.btnFollowTag.setOnClickListener(v -> {
final String cookie = settingsHelper.getString(Constants.COOKIE);
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
binding.btnFollowTag.setClickable(false);
if (!hashtagModel.getFollowing()) {
tagsService.follow(hashtag.substring(1), csrfToken, new ServiceCallback<Boolean>() {
if (csrfToken != null) {
binding.btnFollowTag.setClickable(false);
if (!hashtagModel.getFollowing()) {
tagsService.follow(hashtag.substring(1), csrfToken, new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
binding.btnFollowTag.setClickable(true);
if (!result) {
Log.e(TAG, "onSuccess: result is false");
return;
}
onRefresh();
}
@Override
public void onFailure(@NonNull final Throwable t) {
binding.btnFollowTag.setClickable(true);
Log.e(TAG, "onFailure: ", t);
final String message = t.getMessage();
Snackbar.make(root,
message != null ? message
: getString(R.string.downloader_unknown_error),
BaseTransientBottomBar.LENGTH_LONG)
.show();
}
});
return;
}
tagsService.unfollow(hashtag.substring(1), csrfToken, new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
binding.btnFollowTag.setClickable(true);
@ -349,31 +422,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
.show();
}
});
return;
}
tagsService.unfollow(hashtag.substring(1), csrfToken, new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
binding.btnFollowTag.setClickable(true);
if (!result) {
Log.e(TAG, "onSuccess: result is false");
return;
}
onRefresh();
}
@Override
public void onFailure(@NonNull final Throwable t) {
binding.btnFollowTag.setClickable(true);
Log.e(TAG, "onFailure: ", t);
final String message = t.getMessage();
Snackbar.make(root,
message != null ? message
: getString(R.string.downloader_unknown_error),
BaseTransientBottomBar.LENGTH_LONG)
.show();
}
});
});
} else {
binding.btnFollowTag.setVisibility(View.GONE);
@ -420,16 +469,43 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
binding.mainTagPostCount.setText(span);
binding.mainTagPostCount.setVisibility(View.VISIBLE);
binding.mainHashtagImage.setOnClickListener(v -> {
if (hasStories) {
// show stories
final NavDirections action = HashTagFragmentDirections
.actionHashtagFragmentToStoryViewerFragment(-1, null, true, false, hashtagModel.getName(), hashtagModel.getName());
NavHostFragment.findNavController(this).navigate(action);
return;
}
if (!hasStories) return;
// show stories
final NavDirections action = HashTagFragmentDirections
.actionHashtagFragmentToStoryViewerFragment(-1, null, true, false, hashtagModel.getName(), hashtagModel.getName());
NavHostFragment.findNavController(this).navigate(action);
});
}
private void fetchStories() {
if (!isLoggedIn) return;
storiesFetching = true;
storiesService.getUserStory(
hashtagModel.getName(),
null,
false,
true,
false,
new ServiceCallback<List<StoryModel>>() {
@Override
public void onSuccess(final List<StoryModel> storyModels) {
if (storyModels != null && !storyModels.isEmpty()) {
binding.mainHashtagImage.setStoriesBorder();
hasStories = true;
} else {
hasStories = false;
}
storiesFetching = false;
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
storiesFetching = false;
}
});
}
public void stopCurrentExecutor() {
if (currentlyExecuting != null) {
try {
@ -445,24 +521,28 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
private void setTitle() {
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) {
Log.d(TAG, "setting title: " + hashtag);
final Handler handler = new Handler();
handler.postDelayed(() -> actionBar.setTitle(hashtag), 200);
// Log.d(TAG, "setting title: " + hashtag);
actionBar.setTitle(hashtag);
// final Handler handler = new Handler();
// handler.postDelayed(() -> , 1000);
}
}
private boolean checkAndResetAction() {
if (!onBackPressedCallback.isEnabled() && actionMode == null) {
return false;
}
if (onBackPressedCallback.isEnabled()) {
onBackPressedCallback.setEnabled(false);
onBackPressedCallback.remove();
}
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
return true;
private void updateSwipeRefreshState() {
binding.swipeRefreshLayout.setRefreshing(binding.posts.isFetching() || storiesFetching);
}
private void navigateToProfile(final String username) {
final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle();
bundle.putString("username", username);
navController.navigate(R.id.action_global_profileFragment, bundle);
}
private void showPostsLayoutPreferences() {
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(
Constants.PREF_HASHTAG_POSTS_LAYOUT,
preferences -> new Handler().postDelayed(() -> binding.posts.setLayoutPreferences(preferences), 200));
fragment.show(getChildFragmentManager(), "posts_layout_preferences");
}
}

View File

@ -2,16 +2,20 @@ package awais.instagrabber.fragments;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.text.SpannableStringBuilder;
import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.util.Log;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@ -23,134 +27,221 @@ import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.PostsAdapter;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.asyncs.LocationFetcher;
import awais.instagrabber.asyncs.PostsFetcher;
import awais.instagrabber.asyncs.LocationPostFetchService;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.customviews.helpers.NestedCoordinatorLayout;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentLocationBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.LocationModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.PostsViewModel;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService;
import awaisomereport.LogCollector;
import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "LocationFragment";
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030;
private MainActivity fragmentActivity;
private FragmentLocationBinding binding;
private NestedCoordinatorLayout root;
private boolean shouldRefresh = true, hasStories = false;
private boolean shouldRefresh = true;
private boolean hasStories = false;
private String locationId;
private LocationModel locationModel;
private PostsViewModel postsViewModel;
private PostsAdapter postsAdapter;
private ActionMode actionMode;
private StoriesService storiesService;
private boolean hasNextPage;
private String endCursor;
private AsyncTask<?, ?, ?> currentlyExecuting;
private boolean isLoggedIn;
private boolean isPullToRefresh;
private boolean storiesFetching;
private Set<FeedModel> selectedFeedModels;
private FeedModel downloadFeedModel;
private int downloadChildPosition = -1;
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
if (postsAdapter == null) {
setEnabled(false);
remove();
return;
}
postsAdapter.clearSelection();
setEnabled(false);
remove();
binding.posts.endSelection();
}
};
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
R.menu.multi_select_download_menu, new PrimaryActionModeCallback.CallbacksHelper() {
@Override
public void onDestroy(final ActionMode mode) {
onBackPressedCallback.handleOnBackPressed();
binding.posts.endSelection();
}
@Override
public boolean onActionItemClicked(final ActionMode mode,
final MenuItem item) {
if (item.getItemId() == R.id.action_download) {
if (postsAdapter == null || locationId == null) {
return false;
}
if (LocationFragment.this.selectedFeedModels == null) return false;
final Context context = getContext();
if (context == null) return false;
DownloadUtils.batchDownload(context,
locationId,
DownloadMethod.DOWNLOAD_MAIN,
postsAdapter.getSelectedModels());
checkAndResetAction();
return true;
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
DownloadUtils.download(context, ImmutableList.copyOf(LocationFragment.this.selectedFeedModels));
binding.posts.endSelection();
return true;
}
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
}
return false;
}
});
private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
@Override
public void onResult(final List<PostModel> result) {
binding.swipeRefreshLayout.setRefreshing(false);
if (result == null) return;
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
final List<PostModel> postModels = postsViewModel.getList().getValue();
List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>()
: new ArrayList<>(postModels);
if (isPullToRefresh) {
finalList = result;
isPullToRefresh = false;
} else {
finalList.addAll(result);
public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {
openPostDialog(feedModel, profilePicView, mainPostImage, -1);
}
@Override
public void onSliderClick(final FeedModel feedModel, final int position) {
openPostDialog(feedModel, null, null, position);
}
@Override
public void onCommentsClick(final FeedModel feedModel) {
final NavDirections commentsAction = LocationFragmentDirections.actionGlobalCommentsViewerFragment(
feedModel.getShortCode(),
feedModel.getPostId(),
feedModel.getProfileModel().getId()
);
NavHostFragment.findNavController(LocationFragment.this).navigate(commentsAction);
}
@Override
public void onDownloadClick(final FeedModel feedModel, final int childPosition) {
final Context context = getContext();
if (context == null) return;
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
return;
}
postsViewModel.getList().postValue(finalList);
PostModel model = null;
if (!result.isEmpty()) {
model = result.get(result.size() - 1);
downloadFeedModel = feedModel;
downloadChildPosition = childPosition;
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
}
@Override
public void onHashtagClick(final String hashtag) {
final NavDirections action = LocationFragmentDirections.actionGlobalHashTagFragment(hashtag);
NavHostFragment.findNavController(LocationFragment.this).navigate(action);
}
@Override
public void onLocationClick(final FeedModel feedModel) {
final NavDirections action = LocationFragmentDirections.actionGlobalLocationFragment(feedModel.getLocationId());
NavHostFragment.findNavController(LocationFragment.this).navigate(action);
}
@Override
public void onMentionClick(final String mention) {
navigateToProfile(mention.trim());
}
@Override
public void onNameClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onProfilePicClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onURLClick(final String url) {
Utils.openURL(getContext(), url);
}
@Override
public void onEmailClick(final String emailId) {
Utils.openEmailAddress(getContext(), emailId);
}
private void openPostDialog(final FeedModel feedModel,
final View profilePicView,
final View mainPostImage,
final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(feedModel);
if (position >= 0) {
builder.setPosition(position);
}
final PostViewV2Fragment fragment = builder
.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage)
.build();
fragment.show(getChildFragmentManager(), "post_view");
}
};
private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() {
@Override
public void onSelectionStart() {
if (!onBackPressedCallback.isEnabled()) {
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
onBackPressedCallback.setEnabled(true);
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
}
if (actionMode == null) {
actionMode = fragmentActivity.startActionMode(multiSelectAction);
}
}
@Override
public void onSelectionChange(final Set<FeedModel> selectedFeedModels) {
final String title = getString(R.string.number_selected, selectedFeedModels.size());
if (actionMode != null) {
actionMode.setTitle(title);
}
LocationFragment.this.selectedFeedModels = selectedFeedModels;
}
@Override
public void onSelectionEnd() {
if (onBackPressedCallback.isEnabled()) {
onBackPressedCallback.setEnabled(false);
onBackPressedCallback.remove();
}
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
if (model == null) return;
endCursor = model.getEndCursor();
hasNextPage = model.hasNextPage();
model.setPageCursor(false, null);
}
};
@ -159,6 +250,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity();
storiesService = StoriesService.getInstance();
setHasOptionsMenu(true);
}
@Nullable
@ -185,9 +277,8 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
@Override
public void onRefresh() {
isPullToRefresh = true;
endCursor = null;
fetchLocationModel();
binding.posts.refresh();
fetchStories();
}
@Override
@ -197,10 +288,35 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
}
@Override
public void onDestroy() {
super.onDestroy();
if (postsViewModel != null) {
postsViewModel.getList().postValue(Collections.emptyList());
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.topic_posts_menu, menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == R.id.layout) {
showPostsLayoutPreferences();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
final Context context = getContext();
if (context == null) return;
if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) {
if (downloadFeedModel == null) return;
DownloadUtils.showDownloadDialog(context, downloadFeedModel, downloadChildPosition);
downloadFeedModel = null;
downloadChildPosition = -1;
return;
}
if (requestCode == STORAGE_PERM_REQUEST_CODE_FOR_SELECTION && granted) {
DownloadUtils.download(context, ImmutableList.copyOf(selectedFeedModels));
binding.posts.endSelection();
}
}
@ -213,66 +329,19 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
binding.favChip.setVisibility(View.GONE);
binding.btnMap.setVisibility(View.GONE);
setTitle();
setupPosts();
fetchLocationModel();
}
private void setupPosts() {
postsViewModel = new ViewModelProvider(this).get(PostsViewModel.class);
final Context context = getContext();
if (context == null) return;
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110));
binding.mainPosts.setLayoutManager(layoutManager);
binding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
postsAdapter = new PostsAdapter((postModel, position) -> {
if (postsAdapter.isSelecting()) {
if (actionMode == null) return;
final String title = getString(R.string.number_selected, postsAdapter.getSelectedModels().size());
actionMode.setTitle(title);
return;
}
if (checkAndResetAction()) return;
final List<PostModel> postModels = postsViewModel.getList().getValue();
if (postModels == null || postModels.size() == 0) return;
if (postModels.get(0) == null) return;
final String postId = postModels.get(0).getPostId();
final boolean isId = postId != null && isLoggedIn;
final String[] idsOrShortCodes = new String[postModels.size()];
for (int i = 0; i < postModels.size(); i++) {
idsOrShortCodes[i] = isId ? postModels.get(i).getPostId()
: postModels.get(i).getShortCode();
}
final NavDirections action = LocationFragmentDirections.actionGlobalPostViewFragment(
position,
idsOrShortCodes,
isId);
NavHostFragment.findNavController(this).navigate(action);
}, (model, position) -> {
if (!postsAdapter.isSelecting()) {
checkAndResetAction();
return true;
}
if (onBackPressedCallback.isEnabled()) {
return true;
}
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity
.getOnBackPressedDispatcher();
onBackPressedCallback.setEnabled(true);
actionMode = fragmentActivity.startActionMode(multiSelectAction);
final String title = getString(R.string.number_selected, 1);
actionMode.setTitle(title);
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
return true;
});
postsViewModel.getList().observe(fragmentActivity, postsAdapter::submitList);
binding.mainPosts.setAdapter(postsAdapter);
final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!hasNextPage) return;
binding.swipeRefreshLayout.setRefreshing(true);
fetchPosts();
endCursor = null;
});
binding.mainPosts.addOnScrollListener(lazyLoader);
binding.posts.setViewModelStoreOwner(this)
.setLifeCycleOwner(this)
.setPostFetchService(new LocationPostFetchService(locationModel))
.setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_LOCATION_POSTS_LAYOUT)))
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
.setFeedItemCallback(feedItemCallback)
.setSelectionModeCallback(selectionModeCallback)
.init();
binding.swipeRefreshLayout.setRefreshing(true);
}
private void fetchLocationModel() {
@ -289,34 +358,15 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
}
setTitle();
setupLocationDetails();
fetchPosts();
setupPosts();
fetchStories();
// fetchPosts();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void setupLocationDetails() {
final String locationId = locationModel.getId();
binding.swipeRefreshLayout.setRefreshing(true);
if (isLoggedIn) {
storiesService.getUserStory(locationId,
null,
true,
false,
false,
new ServiceCallback<List<StoryModel>>() {
@Override
public void onSuccess(final List<StoryModel> storyModels) {
if (storyModels != null && !storyModels.isEmpty()) {
binding.mainLocationImage.setStoriesBorder();
hasStories = true;
}
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
}
});
}
// binding.swipeRefreshLayout.setRefreshing(true);
binding.mainLocationImage.setImageURI(locationModel.getSdProfilePic());
final String postCount = String.valueOf(locationModel.getPostCount());
final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline,
@ -327,8 +377,8 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
binding.mainLocPostCount.setVisibility(View.VISIBLE);
binding.locationFullName.setText(locationModel.getName());
CharSequence biography = locationModel.getBio();
binding.locationBiography.setCaptionIsExpandable(true);
binding.locationBiography.setCaptionIsExpanded(true);
// binding.locationBiography.setCaptionIsExpandable(true);
// binding.locationBiography.setCaptionIsExpanded(true);
if (TextUtils.isEmpty(biography)) {
binding.locationBiography.setVisibility(View.GONE);
@ -405,18 +455,38 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
final NavDirections action = LocationFragmentDirections
.actionLocationFragmentToStoryViewerFragment(-1, null, false, true, locationId, locationModel.getName());
NavHostFragment.findNavController(this).navigate(action);
return;
}
});
}
private void fetchPosts() {
stopCurrentExecutor();
currentlyExecuting = new PostsFetcher(locationModel.getId(), PostItemType.LOCATION, endCursor, postsFetchListener)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
private void fetchStories() {
if (isLoggedIn) {
storiesFetching = true;
storiesService.getUserStory(locationId,
null,
true,
false,
false,
new ServiceCallback<List<StoryModel>>() {
@Override
public void onSuccess(final List<StoryModel> storyModels) {
if (storyModels != null && !storyModels.isEmpty()) {
binding.mainLocationImage.setStoriesBorder();
hasStories = true;
}
storiesFetching = false;
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
storiesFetching = false;
}
});
}
}
public void stopCurrentExecutor() {
private void stopCurrentExecutor() {
if (currentlyExecuting != null) {
try {
currentlyExecuting.cancel(true);
@ -435,18 +505,21 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
}
}
private boolean checkAndResetAction() {
if (!onBackPressedCallback.isEnabled() && actionMode == null) {
return false;
}
if (onBackPressedCallback.isEnabled()) {
onBackPressedCallback.setEnabled(false);
onBackPressedCallback.remove();
}
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
return true;
private void updateSwipeRefreshState() {
binding.swipeRefreshLayout.setRefreshing(binding.posts.isFetching() || storiesFetching);
}
private void navigateToProfile(final String username) {
final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle();
bundle.putString("username", username);
navController.navigate(R.id.action_global_profileFragment, bundle);
}
private void showPostsLayoutPreferences() {
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(
Constants.PREF_LOCATION_POSTS_LAYOUT,
preferences -> new Handler().postDelayed(() -> binding.posts.setLayoutPreferences(preferences), 200));
fragment.show(getChildFragmentManager(), "posts_layout_preferences");
}
}

View File

@ -28,6 +28,7 @@ import awais.instagrabber.R;
import awais.instagrabber.adapters.NotificationsAdapter;
import awais.instagrabber.adapters.NotificationsAdapter.OnNotificationClickListener;
import awais.instagrabber.asyncs.NotificationsFetcher;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.databinding.FragmentNotificationsViewerBinding;
import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections;
import awais.instagrabber.interfaces.MentionClickListener;
@ -75,7 +76,8 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
} else {
commentDialogList = new String[]{getString(R.string.open_profile)};
}
if (getContext() == null) return;
final Context context = getContext();
if (context == null) return;
final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> {
switch (which) {
case 0:
@ -101,9 +103,18 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
});
return;
}
final NavDirections action = MorePreferencesFragmentDirections
.actionGlobalPostViewFragment(0, new String[]{model.getShortCode()}, false);
NavHostFragment.findNavController(this).navigate(action);
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setCancelable(false)
.setView(R.layout.dialog_opening_post)
.create();
alertDialog.show();
new PostFetcher(model.getShortCode(), feedModel -> {
final PostViewV2Fragment fragment = PostViewV2Fragment
.builder(feedModel)
.build();
fragment.setOnShowListener(dialog1 -> alertDialog.dismiss());
fragment.show(getChildFragmentManager(), "post_view");
}).execute();
break;
case 2:
friendshipService.ignore(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() {
@ -125,7 +136,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
break;
}
};
new AlertDialog.Builder(getContext())
new AlertDialog.Builder(context)
.setTitle(title)
.setItems(commentDialogList, profileDialogListener)
.setNegativeButton(R.string.cancel, null)

View File

@ -1,342 +0,0 @@
package awais.instagrabber.fragments;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.viewpager2.widget.ViewPager2;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.adapters.PostViewAdapter;
import awais.instagrabber.adapters.PostViewAdapter.OnPostViewChildViewClickListener;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.asyncs.i.iPostFetcher;
import awais.instagrabber.databinding.FragmentPostViewBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.ViewerPostModelWrapper;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.ViewerPostViewModel;
import awais.instagrabber.webservices.MediaService;
import awais.instagrabber.webservices.ServiceCallback;
import static androidx.core.content.ContextCompat.checkSelfPermission;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class PostViewFragment extends Fragment {
private static final String TAG = "PostViewFragment";
private static final String COOKIE = settingsHelper.getString(Constants.COOKIE);
private FragmentActivity fragmentActivity;
private FragmentPostViewBinding binding;
private ViewPager2 root;
private boolean shouldRefresh = true;
private ViewerPostViewModel viewerPostViewModel;
private boolean isId;
private int currentPostIndex;
private List<String> idOrCodeList;
private boolean hasInitialResult = false;
private PostViewAdapter adapter;
private boolean session;
private MediaService mediaService;
private FetchListener<ViewerPostModel[]> pfl = result -> {
if (result == null) return;
if (result.length <= 0) return;
final List<ViewerPostModelWrapper> viewerPostModels = viewerPostViewModel.getList().getValue();
final List<ViewerPostModelWrapper> temp = viewerPostModels == null ? new ArrayList<>(idOrCodeList.size())
: new ArrayList<>(viewerPostModels);
final ViewerPostModel firstPost = result[0];
if (firstPost == null) return;
String idOrCode = isId ? firstPost.getPostId() : firstPost.getShortCode();
if (idOrCode == null) return;
if (isId) {
// the post id is appended with `_` in the result
idOrCode = idOrCode.substring(0, idOrCode.indexOf('_'));
}
final int index = idOrCodeList.indexOf(idOrCode);
if (index < 0) return;
final ViewerPostModelWrapper viewerPostModelWrapper = temp.get(index);
viewerPostModelWrapper.setViewerPostModels(result);
temp.set(index, viewerPostModelWrapper);
viewerPostViewModel.getList().setValue(temp);
adapter.notifyItemChanged(index);
if (!hasInitialResult) {
Log.d(TAG, "setting delayed position to: " + currentPostIndex);
binding.getRoot()
.postDelayed(() -> binding.getRoot().setCurrentItem(currentPostIndex), 200);
}
hasInitialResult = true;
};
private MentionClickListener mentionListener = (view, text, isHashtag, isLocation) -> {
if (isHashtag) {
final NavDirections action = PostViewFragmentDirections
.actionGlobalHashTagFragment(text);
NavHostFragment.findNavController(this).navigate(action);
return;
}
if (isLocation) {
final NavDirections action = PostViewFragmentDirections
.actionGlobalLocationFragment(text);
NavHostFragment.findNavController(this).navigate(action);
return;
}
final NavDirections action = PostViewFragmentDirections
.actionGlobalProfileFragment("@" + text);
NavHostFragment.findNavController(this).navigate(action);
};
private OnPostViewChildViewClickListener clickListener = (v, wrapper, postPosition, childPosition) -> {
final ViewerPostModel postModel = wrapper.getViewerPostModels()[0];
final String username = postModel.getProfileModel().getUsername();
final int id = v.getId();
switch (id) {
case R.id.viewerCaption:
break;
case R.id.btnComments:
String postId = postModel.getPostId();
if (postId.contains("_")) postId = postId.substring(0, postId.indexOf("_"));
final NavDirections commentsAction = PostViewFragmentDirections.actionGlobalCommentsViewerFragment(
postModel.getShortCode(),
postId,
postModel.getProfileModel().getId()
);
NavHostFragment.findNavController(this).navigate(commentsAction);
break;
case R.id.btnDownload:
final Context context = getContext();
if (context == null) return;
if (checkSelfPermission(context,
DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
showDownloadDialog(Arrays.asList(wrapper.getViewerPostModels()),
childPosition,
username);
return;
}
requestPermissions(DownloadUtils.PERMS, 8020);
break;
case R.id.ivProfilePic:
case R.id.title:
mentionListener.onClick(null, username, false, false);
break;
case R.id.btnLike:
if (mediaService != null) {
final String userId = CookieUtils.getUserIdFromCookie(COOKIE);
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(COOKIE);
v.setEnabled(false);
final ServiceCallback<Boolean> likeCallback = new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
v.setEnabled(true);
if (result) {
postModel.setManualLike(!postModel.getLike());
adapter.notifyItemChanged(postPosition);
return;
}
Log.e(TAG, "like/unlike unsuccessful!");
}
@Override
public void onFailure(final Throwable t) {
v.setEnabled(true);
Log.e(TAG, "Error during like/unlike", t);
}
};
if (!postModel.getLike()) {
mediaService.like(postModel.getPostId(), userId, csrfToken, likeCallback);
} else {
mediaService.unlike(postModel.getPostId(), userId, csrfToken, likeCallback);
}
}
break;
case R.id.btnBookmark:
if (mediaService != null) {
final String userId = CookieUtils.getUserIdFromCookie(COOKIE);
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(COOKIE);
v.setEnabled(false);
final ServiceCallback<Boolean> saveCallback = new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
v.setEnabled(true);
if (result) {
postModel.setBookmarked(!postModel.getBookmark());
adapter.notifyItemChanged(postPosition);
return;
}
Log.e(TAG, "save/unsave unsuccessful!");
}
@Override
public void onFailure(final Throwable t) {
v.setEnabled(true);
Log.e(TAG, "Error during save/unsave", t);
}
};
if (!postModel.getBookmark()) {
mediaService.save(postModel.getPostId(), userId, csrfToken, saveCallback);
} else {
mediaService.unsave(postModel.getPostId(), userId, csrfToken, saveCallback);
}
}
break;
}
};
private PostViewAdapter.OnPostCaptionLongClickListener captionLongClickListener = text -> {
final Context context = getContext();
if (context == null) return;
Utils.copyText(context, text);
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = getActivity();
mediaService = MediaService.getInstance();
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
if (root != null) {
shouldRefresh = false;
return root;
}
binding = FragmentPostViewBinding.inflate(inflater, container, false);
root = binding.getRoot();
setupViewPager();
return root;
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
init();
shouldRefresh = false;
}
private void setupViewPager() {
viewerPostViewModel = new ViewModelProvider(fragmentActivity)
.get(ViewerPostViewModel.class);
adapter = new PostViewAdapter(clickListener, captionLongClickListener, mentionListener);
root.setAdapter(adapter);
root.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(final int position) {
// Log.d(TAG, "onPageSelected: " + position + ", hasInitialResult: " + hasInitialResult);
if (!hasInitialResult) {
return;
}
currentPostIndex = position;
fetchPost();
}
});
viewerPostViewModel.getList().observe(fragmentActivity, list -> adapter.submitList(list));
}
private void init() {
if (getArguments() == null) return;
final PostViewFragmentArgs fragmentArgs = PostViewFragmentArgs.fromBundle(getArguments());
final String[] idOrCodeArray = fragmentArgs.getIdOrCodeArray();
if (idOrCodeArray.length == 0) return;
currentPostIndex = fragmentArgs.getIndex();
if (currentPostIndex < 0) return;
if (currentPostIndex >= idOrCodeArray.length) return;
idOrCodeList = Arrays.asList(idOrCodeArray);
viewerPostViewModel.getList().setValue(createPlaceholderModels(idOrCodeArray.length));
isId = fragmentArgs.getIsId();
fetchPost();
}
private List<ViewerPostModelWrapper> createPlaceholderModels(final int size) {
final List<ViewerPostModelWrapper> viewerPostModels = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
// viewerPostModels.add(new ViewerPostModel[]{ViewerPostModel.getDefaultModel(-i, "")});
viewerPostModels.add(new ViewerPostModelWrapper(i, null));
}
return viewerPostModels;
}
private void fetchPost() {
// Log.d(TAG, "fetchPost, currentPostIndex: " + currentPostIndex);
final List<ViewerPostModelWrapper> list = viewerPostViewModel.getList().getValue();
if (list != null) {
final ViewerPostModelWrapper viewerPostModels = list.get(currentPostIndex);
if (viewerPostModels != null && viewerPostModels
.getViewerPostModels() != null && viewerPostModels
.getViewerPostModels().length > 0) {
Log.d(TAG, "returning without fetching");
return;
}
}
if (currentPostIndex >= idOrCodeList.size() || currentPostIndex < 0) return;
final String idOrShortCode = idOrCodeList.get(currentPostIndex);
if (isId) {
new iPostFetcher(idOrShortCode, pfl).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return;
}
new PostFetcher(idOrShortCode, pfl).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void showDownloadDialog(final List<ViewerPostModel> postModels,
final int childPosition,
final String username) {
final List<ViewerPostModel> postModelsToDownload = new ArrayList<>();
final Context context = getContext();
if (context == null) return;
if (!session && postModels.size() > 1) {
final DialogInterface.OnClickListener clickListener = (dialog, which) -> {
if (which == DialogInterface.BUTTON_NEGATIVE) {
postModelsToDownload.addAll(postModels);
} else if (which == DialogInterface.BUTTON_POSITIVE) {
postModelsToDownload.add(postModels.get(childPosition));
} else {
session = true;
postModelsToDownload.add(postModels.get(childPosition));
}
if (postModelsToDownload.size() > 0) {
DownloadUtils.batchDownload(context,
username,
DownloadMethod.DOWNLOAD_POST_VIEWER,
postModelsToDownload);
}
};
new AlertDialog.Builder(context)
.setTitle(R.string.post_viewer_download_dialog_title)
.setMessage(R.string.post_viewer_download_message)
.setNeutralButton(R.string.post_viewer_download_session, clickListener)
.setPositiveButton(R.string.post_viewer_download_current, clickListener)
.setNegativeButton(R.string.post_viewer_download_album, clickListener).show();
} else {
DownloadUtils.batchDownload(context,
username,
DownloadMethod.DOWNLOAD_POST_VIEWER,
Collections.singletonList(postModels.get(childPosition)));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,16 +2,15 @@ package awais.instagrabber.fragments;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.os.Handler;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
@ -19,64 +18,55 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.google.common.collect.ImmutableList;
import java.util.Set;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.adapters.PostsAdapter;
import awais.instagrabber.asyncs.PostsFetcher;
import awais.instagrabber.asyncs.i.iLikedFetcher;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.asyncs.SavedPostFetchService;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentSavedBinding;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.fragments.main.ProfileFragmentDirections;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.PostsViewModel;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class SavedViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static AsyncTask<?, ?, ?> currentlyExecuting;
private PostsAdapter postsAdapter;
private boolean hasNextPage;
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030;
private FragmentSavedBinding binding;
private String username;
private String endCursor;
private RecyclerLazyLoader lazyLoader;
private ArrayList<PostModel> selectedItems = new ArrayList<>();
private ActionMode actionMode;
private PostsViewModel postsViewModel;
private LinearLayout root;
private SwipeRefreshLayout root;
private AppCompatActivity fragmentActivity;
private boolean shouldRefresh = true;
private PostItemType type;
private String profileId;
private Set<FeedModel> selectedFeedModels;
private FeedModel downloadFeedModel;
private int downloadChildPosition = -1;
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
setEnabled(false);
remove();
if (postsAdapter == null) return;
postsAdapter.clearSelection();
binding.posts.endSelection();
}
};
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
@ -84,70 +74,153 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
new PrimaryActionModeCallback.CallbacksHelper() {
@Override
public void onDestroy(final ActionMode mode) {
onBackPressedCallback.handleOnBackPressed();
binding.posts.endSelection();
}
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
if (item.getItemId() == R.id.action_download) {
if (postsAdapter == null || username == null) {
return false;
}
if (SavedViewerFragment.this.selectedFeedModels == null) return false;
final Context context = getContext();
if (context == null) return false;
DownloadUtils.batchDownload(context,
username,
DownloadMethod.DOWNLOAD_SAVED,
postsAdapter.getSelectedModels());
checkAndResetAction();
return true;
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
DownloadUtils.download(context, ImmutableList.copyOf(SavedViewerFragment.this.selectedFeedModels));
binding.posts.endSelection();
return true;
}
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
}
return false;
}
});
private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
@Override
public void onResult(final List<PostModel> result) {
final List<PostModel> current = postsViewModel.getList().getValue();
if (result != null && !result.isEmpty()) {
if (current == null) {
postsViewModel.getList().postValue(result);
} else {
final List<PostModel> currentCopy = new ArrayList<>(current);
currentCopy.addAll(result);
postsViewModel.getList().postValue(currentCopy);
}
binding.mainPosts.post(() -> {
binding.mainPosts.setNestedScrollingEnabled(true);
binding.mainPosts.setVisibility(View.VISIBLE);
});
public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {
openPostDialog(feedModel, profilePicView, mainPostImage, -1);
}
final PostModel model = !result.isEmpty() ? result.get(result.size() - 1) : null;
if (model != null) {
endCursor = model.getEndCursor();
hasNextPage = model.hasNextPage();
if (hasNextPage) {
fetchPosts();
} else {
binding.swipeRefreshLayout.setRefreshing(false);
}
model.setPageCursor(false, null);
}
} else if (current == null) {
final Context context = getContext();
if (context == null) return;
Toast.makeText(context, R.string.empty_list, Toast.LENGTH_SHORT).show();
NavHostFragment.findNavController(SavedViewerFragment.this).popBackStack();
@Override
public void onSliderClick(final FeedModel feedModel, final int position) {
openPostDialog(feedModel, null, null, position);
}
@Override
public void onCommentsClick(final FeedModel feedModel) {
final NavDirections commentsAction = ProfileFragmentDirections.actionGlobalCommentsViewerFragment(
feedModel.getShortCode(),
feedModel.getPostId(),
feedModel.getProfileModel().getId()
);
NavHostFragment.findNavController(SavedViewerFragment.this).navigate(commentsAction);
}
@Override
public void onDownloadClick(final FeedModel feedModel, final int childPosition) {
final Context context = getContext();
if (context == null) return;
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
return;
}
downloadFeedModel = feedModel;
downloadChildPosition = childPosition;
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
}
@Override
public void onHashtagClick(final String hashtag) {
final NavDirections action = ProfileFragmentDirections.actionGlobalHashTagFragment(hashtag);
NavHostFragment.findNavController(SavedViewerFragment.this).navigate(action);
}
@Override
public void onLocationClick(final FeedModel feedModel) {
final NavDirections action = ProfileFragmentDirections.actionGlobalLocationFragment(feedModel.getLocationId());
NavHostFragment.findNavController(SavedViewerFragment.this).navigate(action);
}
@Override
public void onMentionClick(final String mention) {
navigateToProfile(mention.trim());
}
@Override
public void onNameClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onProfilePicClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onURLClick(final String url) {
Utils.openURL(getContext(), url);
}
@Override
public void onEmailClick(final String emailId) {
Utils.openEmailAddress(getContext(), emailId);
}
private void openPostDialog(final FeedModel feedModel,
final View profilePicView,
final View mainPostImage,
final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(feedModel);
if (position >= 0) {
builder.setPosition(position);
}
final PostViewV2Fragment fragment = builder
.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage)
.build();
fragment.show(getChildFragmentManager(), "post_view");
}
};
private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() {
@Override
public void onSelectionStart() {
if (!onBackPressedCallback.isEnabled()) {
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
onBackPressedCallback.setEnabled(true);
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
}
if (actionMode == null) {
actionMode = fragmentActivity.startActionMode(multiSelectAction);
}
}
@Override
public void onSelectionChange(final Set<FeedModel> selectedFeedModels) {
final String title = getString(R.string.number_selected, selectedFeedModels.size());
if (actionMode != null) {
actionMode.setTitle(title);
}
SavedViewerFragment.this.selectedFeedModels = selectedFeedModels;
}
@Override
public void onSelectionEnd() {
if (onBackPressedCallback.isEnabled()) {
onBackPressedCallback.setEnabled(false);
onBackPressedCallback.remove();
}
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
binding.swipeRefreshLayout.setRefreshing(false);
}
};
private Observer<List<PostModel>> listObserver;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (AppCompatActivity) getActivity();
setHasOptionsMenu(true);
}
@Override
@ -164,22 +237,34 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
binding.swipeRefreshLayout.setOnRefreshListener(this);
init();
shouldRefresh = false;
}
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.saved_viewer_menu, menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == R.id.layout) {
showPostsLayoutPreferences();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onResume() {
super.onResume();
setTitle();
observeData();
}
private void observeData() {
postsViewModel = new ViewModelProvider(this).get(PostsViewModel.class);
postsViewModel.getList().removeObserver(listObserver);
if (postsAdapter != null) {
postsViewModel.getList().observe(getViewLifecycleOwner(), listObserver);
}
@Override
public void onRefresh() {
binding.posts.refresh();
}
private void init() {
@ -189,122 +274,89 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
username = fragmentArgs.getUsername();
profileId = fragmentArgs.getProfileId();
type = fragmentArgs.getType();
setTitle();
binding.swipeRefreshLayout.setOnRefreshListener(this);
// autoloadPosts = Utils.settingsHelper.getBoolean(AUTOLOAD_POSTS);
binding.mainPosts.setNestedScrollingEnabled(false);
final Context context = getContext();
if (context == null) return;
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110));
binding.mainPosts.setLayoutManager(layoutManager);
binding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
postsAdapter = new PostsAdapter((postModel, position) -> {
if (postsAdapter.isSelecting()) {
if (actionMode == null) return;
final String title = getString(R.string.number_selected, postsAdapter.getSelectedModels().size());
actionMode.setTitle(title);
return;
}
if (checkAndResetAction()) return;
final List<PostModel> postModels = postsViewModel.getList().getValue();
if (postModels == null || postModels.size() == 0) return;
if (postModels.get(0) == null) return;
final String postId = postModels.get(0).getPostId();
final boolean isId = postId != null;
final String[] idsOrShortCodes = new String[postModels.size()];
for (int i = 0; i < postModels.size(); i++) {
final PostModel tempPostModel = postModels.get(i);
final String tempId = tempPostModel.getPostId();
final String finalPostId = type == PostItemType.LIKED ? tempId.substring(0, tempId.indexOf("_")) : tempId;
idsOrShortCodes[i] = isId ? finalPostId
: tempPostModel.getShortCode();
}
final NavDirections action = ProfileFragmentDirections.actionGlobalPostViewFragment(
position,
idsOrShortCodes,
isId);
NavHostFragment.findNavController(this).navigate(action);
}, (model, position) -> {
if (!postsAdapter.isSelecting()) {
checkAndResetAction();
return true;
}
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
if (onBackPressedCallback.isEnabled()) return true;
actionMode = fragmentActivity.startActionMode(multiSelectAction);
final String title = getString(R.string.number_selected, 1);
actionMode.setTitle(title);
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
return true;
});
binding.mainPosts.setAdapter(postsAdapter);
listObserver = list -> postsAdapter.submitList(list);
observeData();
binding.swipeRefreshLayout.setRefreshing(true);
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (hasNextPage) {
binding.swipeRefreshLayout.setRefreshing(true);
fetchPosts();
endCursor = null;
}
});
binding.mainPosts.addOnScrollListener(lazyLoader);
fetchPosts();
setupPosts();
// postsAdapter = new PostsAdapter((postModel, position) -> {
// if (postsAdapter.isSelecting()) {
// if (actionMode == null) return;
// final String title = getString(R.string.number_selected, postsAdapter.getSelectedModels().size());
// actionMode.setTitle(title);
// return;
// }
// if (checkAndResetAction()) return;
// final List<PostModel> postModels = postsViewModel.getList().getValue();
// if (postModels == null || postModels.size() == 0) return;
// if (postModels.get(0) == null) return;
// final String postId = postModels.get(0).getPostId();
// final boolean isId = postId != null;
// final String[] idsOrShortCodes = new String[postModels.size()];
// for (int i = 0; i < postModels.size(); i++) {
// final PostModel tempPostModel = postModels.get(i);
// final String tempId = tempPostModel.getPostId();
// final String finalPostId = type == PostItemType.LIKED ? tempId.substring(0, tempId.indexOf("_")) : tempId;
// idsOrShortCodes[i] = isId ? finalPostId
// : tempPostModel.getShortCode();
// }
// final NavDirections action = ProfileFragmentDirections.actionGlobalPostViewFragment(
// position,
// idsOrShortCodes,
// isId);
// NavHostFragment.findNavController(this).navigate(action);
// }, (model, position) -> {
// if (!postsAdapter.isSelecting()) {
// checkAndResetAction();
// return true;
// }
// final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
// if (onBackPressedCallback.isEnabled()) return true;
// actionMode = fragmentActivity.startActionMode(multiSelectAction);
// final String title = getString(R.string.number_selected, 1);
// actionMode.setTitle(title);
// onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
// return true;
// });
}
private void fetchPosts() {
stopCurrentExecutor();
final AsyncTask<Void, Void, List<PostModel>> asyncTask;
private void setupPosts() {
binding.posts.setViewModelStoreOwner(this)
.setLifeCycleOwner(this)
.setPostFetchService(new SavedPostFetchService(profileId, type))
.setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(getPostsLayoutPreferenceKey())))
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
.setFeedItemCallback(feedItemCallback)
.setSelectionModeCallback(selectionModeCallback)
.init();
binding.swipeRefreshLayout.setRefreshing(true);
}
@NonNull
private String getPostsLayoutPreferenceKey() {
switch (type) {
case LIKED:
asyncTask = new iLikedFetcher(endCursor, postsFetchListener);
break;
case SAVED:
return Constants.PREF_LIKED_POSTS_LAYOUT;
case TAGGED:
if (TextUtils.isEmpty(profileId)) return;
asyncTask = new PostsFetcher(profileId, type, endCursor, postsFetchListener);
break;
return Constants.PREF_TAGGED_POSTS_LAYOUT;
case SAVED:
default:
return;
return Constants.PREF_SAVED_POSTS_LAYOUT;
}
currentlyExecuting = asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public void onRefresh() {
if (lazyLoader != null) lazyLoader.resetState();
stopCurrentExecutor();
endCursor = null;
postsViewModel.getList().postValue(Collections.emptyList());
selectedItems.clear();
if (postsAdapter != null) {
// postsAdapter.isSelecting = false;
postsAdapter.notifyDataSetChanged();
}
binding.swipeRefreshLayout.setRefreshing(true);
fetchPosts();
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 8020 && grantResults[0] == PackageManager.PERMISSION_GRANTED && selectedItems.size() > 0) {
final Context context = getContext();
if (context == null) return;
DownloadUtils.batchDownload(context, null, DownloadMethod.DOWNLOAD_SAVED, selectedItems);
final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
final Context context = getContext();
if (context == null) return;
if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) {
if (downloadFeedModel == null) return;
DownloadUtils.showDownloadDialog(context, downloadFeedModel, downloadChildPosition);
downloadFeedModel = null;
downloadChildPosition = -1;
return;
}
}
public static void stopCurrentExecutor() {
if (currentlyExecuting != null) {
try {
currentlyExecuting.cancel(true);
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.MAIN_HELPER, "stopCurrentExecutor");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
if (requestCode == STORAGE_PERM_REQUEST_CODE_FOR_SELECTION && granted) {
DownloadUtils.download(context, ImmutableList.copyOf(selectedFeedModels));
binding.posts.endSelection();
}
}
@ -313,9 +365,6 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
if (actionBar == null) return;
final int titleRes;
switch (type) {
case SAVED:
titleRes = R.string.saved;
break;
case LIKED:
titleRes = R.string.liked;
break;
@ -323,24 +372,29 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
titleRes = R.string.tagged;
break;
default:
return; // no other types supported in this view
case SAVED:
titleRes = R.string.saved;
break;
}
actionBar.setTitle(titleRes);
actionBar.setSubtitle(username);
}
private boolean checkAndResetAction() {
if (!onBackPressedCallback.isEnabled() && actionMode == null) {
return false;
}
if (onBackPressedCallback.isEnabled()) {
onBackPressedCallback.setEnabled(false);
onBackPressedCallback.remove();
}
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
return true;
private void updateSwipeRefreshState() {
binding.swipeRefreshLayout.setRefreshing(binding.posts.isFetching());
}
private void navigateToProfile(final String username) {
final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle();
bundle.putString("username", username);
navController.navigate(R.id.action_global_profileFragment, bundle);
}
private void showPostsLayoutPreferences() {
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(
getPostsLayoutPreferenceKey(),
preferences -> new Handler().postDelayed(() -> binding.posts.setLayoutPreferences(preferences), 200));
fragment.show(getChildFragmentManager(), "posts_layout_preferences");
}
}

View File

@ -6,9 +6,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Animatable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.util.Pair;
@ -45,14 +43,16 @@ import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.LoadEventInfo;
import com.google.android.exoplayer2.source.MediaLoadData;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
@ -62,7 +62,7 @@ import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.adapters.StoriesAdapter;
import awais.instagrabber.asyncs.DownloadAsync;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.asyncs.QuizAction;
import awais.instagrabber.asyncs.RespondAction;
import awais.instagrabber.asyncs.SeenAction;
@ -81,7 +81,6 @@ import awais.instagrabber.models.stickers.PollModel;
import awais.instagrabber.models.stickers.QuestionModel;
import awais.instagrabber.models.stickers.QuizModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
@ -94,8 +93,6 @@ import awaisomereport.LogCollector;
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_THRESHOLD;
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Constants.MARK_AS_SEEN;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
@ -316,15 +313,7 @@ public class StoryViewerFragment extends Fragment {
final Object feedStoryModel = isRightSwipe
? finalModels.get(index - 1)
: finalModels.size() == index + 1 ? null : finalModels.get(index + 1);
if (feedStoryModel != null) {
if (fetching) {
Toast.makeText(context, R.string.be_patient, Toast.LENGTH_SHORT).show();
return;
}
fetching = true;
currentFeedStoryIndex = isRightSwipe ? (index - 1) : (index + 1);
resetView();
}
paginateStories(feedStoryModel, context, isRightSwipe, currentFeedStoryIndex == finalModels.size() - 2);
return;
}
if (isRightSwipe) {
@ -359,6 +348,15 @@ public class StoryViewerFragment extends Fragment {
return false;
}
};
if (hasFeedStories) {
binding.btnBackward.setVisibility(currentFeedStoryIndex == 0 ? View.INVISIBLE : View.VISIBLE);
binding.btnForward.setVisibility(currentFeedStoryIndex == finalModels.size() - 1 ? View.INVISIBLE : View.VISIBLE);
binding.btnBackward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex - 1), context, true, false));
binding.btnForward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex + 1), context, false,
currentFeedStoryIndex == finalModels.size() - 2));
}
binding.imageViewer.setTapListener(simpleOnGestureListener);
binding.spotify.setOnClickListener(v -> {
final Object tag = v.getTag();
@ -371,14 +369,19 @@ public class StoryViewerFragment extends Fragment {
binding.viewStoryPost.setOnClickListener(v -> {
final Object tag = v.getTag();
if (!(tag instanceof CharSequence)) return;
final String postId = tag.toString();
final boolean isId = tag.toString().matches("^[\\d]+$");
final String[] idsOrShortCodes = new String[]{postId};
final NavDirections action = HashTagFragmentDirections.actionGlobalPostViewFragment(
0,
idsOrShortCodes,
isId);
NavHostFragment.findNavController(this).navigate(action);
final String shortCode = tag.toString();
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setCancelable(false)
.setView(R.layout.dialog_opening_post)
.create();
alertDialog.show();
new PostFetcher(shortCode, feedModel -> {
final PostViewV2Fragment fragment = PostViewV2Fragment
.builder(feedModel)
.build();
fragment.setOnShowListener(dialog -> alertDialog.dismiss());
fragment.show(getChildFragmentManager(), "post_view");
}).execute();
});
final View.OnClickListener storyActionListener = v -> {
final Object tag = v.getTag();
@ -509,7 +512,7 @@ public class StoryViewerFragment extends Fragment {
}
storiesViewModel.getList().setValue(Collections.emptyList());
if (currentStoryMediaId == null) return;
final ServiceCallback storyCallback = new ServiceCallback<List<StoryModel>>() {
final ServiceCallback<List<StoryModel>> storyCallback = new ServiceCallback<List<StoryModel>>() {
@Override
public void onSuccess(final List<StoryModel> storyModels) {
fetching = false;
@ -604,40 +607,13 @@ public class StoryViewerFragment extends Fragment {
}
private void downloadStory() {
int error = 0;
final Context context = getContext();
if (context == null) return;
if (currentStory != null) {
File dir = new File(Environment.getExternalStorageDirectory(), "Download");
if (settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = settingsHelper.getString(FOLDER_PATH);
if (!TextUtils.isEmpty(customPath)) dir = new File(customPath);
}
if (settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(currentStoryUsername))
dir = new File(dir, currentStoryUsername);
if (dir.exists() || dir.mkdirs()) {
final String storyUrl = currentStory.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO
? currentStory.getVideoUrl()
: currentStory.getStoryUrl();
final File saveFile = new File(dir, currentStory.getStoryMediaId() + "_" + currentStory.getTimestamp()
+ DownloadUtils.getExtensionFromModel(storyUrl, currentStory));
new DownloadAsync(context, storyUrl, saveFile, result -> {
final int toastRes = result != null && result.exists() ? R.string.downloader_complete
: R.string.downloader_error_download_file;
Toast.makeText(context, toastRes, Toast.LENGTH_SHORT).show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else error = 1;
} else error = 2;
if (error == 1)
Toast.makeText(context, R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show();
else if (error == 2)
if (currentStory == null) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
DownloadUtils.download(context, currentStory);
}
private void setupImage() {
@ -687,8 +663,10 @@ public class StoryViewerFragment extends Fragment {
binding.playerView.setPlayer(player);
player.setPlayWhenReady(settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS));
final Uri uri = Uri.parse(url);
final MediaItem mediaItem = MediaItem.fromUri(uri);
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(context, "instagram"))
.createMediaSource(Uri.parse(url));
.createMediaSource(mediaItem);
mediaSource.addEventListener(new Handler(), new MediaSourceEventListener() {
@Override
public void onLoadCompleted(final int windowIndex,
@ -732,7 +710,8 @@ public class StoryViewerFragment extends Fragment {
binding.progressView.setVisibility(View.GONE);
}
});
player.prepare(mediaSource);
player.setMediaSource(mediaSource);
player.prepare();
binding.playerView.setOnClickListener(v -> {
if (player != null) {
@ -751,12 +730,10 @@ public class StoryViewerFragment extends Fragment {
if (t == '@') {
final NavDirections action = HashTagFragmentDirections.actionGlobalProfileFragment(username);
NavHostFragment.findNavController(this).navigate(action);
}
else if (t == '#') {
} else if (t == '#') {
final NavDirections action = HashTagFragmentDirections.actionGlobalHashTagFragment(username.substring(1));
NavHostFragment.findNavController(this).navigate(action);
}
else {
} else {
final NavDirections action = ProfileFragmentDirections.actionGlobalLocationFragment(username.split(" \\(")[1].replace(")", ""));
NavHostFragment.findNavController(this).navigate(action);
}
@ -768,4 +745,18 @@ public class StoryViewerFragment extends Fragment {
try { player.release(); } catch (Exception ignored) { }
player = null;
}
private void paginateStories(Object feedStory, Context context, boolean backward, boolean last) {
if (feedStory != null) {
if (fetching) {
Toast.makeText(context, R.string.be_patient, Toast.LENGTH_SHORT).show();
return;
}
fetching = true;
binding.btnBackward.setVisibility(currentFeedStoryIndex == 1 && backward ? View.INVISIBLE : View.VISIBLE);
binding.btnForward.setVisibility(last ? View.INVISIBLE : View.VISIBLE);
currentFeedStoryIndex = backward ? (currentFeedStoryIndex - 1) : (currentFeedStoryIndex + 1);
resetView();
}
}
}

View File

@ -0,0 +1,428 @@
package awais.instagrabber.fragments;
import android.animation.ArgbEvaluator;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.PermissionChecker;
import androidx.core.graphics.ColorUtils;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.transition.ChangeBounds;
import androidx.transition.TransitionInflater;
import androidx.transition.TransitionSet;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.imagepipeline.image.ImageInfo;
import com.google.common.collect.ImmutableList;
import java.util.Set;
import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.asyncs.DiscoverPostFetchService;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.helpers.NestedCoordinatorLayout;
import awais.instagrabber.databinding.FragmentTopicPostsBinding;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.fragments.main.DiscoverFragmentDirections;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.TopicCluster;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.DiscoverService;
import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030;
private MainActivity fragmentActivity;
private FragmentTopicPostsBinding binding;
private NestedCoordinatorLayout root;
private boolean shouldRefresh = true;
private TopicCluster topicCluster;
private ActionMode actionMode;
private Set<FeedModel> selectedFeedModels;
private FeedModel downloadFeedModel;
private int downloadChildPosition = -1;
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
binding.posts.endSelection();
}
};
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
R.menu.multi_select_download_menu, new PrimaryActionModeCallback.CallbacksHelper() {
@Override
public void onDestroy(final ActionMode mode) {
binding.posts.endSelection();
}
@Override
public boolean onActionItemClicked(final ActionMode mode,
final MenuItem item) {
if (item.getItemId() == R.id.action_download) {
if (TopicPostsFragment.this.selectedFeedModels == null) return false;
final Context context = getContext();
if (context == null) return false;
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
DownloadUtils.download(context, ImmutableList.copyOf(TopicPostsFragment.this.selectedFeedModels));
binding.posts.endSelection();
return true;
}
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
}
return false;
}
});
private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
@Override
public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {
openPostDialog(feedModel, profilePicView, mainPostImage, -1);
}
@Override
public void onSliderClick(final FeedModel feedModel, final int position) {
openPostDialog(feedModel, null, null, position);
}
@Override
public void onCommentsClick(final FeedModel feedModel) {
final NavDirections commentsAction = DiscoverFragmentDirections.actionGlobalCommentsViewerFragment(
feedModel.getShortCode(),
feedModel.getPostId(),
feedModel.getProfileModel().getId()
);
NavHostFragment.findNavController(TopicPostsFragment.this).navigate(commentsAction);
}
@Override
public void onDownloadClick(final FeedModel feedModel, final int childPosition) {
final Context context = getContext();
if (context == null) return;
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
return;
}
downloadFeedModel = feedModel;
downloadChildPosition = -1;
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
}
@Override
public void onHashtagClick(final String hashtag) {
final NavDirections action = DiscoverFragmentDirections.actionGlobalHashTagFragment(hashtag);
NavHostFragment.findNavController(TopicPostsFragment.this).navigate(action);
}
@Override
public void onLocationClick(final FeedModel feedModel) {
final NavDirections action = DiscoverFragmentDirections.actionGlobalLocationFragment(feedModel.getLocationId());
NavHostFragment.findNavController(TopicPostsFragment.this).navigate(action);
}
@Override
public void onMentionClick(final String mention) {
navigateToProfile(mention.trim());
}
@Override
public void onNameClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onProfilePicClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onURLClick(final String url) {
Utils.openURL(getContext(), url);
}
@Override
public void onEmailClick(final String emailId) {
Utils.openEmailAddress(getContext(), emailId);
}
private void openPostDialog(final FeedModel feedModel,
final View profilePicView,
final View mainPostImage,
final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(feedModel);
if (position >= 0) {
builder.setPosition(position);
}
final PostViewV2Fragment fragment = builder
.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage)
.build();
fragment.show(getChildFragmentManager(), "post_view");
}
};
private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() {
@Override
public void onSelectionStart() {
if (!onBackPressedCallback.isEnabled()) {
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
onBackPressedCallback.setEnabled(true);
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
}
if (actionMode == null) {
actionMode = fragmentActivity.startActionMode(multiSelectAction);
}
}
@Override
public void onSelectionChange(final Set<FeedModel> selectedFeedModels) {
final String title = getString(R.string.number_selected, selectedFeedModels.size());
if (actionMode != null) {
actionMode.setTitle(title);
}
TopicPostsFragment.this.selectedFeedModels = selectedFeedModels;
}
@Override
public void onSelectionEnd() {
if (onBackPressedCallback.isEnabled()) {
onBackPressedCallback.setEnabled(false);
onBackPressedCallback.remove();
}
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
}
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity();
final TransitionSet transitionSet = new TransitionSet();
transitionSet.addTransition(new ChangeBounds())
.addTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move))
.setDuration(200);
setSharedElementEnterTransition(transitionSet);
postponeEnterTransition();
setHasOptionsMenu(true);
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
if (root != null) {
shouldRefresh = false;
return root;
}
binding = FragmentTopicPostsBinding.inflate(inflater, container, false);
root = binding.getRoot();
return root;
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
binding.swipeRefreshLayout.setOnRefreshListener(this);
init();
shouldRefresh = false;
}
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.topic_posts_menu, menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == R.id.layout) {
showPostsLayoutPreferences();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onResume() {
super.onResume();
fragmentActivity.setToolbar(binding.toolbar);
}
@Override
public void onRefresh() {
binding.posts.refresh();
}
@Override
public void onDestroy() {
super.onDestroy();
resetToolbar();
}
@Override
public void onDestroyView() {
super.onDestroyView();
resetToolbar();
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
final Context context = getContext();
if (context == null) return;
if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) {
if (downloadFeedModel == null) return;
DownloadUtils.showDownloadDialog(context, downloadFeedModel, downloadChildPosition);
downloadFeedModel = null;
downloadChildPosition = -1;
return;
}
if (requestCode == STORAGE_PERM_REQUEST_CODE_FOR_SELECTION && granted) {
DownloadUtils.download(context, ImmutableList.copyOf(selectedFeedModels));
binding.posts.endSelection();
}
}
private void resetToolbar() {
fragmentActivity.resetToolbar();
}
private void init() {
if (getArguments() == null) return;
final TopicPostsFragmentArgs fragmentArgs = TopicPostsFragmentArgs.fromBundle(getArguments());
topicCluster = fragmentArgs.getTopicCluster();
setupToolbar(fragmentArgs.getTitleColor(), fragmentArgs.getBackgroundColor());
setupPosts();
}
private void setupToolbar(final int titleColor, final int backgroundColor) {
if (topicCluster == null) {
return;
}
binding.cover.setTransitionName("cover-" + topicCluster.getId());
fragmentActivity.setToolbar(binding.toolbar);
binding.collapsingToolbarLayout.setTitle(topicCluster.getTitle());
final int collapsedTitleTextColor = ColorUtils.setAlphaComponent(titleColor, 0xFF);
final int expandedTitleTextColor = ColorUtils.setAlphaComponent(titleColor, 0x99);
binding.collapsingToolbarLayout.setExpandedTitleColor(expandedTitleTextColor);
binding.collapsingToolbarLayout.setCollapsedTitleTextColor(collapsedTitleTextColor);
binding.collapsingToolbarLayout.setContentScrimColor(backgroundColor);
final Drawable navigationIcon = binding.toolbar.getNavigationIcon();
final Drawable overflowIcon = binding.toolbar.getOverflowIcon();
if (navigationIcon != null && overflowIcon != null) {
final Drawable navDrawable = navigationIcon.mutate();
final Drawable overflowDrawable = overflowIcon.mutate();
navDrawable.setAlpha(0xFF);
overflowDrawable.setAlpha(0xFF);
final ArgbEvaluator argbEvaluator = new ArgbEvaluator();
binding.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
final int totalScrollRange = appBarLayout.getTotalScrollRange();
final float current = totalScrollRange + verticalOffset;
final float fraction = current / totalScrollRange;
final int tempColor = (int) argbEvaluator.evaluate(fraction, collapsedTitleTextColor, expandedTitleTextColor);
navDrawable.setColorFilter(tempColor, PorterDuff.Mode.SRC_ATOP);
overflowDrawable.setColorFilter(tempColor, PorterDuff.Mode.SRC_ATOP);
});
}
final GradientDrawable gd = new GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
new int[]{Color.TRANSPARENT, backgroundColor});
binding.background.setBackground(gd);
setupCover();
}
private void setupCover() {
final String coverUrl = topicCluster.getCoverMedia().getDisplayUrl();
final DraweeController controller = Fresco
.newDraweeControllerBuilder()
.setOldController(binding.cover.getController())
.setUri(coverUrl)
.setControllerListener(new BaseControllerListener<ImageInfo>() {
@Override
public void onFailure(final String id, final Throwable throwable) {
super.onFailure(id, throwable);
startPostponedEnterTransition();
}
@Override
public void onFinalImageSet(final String id,
@Nullable final ImageInfo imageInfo,
@Nullable final Animatable animatable) {
startPostponedEnterTransition();
}
})
.build();
binding.cover.setController(controller);
}
private void setupPosts() {
final DiscoverService.TopicalExploreRequest topicalExploreRequest = new DiscoverService.TopicalExploreRequest();
topicalExploreRequest.setClusterId(topicCluster.getId());
binding.posts.setViewModelStoreOwner(this)
.setLifeCycleOwner(this)
.setPostFetchService(new DiscoverPostFetchService(topicalExploreRequest))
.setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_TOPIC_POSTS_LAYOUT)))
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
.setFeedItemCallback(feedItemCallback)
.setSelectionModeCallback(selectionModeCallback)
.init();
binding.swipeRefreshLayout.setRefreshing(true);
}
private void updateSwipeRefreshState() {
binding.swipeRefreshLayout.setRefreshing(binding.posts.isFetching());
}
private void navigateToProfile(final String username) {
final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle();
bundle.putString("username", username);
navController.navigate(R.id.action_global_profileFragment, bundle);
}
private void showPostsLayoutPreferences() {
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(
Constants.PREF_TOPIC_POSTS_LAYOUT,
preferences -> new Handler().postDelayed(() -> binding.posts.setLayoutPreferences(preferences), 200));
fragment.show(getChildFragmentManager(), "posts_layout_preferences");
}
}

View File

@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
@ -26,6 +27,8 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
@ -55,10 +58,12 @@ import awais.instagrabber.ProfileNavGraphDirections;
import awais.instagrabber.R;
import awais.instagrabber.adapters.DirectMessageItemsAdapter;
import awais.instagrabber.asyncs.ImageUploader;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.asyncs.direct_messages.DirectMessageInboxThreadFetcher;
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding;
import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.ImageUploadOptions;
@ -78,6 +83,7 @@ import awais.instagrabber.utils.Utils;
public class DirectMessageThreadFragment extends Fragment {
private static final String TAG = "DirectMessagesThreadFmt";
private static final int PICK_IMAGE = 100;
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private AppCompatActivity fragmentActivity;
private String threadId;
@ -169,8 +175,9 @@ public class DirectMessageThreadFragment extends Fragment {
binding.swipeRefreshLayout.setRefreshing(false);
}
};
private LinearLayout root;
private ConstraintLayout root;
private boolean shouldRefresh = true;
private DirectItemModel.DirectItemMediaModel downloadMediaItem;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -234,16 +241,14 @@ public class DirectMessageThreadFragment extends Fragment {
switch (itemType) {
case MEDIA_SHARE:
case CLIP:
final long postId = directItemModel.getMediaModel().getPk();
final boolean isId = true;
final NavController navController = NavHostFragment.findNavController(this);
final NavDirections action = DirectMessageThreadFragmentDirections
.actionGlobalPostViewFragment(
0,
new String[]{String.valueOf(postId)},
isId
);
navController.navigate(action);
case FELIX_SHARE:
final String shortCode = directItemModel.getMediaModel().getCode();
new PostFetcher(shortCode, feedModel -> {
final PostViewV2Fragment fragment = PostViewV2Fragment
.builder(feedModel)
.build();
fragment.show(getChildFragmentManager(), "post_view");
}).execute();
break;
case LINK:
Intent linkIntent = new Intent(Intent.ACTION_VIEW);
@ -258,18 +263,7 @@ public class DirectMessageThreadFragment extends Fragment {
break;
case RAVEN_MEDIA:
case MEDIA:
final ProfileModel user = getUser(directItemModel.getUserId());
final DirectItemModel.DirectItemMediaModel selectedItem =
itemType == DirectItemType.MEDIA ? directItemModel.getMediaModel() : directItemModel.getRavenMediaModel().getMedia();
final String url = selectedItem.getMediaType() == MediaItemType.MEDIA_TYPE_VIDEO
? selectedItem.getVideoUrl()
: selectedItem.getThumbUrl();
if (url == null) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
} else {
DownloadUtils.dmDownload(context, user.getUsername(), DownloadMethod.DOWNLOAD_DIRECT, selectedItem);
Toast.makeText(context, R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show();
}
downloadItem(context);
break;
case STORY_SHARE:
if (directItemModel.getReelShare() != null) {
@ -302,11 +296,13 @@ public class DirectMessageThreadFragment extends Fragment {
} else if (which == 1) {
sendText(null, directItemModel.getItemId(), directItemModel.isLiked());
} else if (which == 2) {
if (directItemModel == null)
if (directItemModel == null) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
else if (String.valueOf(directItemModel.getUserId()).equals(myId))
} else if (String.valueOf(directItemModel.getUserId()).equals(myId)) {
new ThreadAction().execute("delete", directItemModel.getItemId());
else searchUsername(getUser(directItemModel.getUserId()).getUsername());
} else {
searchUsername(getUser(directItemModel.getUserId()).getUsername());
}
}
};
final View.OnClickListener onClickListener = v -> {
@ -322,6 +318,7 @@ public class DirectMessageThreadFragment extends Fragment {
switch (itemType) {
case MEDIA_SHARE:
case CLIP:
case FELIX_SHARE:
firstOption = R.string.view_post;
break;
case LINK:
@ -371,6 +368,26 @@ public class DirectMessageThreadFragment extends Fragment {
}
}
private void downloadItem(final Context context) {
final ProfileModel user = getUser(directItemModel.getUserId());
final DirectItemModel.DirectItemMediaModel selectedItem = directItemModel.getItemType() == DirectItemType.MEDIA
? directItemModel.getMediaModel()
: directItemModel.getRavenMediaModel().getMedia();
final String url = selectedItem.getMediaType() == MediaItemType.MEDIA_TYPE_VIDEO
? selectedItem.getVideoUrl()
: selectedItem.getThumbUrl();
if (url == null) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
} else {
if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
DownloadUtils.dmDownload(context, user.getUsername(), selectedItem.getId(), url);
} else {
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
}
Toast.makeText(context, R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onResume() {
super.onResume();
@ -428,6 +445,17 @@ public class DirectMessageThreadFragment extends Fragment {
if (listViewModel != null) listViewModel.getList().postValue(Collections.emptyList());
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
final Context context = getContext();
if (context == null) return;
if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) {
downloadItem(context);
}
}
private void sendText(final String text, final String itemId, final boolean delete) {
DirectThreadBroadcaster.TextBroadcastOptions textOptions = null;
DirectThreadBroadcaster.ReactionBroadcastOptions reactionOptions = null;

View File

@ -1,126 +1,84 @@
package awais.instagrabber.fragments.main;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.FragmentNavigator;
import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.DiscoverAdapter;
import awais.instagrabber.asyncs.DiscoverFetcher;
import awais.instagrabber.asyncs.i.iTopicFetcher;
import awais.instagrabber.adapters.DiscoverTopicsAdapter;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentDiscoverBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.DiscoverItemModel;
import awais.instagrabber.models.DiscoverTopicModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.DiscoverItemViewModel;
import awais.instagrabber.viewmodels.TopicClusterViewModel;
import awais.instagrabber.webservices.DiscoverService;
import awais.instagrabber.webservices.ServiceCallback;
public class DiscoverFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "DiscoverFragment";
private MainActivity fragmentActivity;
private CoordinatorLayout root;
private FragmentDiscoverBinding binding;
private DiscoverAdapter discoverAdapter;
private RecyclerLazyLoader lazyLoader;
private boolean discoverHasMore = false;
private String[] topicIds;
private String rankToken;
private String currentTopic;
private String discoverEndMaxId;
private ActionMode actionMode;
private DiscoverItemViewModel discoverItemViewModel;
private TopicClusterViewModel topicClusterViewModel;
private boolean shouldRefresh = true;
private boolean isPullToRefresh;
private DiscoverService discoverService;
private final FetchListener<DiscoverTopicModel> topicFetchListener = new FetchListener<DiscoverTopicModel>() {
@Override
public void doBefore() {}
@Override
public void onResult(final DiscoverTopicModel result) {
if (result != null) {
topicIds = result.getIds();
rankToken = result.getToken();
final Context context = getContext();
if (context == null) return;
final ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(
context,
android.R.layout.simple_spinner_dropdown_item,
result.getNames()
);
binding.discoverType.setAdapter(spinnerArrayAdapter);
}
}
};
private final FetchListener<DiscoverItemModel[]> postsFetchListener = new FetchListener<DiscoverItemModel[]>() {
@Override
public void doBefore() {}
@Override
public void onResult(final DiscoverItemModel[] result) {
if (result == null || result.length <= 0) {
binding.discoverSwipeRefreshLayout.setRefreshing(false);
final Context context = getContext();
if (context == null) return;
Toast.makeText(context, R.string.discover_empty, Toast.LENGTH_SHORT).show();
return;
}
List<DiscoverItemModel> current = discoverItemViewModel.getList().getValue();
final List<DiscoverItemModel> resultList = Arrays.asList(result);
current = current == null ? new ArrayList<>() : new ArrayList<>(current); // copy to modifiable list
if (isPullToRefresh) {
current = resultList;
isPullToRefresh = false;
} else {
current.addAll(resultList);
}
discoverItemViewModel.getList().postValue(current);
binding.discoverSwipeRefreshLayout.setRefreshing(false);
final DiscoverItemModel discoverItemModel = result[result.length - 1];
if (discoverItemModel != null) {
discoverEndMaxId = discoverItemModel.getNextMaxId();
discoverHasMore = discoverItemModel.hasMore();
discoverItemModel.setMore(false, null);
}
}
};
// private final FetchListener<DiscoverItemModel[]> postsFetchListener = new FetchListener<DiscoverItemModel[]>() {
// @Override
// public void doBefore() {}
//
// @Override
// public void onResult(final DiscoverItemModel[] result) {
// if (result == null || result.length <= 0) {
// binding.swipeRefreshLayout.setRefreshing(false);
// final Context context = getContext();
// if (context == null) return;
// Toast.makeText(context, R.string.discover_empty, Toast.LENGTH_SHORT).show();
// return;
// }
// List<DiscoverItemModel> current = discoverItemViewModel.getList().getValue();
// final List<DiscoverItemModel> resultList = Arrays.asList(result);
// current = current == null ? new ArrayList<>() : new ArrayList<>(current); // copy to modifiable list
// if (isPullToRefresh) {
// current = resultList;
// isPullToRefresh = false;
// } else {
// current.addAll(resultList);
// }
// discoverItemViewModel.getList().postValue(current);
// binding.swipeRefreshLayout.setRefreshing(false);
// final DiscoverItemModel discoverItemModel = result[result.length - 1];
// if (discoverItemModel != null) {
// discoverEndMaxId = discoverItemModel.getNextMaxId();
// discoverHasMore = discoverItemModel.hasMore();
// discoverItemModel.setMore(false, null);
// }
// }
// };
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
setEnabled(false);
remove();
if (discoverAdapter == null) return;
discoverAdapter.clearSelection();
// if (discoverAdapter == null) return;
// discoverAdapter.clearSelection();
}
};
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
@ -134,14 +92,14 @@ public class DiscoverFragment extends Fragment implements SwipeRefreshLayout.OnR
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
if (item.getItemId() == R.id.action_download) {
if (discoverAdapter == null) return false;
final Context context = getContext();
if (context == null) return false;
DownloadUtils.batchDownload(context,
null,
DownloadMethod.DOWNLOAD_DISCOVER,
discoverAdapter.getSelectedModels());
checkAndResetAction();
// if (discoverAdapter == null) return false;
// final Context context = getContext();
// if (context == null) return false;
// DownloadUtils.batchDownload(context,
// null,
// DownloadMethod.DOWNLOAD_DISCOVER,
// discoverAdapter.getSelectedModels());
// checkAndResetAction();
return true;
}
return false;
@ -152,6 +110,7 @@ public class DiscoverFragment extends Fragment implements SwipeRefreshLayout.OnR
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity();
discoverService = DiscoverService.getInstance();
}
@Override
@ -170,96 +129,159 @@ public class DiscoverFragment extends Fragment implements SwipeRefreshLayout.OnR
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
binding.discoverSwipeRefreshLayout.setOnRefreshListener(this);
setupExplore();
binding.swipeRefreshLayout.setOnRefreshListener(this);
init();
shouldRefresh = false;
}
private void init() {
// setExitSharedElementCallback(new SharedElementCallback() {
// @Override
// public void onSharedElementsArrived(final List<String> sharedElementNames,
// final List<View> sharedElements,
// final OnSharedElementsReadyListener listener) {
// super.onSharedElementsArrived(sharedElementNames, sharedElements, listener);
// Log.d(TAG, "onSharedElementsArrived: sharedElementNames: " + sharedElementNames);
// }
//
// @Override
// public void onSharedElementEnd(final List<String> sharedElementNames,
// final List<View> sharedElements,
// final List<View> sharedElementSnapshots) {
// super.onSharedElementEnd(sharedElementNames, sharedElements, sharedElementSnapshots);
// Log.d(TAG, "onSharedElementEnd: sharedElementNames: " + sharedElementNames);
// }
//
// @Override
// public void onSharedElementStart(final List<String> sharedElementNames,
// final List<View> sharedElements,
// final List<View> sharedElementSnapshots) {
// super.onSharedElementStart(sharedElementNames, sharedElements, sharedElementSnapshots);
// Log.d(TAG, "onSharedElementStart: sharedElementNames: " + sharedElementNames);
// }
// });
setupTopics();
fetchTopics();
}
@Override
public void onRefresh() {
isPullToRefresh = true;
discoverEndMaxId = null;
lazyLoader.resetState();
fetchPosts();
}
private void setupExplore() {
discoverItemViewModel = new ViewModelProvider(fragmentActivity).get(DiscoverItemViewModel.class);
final Context context = getContext();
if (context == null) return;
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110));
binding.discoverPosts.setLayoutManager(layoutManager);
binding.discoverPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
binding.discoverType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (topicIds == null || topicIds.length <= 0) return;
currentTopic = topicIds[pos];
onRefresh();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {}
});
discoverAdapter = new DiscoverAdapter((model, position) -> {
if (discoverAdapter.isSelecting()) {
if (actionMode == null) return;
final String title = getString(R.string.number_selected, discoverAdapter.getSelectedModels().size());
actionMode.setTitle(title);
return;
}
if (checkAndResetAction()) return;
final List<DiscoverItemModel> discoverItemModels = discoverItemViewModel.getList().getValue();
if (discoverItemModels == null || discoverItemModels.size() == 0) return;
if (discoverItemModels.get(0) == null) return;
final String postId = discoverItemModels.get(0).getPostId();
final boolean isId = postId != null;
final String[] idsOrShortCodes = new String[discoverItemModels.size()];
for (int i = 0; i < discoverItemModels.size(); i++) {
idsOrShortCodes[i] = isId ? discoverItemModels.get(i).getPostId()
: discoverItemModels.get(i).getShortCode();
}
final NavDirections action = DiscoverFragmentDirections.actionGlobalPostViewFragment(
position,
idsOrShortCodes,
isId);
NavHostFragment.findNavController(this).navigate(action);
}, (model, position) -> {
if (!discoverAdapter.isSelecting()) {
checkAndResetAction();
return true;
}
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
if (onBackPressedCallback.isEnabled()) {
return true;
}
actionMode = fragmentActivity.startActionMode(multiSelectAction);
final String title = getString(R.string.number_selected, 1);
actionMode.setTitle(title);
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
return true;
});
binding.discoverPosts.setAdapter(discoverAdapter);
discoverItemViewModel.getList().observe(fragmentActivity, discoverAdapter::submitList);
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (discoverHasMore) {
fetchPosts();
}
}, 3);
binding.discoverPosts.addOnScrollListener(lazyLoader);
fetchTopics();
}
private void fetchTopics() {
new iTopicFetcher(topicFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
public void setupTopics() {
topicClusterViewModel = new ViewModelProvider(fragmentActivity).get(TopicClusterViewModel.class);
binding.topicsRecyclerView.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(2)));
final DiscoverTopicsAdapter adapter = new DiscoverTopicsAdapter((topicCluster, root, cover, title, titleColor, backgroundColor) -> {
final FragmentNavigator.Extras.Builder builder = new FragmentNavigator.Extras.Builder()
.addSharedElement(cover, "cover-" + topicCluster.getId());
// .addSharedElement(title, "title-" + topicCluster.getId());
final DiscoverFragmentDirections.ActionDiscoverFragmentToTopicPostsFragment action = DiscoverFragmentDirections
.actionDiscoverFragmentToTopicPostsFragment(topicCluster, titleColor, backgroundColor);
NavHostFragment.findNavController(this).navigate(action, builder.build());
});
binding.topicsRecyclerView.setAdapter(adapter);
topicClusterViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList);
}
private void fetchPosts() {
binding.discoverSwipeRefreshLayout.setRefreshing(true);
new DiscoverFetcher(currentTopic, discoverEndMaxId, rankToken, postsFetchListener, false)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
private void setupExplore() {
// discoverItemViewModel = new ViewModelProvider(fragmentActivity).get(DiscoverItemViewModel.class);
// final Context context = getContext();
// if (context == null) return;
// final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110));
// binding.postsRecyclerView.setLayoutManager(layoutManager);
// binding.postsRecyclerView.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
// binding.discoverType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
// @Override
// public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
// if (topicIds == null || topicIds.length <= 0) return;
// currentTopic = topicIds[pos];
// onRefresh();
// }
//
// @Override
// public void onNothingSelected(AdapterView<?> parent) {}
// });
// discoverAdapter = new DiscoverAdapter((model, position) -> {
// if (discoverAdapter.isSelecting()) {
// if (actionMode == null) return;
// final String title = getString(R.string.number_selected, discoverAdapter.getSelectedModels().size());
// actionMode.setTitle(title);
// return;
// }
// if (checkAndResetAction()) return;
// final List<DiscoverItemModel> discoverItemModels = discoverItemViewModel.getList().getValue();
// if (discoverItemModels == null || discoverItemModels.size() == 0) return;
// if (discoverItemModels.get(0) == null) return;
// final String postId = discoverItemModels.get(0).getPostId();
// final boolean isId = postId != null;
// final String[] idsOrShortCodes = new String[discoverItemModels.size()];
// for (int i = 0; i < discoverItemModels.size(); i++) {
// idsOrShortCodes[i] = isId ? discoverItemModels.get(i).getPostId()
// : discoverItemModels.get(i).getShortCode();
// }
// final NavDirections action = DiscoverFragmentDirections.actionGlobalPostViewFragment(
// position,
// idsOrShortCodes,
// isId);
// NavHostFragment.findNavController(this).navigate(action);
// }, (model, position) -> {
// if (!discoverAdapter.isSelecting()) {
// checkAndResetAction();
// return true;
// }
// final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
// if (onBackPressedCallback.isEnabled()) {
// return true;
// }
// actionMode = fragmentActivity.startActionMode(multiSelectAction);
// final String title = getString(R.string.number_selected, 1);
// actionMode.setTitle(title);
// onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
// return true;
// });
// binding.postsRecyclerView.setAdapter(discoverAdapter);
// discoverItemViewModel.getList().observe(fragmentActivity, discoverAdapter::submitList);
// lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
// if (discoverHasMore) {
// fetchPosts();
// }
// }, 3);
// binding.postsRecyclerView.addOnScrollListener(lazyLoader);
// binding.postsRecyclerView.setViewModelStoreOwner(this)
// .setLifeCycleOwner(this)
// .setPostFetchService(new DiscoverPostFetchService())
// .setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_PROFILE_POSTS_LAYOUT)))
// .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
// .setFeedItemCallback(feedItemCallback)
// .init();
// binding.swipeRefreshLayout.setRefreshing(true);
}
private void fetchTopics() {
// new iTopicFetcher(topicFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
binding.swipeRefreshLayout.setRefreshing(true);
discoverService.topicalExplore(new DiscoverService.TopicalExploreRequest(), new ServiceCallback<DiscoverService.TopicalExploreResponse>() {
@Override
public void onSuccess(final DiscoverService.TopicalExploreResponse result) {
topicClusterViewModel.getList().postValue(result.getClusters());
binding.swipeRefreshLayout.setRefreshing(false);
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "onFailure", t);
binding.swipeRefreshLayout.setRefreshing(false);
}
});
}
// private void fetchPosts() {
// binding.swipeRefreshLayout.setRefreshing(true);
// new DiscoverFetcher(currentTopic, discoverEndMaxId, rankToken, postsFetchListener, false)
// .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
// }
private boolean checkAndResetAction() {
if (!onBackPressedCallback.isEnabled() && actionMode == null) {
return false;

View File

@ -1,295 +1,245 @@
package awais.instagrabber.fragments.main;
import android.content.Context;
import android.content.DialogInterface;
import android.net.Uri;
import android.os.AsyncTask;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.facebook.common.executors.UiThreadImmediateExecutorService;
import com.facebook.datasource.BaseDataSubscriber;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.request.ImageRequest;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.FeedAdapter;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.adapters.FeedStoriesAdapter;
import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder;
import awais.instagrabber.asyncs.FeedFetcher;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.customviews.helpers.VideoAwareRecyclerScroller;
import awais.instagrabber.asyncs.FeedPostFetchService;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.databinding.FragmentFeedBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.FeedStoriesViewModel;
import awais.instagrabber.viewmodels.FeedViewModel;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService;
import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "FeedFragment";
private static final double MAX_VIDEO_HEIGHT = 0.9 * Utils.displayMetrics.heightPixels;
private static final int RESIZED_VIDEO_HEIGHT = (int) (0.8 * Utils.displayMetrics.heightPixels);
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030;
private MainActivity fragmentActivity;
private CoordinatorLayout root;
private FragmentFeedBinding binding;
private StoriesService storiesService;
private boolean feedHasNextPage = false;
private String feedEndCursor = null;
private FeedViewModel feedViewModel;
private VideoAwareRecyclerScroller videoAwareRecyclerScroller;
private boolean shouldRefresh = true;
private boolean isPullToRefresh;
private FeedStoriesViewModel feedStoriesViewModel;
private boolean storiesFetching;
private ActionMode actionMode;
private Set<FeedModel> selectedFeedModels;
private FeedModel downloadFeedModel;
private int downloadChildPosition = -1;
private final boolean shouldAutoPlay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
private final FetchListener<FeedModel[]> feedFetchListener = new FetchListener<FeedModel[]>() {
private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
@Override
public void doBefore() {
binding.feedSwipeRefreshLayout.post(() -> binding.feedSwipeRefreshLayout.setRefreshing(true));
public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {
openPostDialog(feedModel, profilePicView, mainPostImage, -1);
}
@Override
public void onResult(final FeedModel[] result) {
if (result == null || result.length <= 0) {
binding.feedSwipeRefreshLayout.setRefreshing(false);
public void onSliderClick(final FeedModel feedModel, final int position) {
openPostDialog(feedModel, null, null, position);
}
@Override
public void onCommentsClick(final FeedModel feedModel) {
final NavDirections commentsAction = FeedFragmentDirections.actionGlobalCommentsViewerFragment(
feedModel.getShortCode(),
feedModel.getPostId(),
feedModel.getProfileModel().getId()
);
NavHostFragment.findNavController(FeedFragment.this).navigate(commentsAction);
}
@Override
public void onDownloadClick(final FeedModel feedModel, final int childPosition) {
final Context context = getContext();
if (context == null) return;
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
return;
}
final List<FeedModel> currentFeedModelList = feedViewModel.getList().getValue();
final Map<String, FeedModel> thumbToFeedMap = new HashMap<>();
for (final FeedModel feedModel : result) {
thumbToFeedMap.put(feedModel.getThumbnailUrl(), feedModel);
}
final BaseDataSubscriber<Void> subscriber = new BaseDataSubscriber<Void>() {
int success = 0;
int failed = 0;
downloadFeedModel = feedModel;
downloadChildPosition = childPosition;
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
}
@Override
public void onHashtagClick(final String hashtag) {
final NavDirections action = FeedFragmentDirections.actionGlobalHashTagFragment(hashtag);
NavHostFragment.findNavController(FeedFragment.this).navigate(action);
}
@Override
public void onLocationClick(final FeedModel feedModel) {
final NavDirections action = FeedFragmentDirections.actionGlobalLocationFragment(feedModel.getLocationId());
NavHostFragment.findNavController(FeedFragment.this).navigate(action);
}
@Override
public void onMentionClick(final String mention) {
navigateToProfile(mention.trim());
}
@Override
public void onNameClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onProfilePicClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onURLClick(final String url) {
Utils.openURL(getContext(), url);
}
@Override
public void onEmailClick(final String emailId) {
Utils.openEmailAddress(getContext(), emailId);
}
private void openPostDialog(final FeedModel feedModel,
final View profilePicView,
final View mainPostImage,
final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(feedModel);
if (position >= 0) {
builder.setPosition(position);
}
final PostViewV2Fragment fragment = builder
.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage)
.build();
fragment.show(getChildFragmentManager(), "post_view");
}
};
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
binding.feedRecyclerView.endSelection();
}
};
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
R.menu.multi_select_download_menu,
new PrimaryActionModeCallback.CallbacksHelper() {
@Override
protected void onNewResultImpl(@NonNull final DataSource<Void> dataSource) {
final Map<String, Object> extras = dataSource.getExtras();
if (extras == null) return;
final Uri thumbUri = (Uri) extras.get("uri_source");
if (thumbUri == null) return;
final Integer encodedWidth = (Integer) extras.get("encoded_width");
final Integer encodedHeight = (Integer) extras.get("encoded_height");
if (encodedWidth == null || encodedHeight == null) return;
final FeedModel feedModel = thumbToFeedMap.get(thumbUri.toString());
if (feedModel == null) return;
int requiredWidth = Utils.displayMetrics.widthPixels;
int resultingHeight = NumberUtils
.getResultingHeight(requiredWidth, encodedHeight, encodedWidth);
if (feedModel
.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO && resultingHeight >= MAX_VIDEO_HEIGHT) {
// If its a video and the height is too large, need to reduce the height,
// so that entire video fits on screen
resultingHeight = RESIZED_VIDEO_HEIGHT;
requiredWidth = NumberUtils.getResultingWidth(RESIZED_VIDEO_HEIGHT,
resultingHeight,
requiredWidth);
}
feedModel.setImageWidth(requiredWidth);
feedModel.setImageHeight(resultingHeight);
success++;
updateAdapter();
public void onDestroy(final ActionMode mode) {
binding.feedRecyclerView.endSelection();
}
@Override
protected void onFailureImpl(@NonNull final DataSource<Void> dataSource) {
failed++;
updateAdapter();
}
public void updateAdapter() {
if (failed + success != result.length) return;
List<FeedModel> finalList = currentFeedModelList == null || currentFeedModelList.isEmpty()
? new ArrayList<>()
: new ArrayList<>(currentFeedModelList);
final List<FeedModel> resultList = Arrays.asList(result);
if (isPullToRefresh) {
finalList = resultList;
isPullToRefresh = false;
} else {
finalList.addAll(resultList);
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
if (item.getItemId() == R.id.action_download) {
if (FeedFragment.this.selectedFeedModels == null) return false;
final Context context = getContext();
if (context == null) return false;
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
DownloadUtils.download(context, ImmutableList.copyOf(FeedFragment.this.selectedFeedModels));
binding.feedRecyclerView.endSelection();
return true;
}
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
return true;
}
feedViewModel.getList().postValue(finalList);
final PostModel feedPostModel = result[result.length - 1];
if (feedPostModel != null) {
feedEndCursor = feedPostModel.getEndCursor();
feedHasNextPage = feedPostModel.hasNextPage();
feedPostModel.setPageCursor(false, null);
}
binding.feedSwipeRefreshLayout.setRefreshing(false);
return false;
}
};
});
private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() {
for (final FeedModel feedModel : result) {
final DataSource<Void> ds = Fresco.getImagePipeline()
.prefetchToBitmapCache(ImageRequest.fromUri(feedModel.getThumbnailUrl()), null);
ds.subscribe(subscriber, UiThreadImmediateExecutorService.getInstance());
@Override
public void onSelectionStart() {
if (!onBackPressedCallback.isEnabled()) {
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
onBackPressedCallback.setEnabled(true);
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
}
if (actionMode == null) {
actionMode = fragmentActivity.startActionMode(multiSelectAction);
}
}
@Override
public void onSelectionChange(final Set<FeedModel> selectedFeedModels) {
final String title = getString(R.string.number_selected, selectedFeedModels.size());
if (actionMode != null) {
actionMode.setTitle(title);
}
FeedFragment.this.selectedFeedModels = selectedFeedModels;
}
@Override
public void onSelectionEnd() {
if (onBackPressedCallback.isEnabled()) {
onBackPressedCallback.setEnabled(false);
onBackPressedCallback.remove();
}
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
}
};
private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> {
if (isHashtag) {
final NavDirections action = FeedFragmentDirections.actionGlobalHashTagFragment(text);
NavHostFragment.findNavController(this).navigate(action);
return;
}
if (isLocation) {
final NavDirections action = FeedFragmentDirections.actionGlobalLocationFragment(text);
NavHostFragment.findNavController(this).navigate(action);
return;
}
final NavDirections action = FeedFragmentDirections.actionGlobalProfileFragment("@" + text);
NavHostFragment.findNavController(this).navigate(action);
};
private final View.OnClickListener postViewClickListener = v -> {
final Object tag = v.getTag();
if (!(tag instanceof FeedModel)) return;
final FeedModel feedModel = (FeedModel) tag;
if (v instanceof RamboTextView) {
if (feedModel.isMentionClicked()) feedModel.toggleCaption();
feedModel.setMentionClicked(false);
if (!FeedItemViewHolder.expandCollapseTextView((RamboTextView) v, feedModel.getPostCaption()))
feedModel.toggleCaption();
return;
}
final int id = v.getId();
switch (id) {
case R.id.btnComments:
final NavDirections commentsAction = FeedFragmentDirections.actionGlobalCommentsViewerFragment(
feedModel.getShortCode(),
feedModel.getPostId(),
feedModel.getProfileModel().getId()
);
NavHostFragment.findNavController(this).navigate(commentsAction);
break;
case R.id.viewStoryPost:
final List<FeedModel> feedModels = feedViewModel.getList().getValue();
if (feedModels == null || feedModels.size() == 0) return;
if (feedModels.get(0) == null) return;
final String postId = feedModels.get(0).getPostId();
final boolean isId = postId != null;
final String[] idsOrShortCodes = new String[feedModels.size()];
for (int i = 0; i < feedModels.size(); i++) {
idsOrShortCodes[i] = isId ? feedModels.get(i).getPostId()
: feedModels.get(i).getShortCode();
}
final NavDirections action = FeedFragmentDirections.actionGlobalPostViewFragment(
feedModel.getPosition(),
idsOrShortCodes,
isId);
NavHostFragment.findNavController(this).navigate(action);
break;
case R.id.btnDownload:
ProfileModel profileModel = feedModel.getProfileModel();
final String username = profileModel != null ? profileModel.getUsername() : null;
final ViewerPostModel[] sliderItems = feedModel.getSliderItems();
final Context context = getContext();
if (context == null) return;
if (feedModel
.getItemType() != MediaItemType.MEDIA_TYPE_SLIDER || sliderItems == null || sliderItems.length == 1)
DownloadUtils.batchDownload(context,
username,
DownloadMethod.DOWNLOAD_FEED,
Collections.singletonList(feedModel));
else {
final ArrayList<BasePostModel> postModels = new ArrayList<>();
final DialogInterface.OnClickListener clickListener1 = (dialog, which) -> {
postModels.clear();
final boolean breakWhenFoundSelected = which == DialogInterface.BUTTON_POSITIVE;
for (final ViewerPostModel sliderItem : sliderItems) {
if (sliderItem != null) {
if (!breakWhenFoundSelected) postModels.add(sliderItem);
else if (sliderItem.isSelected()) {
postModels.add(sliderItem);
break;
}
}
}
// shows 0 items on first item of viewpager cause onPageSelected hasn't been called yet
if (breakWhenFoundSelected && postModels.size() == 0) {
postModels.add(sliderItems[0]);
}
if (postModels.size() > 0) {
DownloadUtils.batchDownload(context,
username,
DownloadMethod.DOWNLOAD_FEED,
postModels);
}
};
new AlertDialog.Builder(context)
.setTitle(R.string.post_viewer_download_dialog_title).setPositiveButton(
R.string.post_viewer_download_current,
clickListener1)
.setNegativeButton(R.string.post_viewer_download_album, clickListener1)
.show();
}
break;
case R.id.ivProfilePic:
profileModel = feedModel.getProfileModel();
if (profileModel != null) mentionClickListener.onClick(null, profileModel.getUsername(), false, false);
break;
}
};
private FeedStoriesViewModel feedStoriesViewModel;
private void navigateToProfile(final String username) {
final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle();
bundle.putString("username", username);
navController.navigate(R.id.action_global_profileFragment, bundle);
}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity();
storiesService = StoriesService.getInstance();
// feedService = FeedService.getInstance();
setHasOptionsMenu(true);
}
@Override
@ -314,58 +264,80 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
shouldRefresh = false;
}
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.feed_menu, menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == R.id.layout) {
showPostsLayoutPreferences();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onPause() {
super.onPause();
if (videoAwareRecyclerScroller != null) {
videoAwareRecyclerScroller.stopPlaying();
}
// if (videoAwareRecyclerScroller != null) {
// videoAwareRecyclerScroller.stopPlaying();
// }
}
@Override
public void onResume() {
super.onResume();
binding.feedSwipeRefreshLayout.setRefreshing(false);
if (videoAwareRecyclerScroller != null && shouldAutoPlay) {
videoAwareRecyclerScroller.startPlaying();
}
// if (videoAwareRecyclerScroller != null && shouldAutoPlay) {
// videoAwareRecyclerScroller.startPlaying();
// }
}
@Override
public void onRefresh() {
isPullToRefresh = true;
feedEndCursor = null;
fetchFeed();
binding.feedRecyclerView.refresh();
fetchStories();
}
private void setupFeed() {
feedViewModel = new ViewModelProvider(fragmentActivity).get(FeedViewModel.class);
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
final Context context = getContext();
if (context == null) return;
final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
binding.feedRecyclerView.setLayoutManager(layoutManager);
binding.feedRecyclerView.setHasFixedSize(true);
final FeedAdapter feedAdapter = new FeedAdapter(postViewClickListener, mentionClickListener);
feedAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
binding.feedRecyclerView.setAdapter(feedAdapter);
feedViewModel.getList().observe(fragmentActivity, feedAdapter::submitList);
final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (feedHasNextPage) {
fetchFeed();
}
});
if (shouldAutoPlay) {
videoAwareRecyclerScroller = new VideoAwareRecyclerScroller();
binding.feedRecyclerView.addOnScrollListener(videoAwareRecyclerScroller);
if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) {
if (downloadFeedModel == null) return;
DownloadUtils.showDownloadDialog(context, downloadFeedModel, downloadChildPosition);
downloadFeedModel = null;
downloadChildPosition = -1;
return;
}
if (requestCode == STORAGE_PERM_REQUEST_CODE_FOR_SELECTION && granted) {
DownloadUtils.download(context, ImmutableList.copyOf(selectedFeedModels));
binding.feedRecyclerView.endSelection();
}
binding.feedRecyclerView.addOnScrollListener(lazyLoader);
fetchFeed();
}
private void fetchFeed() {
private void setupFeed() {
binding.feedRecyclerView.setViewModelStoreOwner(this)
.setLifeCycleOwner(this)
.setPostFetchService(new FeedPostFetchService())
.setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_POSTS_LAYOUT)))
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
.setFeedItemCallback(feedItemCallback)
.setSelectionModeCallback(selectionModeCallback)
.init();
binding.feedSwipeRefreshLayout.setRefreshing(true);
new FeedFetcher(feedEndCursor, feedFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
// if (shouldAutoPlay) {
// videoAwareRecyclerScroller = new VideoAwareRecyclerScroller();
// binding.feedRecyclerView.addOnScrollListener(videoAwareRecyclerScroller);
// }
}
private void updateSwipeRefreshState() {
binding.feedSwipeRefreshLayout.setRefreshing(binding.feedRecyclerView.isFetching() || storiesFetching);
}
private void setupFeedStories() {
@ -383,16 +355,34 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
}
private void fetchStories() {
storiesFetching = true;
updateSwipeRefreshState();
storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() {
@Override
public void onSuccess(final List<FeedStoryModel> result) {
feedStoriesViewModel.getList().postValue(result);
storiesFetching = false;
updateSwipeRefreshState();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "failed", t);
storiesFetching = false;
updateSwipeRefreshState();
}
});
}
private void showPostsLayoutPreferences() {
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(
Constants.PREF_POSTS_LAYOUT,
preferences -> new Handler().postDelayed(() -> binding.feedRecyclerView.setLayoutPreferences(preferences), 200));
fragment.show(getChildFragmentManager(), "posts_layout_preferences");
}
public void scrollToTop() {
binding.feedRecyclerView.smoothScrollToPosition(0);
binding.storiesContainer.setExpanded(true);
}
}

View File

@ -2,13 +2,13 @@ package awais.instagrabber.fragments.main;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.SpannableStringBuilder;
import android.text.method.LinkMovementMethod;
import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.util.Log;
@ -19,7 +19,6 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
@ -29,10 +28,12 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
@ -41,36 +42,36 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import awais.instagrabber.ProfileNavGraphDirections;
import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.adapters.HighlightsAdapter;
import awais.instagrabber.adapters.PostsAdapter;
import awais.instagrabber.asyncs.HighlightsFetcher;
import awais.instagrabber.asyncs.PostsFetcher;
import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.asyncs.ProfilePostFetchService;
import awais.instagrabber.asyncs.UsernameFetcher;
import awais.instagrabber.asyncs.direct_messages.CreateThreadAction;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.PrimaryActionModeCallback.CallbacksHelper;
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.customviews.helpers.NestedCoordinatorLayout;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentProfileBinding;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.dialogs.ProfilePicDialogFragment;
import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse;
@ -82,17 +83,19 @@ import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.HighlightsViewModel;
import awais.instagrabber.viewmodels.PostsViewModel;
import awais.instagrabber.webservices.FriendshipService;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.fragments.HashTagFragment.ARG_HASHTAG;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "ProfileFragment";
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030;
private MainActivity fragmentActivity;
private CoordinatorLayout root;
@ -101,21 +104,21 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private String cookie;
private String username;
private ProfileModel profileModel;
private PostsViewModel postsViewModel;
private PostsAdapter postsAdapter;
private ActionMode actionMode;
private Handler usernameSettingHandler;
private FriendshipService friendshipService;
private StoriesService storiesService;
private boolean shouldRefresh = true, hasStories = false;
private boolean hasNextPage;
private String endCursor;
private AsyncTask<Void, Void, List<PostModel>> currentlyExecuting;
private boolean isPullToRefresh;
private boolean shouldRefresh = true;
private boolean hasStories = false;
private HighlightsAdapter highlightsAdapter;
private HighlightsViewModel highlightsViewModel;
private MenuItem blockMenuItem;
private MenuItem restrictMenuItem;
private boolean highlightsFetching;
private boolean postsSetupDone = false;
private Set<FeedModel> selectedFeedModels;
private FeedModel downloadFeedModel;
private int downloadChildPosition = -1;
private final Runnable usernameSettingRunnable = () -> {
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
@ -129,10 +132,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
setEnabled(false);
remove();
if (postsAdapter == null) return;
postsAdapter.clearSelection();
binding.postsRecyclerView.endSelection();
}
};
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
@ -140,58 +140,26 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
new CallbacksHelper() {
@Override
public void onDestroy(final ActionMode mode) {
onBackPressedCallback.handleOnBackPressed();
binding.postsRecyclerView.endSelection();
}
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
if (item.getItemId() == R.id.action_download) {
if (postsAdapter == null || username == null) {
return false;
}
if (ProfileFragment.this.selectedFeedModels == null) return false;
final Context context = getContext();
if (context == null) return false;
DownloadUtils.batchDownload(context,
username,
DownloadMethod.DOWNLOAD_MAIN,
postsAdapter.getSelectedModels());
checkAndResetAction();
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
DownloadUtils.download(context, ImmutableList.copyOf(ProfileFragment.this.selectedFeedModels));
binding.postsRecyclerView.endSelection();
return true;
}
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
return true;
}
return false;
}
});
private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
@Override
public void onResult(final List<PostModel> result) {
binding.swipeRefreshLayout.setRefreshing(false);
if (result == null || result.isEmpty()) {
binding.privatePage1.setImageResource(R.drawable.ic_cancel);
binding.privatePage2.setText(R.string.empty_acc);
binding.privatePage.setVisibility(View.VISIBLE);
return;
}
else {
binding.privatePage.setVisibility(View.GONE);
}
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
final List<PostModel> postModels = postsViewModel.getList().getValue();
List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>()
: new ArrayList<>(postModels);
if (isPullToRefresh) {
finalList = result;
isPullToRefresh = false;
} else {
finalList.addAll(result);
}
postsViewModel.getList().postValue(finalList);
final PostModel lastPostModel = result.get(result.size() - 1);
if (lastPostModel == null) return;
endCursor = lastPostModel.getEndCursor();
hasNextPage = lastPostModel.hasNextPage();
lastPostModel.setPageCursor(false, null);
}
};
private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> {
Log.d(TAG, "action...");
if (isHashtag) {
@ -210,6 +178,128 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
action.setUsername("@" + text);
NavHostFragment.findNavController(this).navigate(action);
};
private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
@Override
public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {
openPostDialog(feedModel, profilePicView, mainPostImage, -1);
}
@Override
public void onSliderClick(final FeedModel feedModel, final int position) {
openPostDialog(feedModel, null, null, position);
}
@Override
public void onCommentsClick(final FeedModel feedModel) {
final NavDirections commentsAction = FeedFragmentDirections.actionGlobalCommentsViewerFragment(
feedModel.getShortCode(),
feedModel.getPostId(),
feedModel.getProfileModel().getId()
);
NavHostFragment.findNavController(ProfileFragment.this).navigate(commentsAction);
}
@Override
public void onDownloadClick(final FeedModel feedModel, final int childPosition) {
final Context context = getContext();
if (context == null) return;
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
DownloadUtils.showDownloadDialog(context, feedModel, childPosition);
return;
}
downloadFeedModel = feedModel;
downloadChildPosition = childPosition;
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
}
@Override
public void onHashtagClick(final String hashtag) {
final NavDirections action = FeedFragmentDirections.actionGlobalHashTagFragment(hashtag);
NavHostFragment.findNavController(ProfileFragment.this).navigate(action);
}
@Override
public void onLocationClick(final FeedModel feedModel) {
final NavDirections action = FeedFragmentDirections.actionGlobalLocationFragment(feedModel.getLocationId());
NavHostFragment.findNavController(ProfileFragment.this).navigate(action);
}
@Override
public void onMentionClick(final String mention) {
navigateToProfile(mention.trim());
}
@Override
public void onNameClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onProfilePicClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onURLClick(final String url) {
Utils.openURL(getContext(), url);
}
@Override
public void onEmailClick(final String emailId) {
Utils.openEmailAddress(getContext(), emailId);
}
private void openPostDialog(final FeedModel feedModel,
final View profilePicView,
final View mainPostImage,
final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(feedModel);
if (position >= 0) {
builder.setPosition(position);
}
final PostViewV2Fragment fragment = builder
.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage)
.build();
fragment.show(getChildFragmentManager(), "post_view");
}
};
private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() {
@Override
public void onSelectionStart() {
if (!onBackPressedCallback.isEnabled()) {
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
onBackPressedCallback.setEnabled(true);
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
}
if (actionMode == null) {
actionMode = fragmentActivity.startActionMode(multiSelectAction);
}
}
@Override
public void onSelectionChange(final Set<FeedModel> selectedFeedModels) {
final String title = getString(R.string.number_selected, selectedFeedModels.size());
if (actionMode != null) {
actionMode.setTitle(title);
}
ProfileFragment.this.selectedFeedModels = selectedFeedModels;
}
@Override
public void onSelectionEnd() {
if (onBackPressedCallback.isEnabled()) {
onBackPressedCallback.setEnabled(false);
onBackPressedCallback.remove();
}
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
}
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -264,7 +354,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.profile_menu, menu);
// favMenuItem = menu.findItem(R.id.favourites);
blockMenuItem = menu.findItem(R.id.block);
if (blockMenuItem != null) {
blockMenuItem.setVisible(false);
@ -277,6 +366,10 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == R.id.layout) {
showPostsLayoutPreferences();
return true;
}
if (item.getItemId() == R.id.restrict) {
if (!isLoggedIn) return false;
final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict";
@ -343,10 +436,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override
public void onRefresh() {
isPullToRefresh = true;
endCursor = null;
fetchProfileDetails();
fetchPosts();
}
@Override
@ -355,14 +445,30 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
if (usernameSettingHandler != null) {
usernameSettingHandler.removeCallbacks(usernameSettingRunnable);
}
if (postsViewModel != null) {
postsViewModel.getList().postValue(Collections.emptyList());
}
if (highlightsViewModel != null) {
highlightsViewModel.getList().postValue(Collections.emptyList());
}
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
final Context context = getContext();
if (context == null) return;
if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) {
if (downloadFeedModel == null) return;
DownloadUtils.showDownloadDialog(context, downloadFeedModel, downloadChildPosition);
downloadFeedModel = null;
downloadChildPosition = -1;
return;
}
if (requestCode == STORAGE_PERM_REQUEST_CODE_FOR_SELECTION && granted) {
DownloadUtils.download(context, ImmutableList.copyOf(selectedFeedModels));
binding.postsRecyclerView.endSelection();
}
}
private void init() {
if (getArguments() != null) {
final ProfileFragmentArgs fragmentArgs = ProfileFragmentArgs.fromBundle(getArguments());
@ -381,7 +487,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
return;
}
binding.swipeRefreshLayout.setEnabled(true);
setupPosts();
setupHighlights();
setupCommonListeners();
fetchUsername();
@ -416,7 +521,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private void fetchProfileDetails() {
if (TextUtils.isEmpty(username)) return;
new ProfileFetcher(username.substring(1), profileModel -> {
new ProfileFetcher(username.trim().substring(1), profileModel -> {
if (getContext() == null) return;
this.profileModel = profileModel;
// final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
@ -440,87 +545,18 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show();
return;
}
if (!postsSetupDone) {
setupPosts();
} else {
binding.postsRecyclerView.refresh();
}
binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
final String profileId = profileModel.getId();
final String myId = CookieUtils.getUserIdFromCookie(cookie);
if (isLoggedIn) {
storiesService.getUserStory(profileId,
profileModel.getUsername(),
false,
false,
false,
new ServiceCallback<List<StoryModel>>() {
@Override
public void onSuccess(final List<StoryModel> storyModels) {
if (storyModels != null && !storyModels.isEmpty()) {
binding.mainProfileImage.setStoriesBorder();
hasStories = true;
}
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
}
});
new HighlightsFetcher(profileId,
result -> {
if (result != null) {
binding.highlightsList.setVisibility(View.VISIBLE);
highlightsViewModel.getList().postValue(result);
} else binding.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if (profileId.equals(myId)) {
binding.btnTagged.setVisibility(View.VISIBLE);
binding.btnSaved.setVisibility(View.VISIBLE);
binding.btnLiked.setVisibility(View.VISIBLE);
binding.btnDM.setVisibility(View.GONE);
binding.btnSaved.setText(R.string.saved);
} else {
binding.btnTagged.setVisibility(View.GONE);
binding.btnSaved.setVisibility(View.GONE);
binding.btnLiked.setVisibility(View.GONE);
binding.btnDM.setVisibility(View.VISIBLE); // maybe there is a judgment mechanism?
binding.btnFollow.setVisibility(View.VISIBLE);
if (profileModel.getFollowing()) {
binding.btnFollow.setText(R.string.unfollow);
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
} else if (profileModel.getRequested()) {
binding.btnFollow.setText(R.string.cancel);
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
} else {
binding.btnFollow.setText(R.string.follow);
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_24);
}
if (restrictMenuItem != null) {
restrictMenuItem.setVisible(true);
if (profileModel.getRestricted()) {
restrictMenuItem.setTitle(R.string.unrestrict);
} else {
restrictMenuItem.setTitle(R.string.restrict);
}
}
binding.btnTagged.setVisibility(profileModel.isReallyPrivate() ? View.GONE : View.VISIBLE);
if (blockMenuItem != null) {
blockMenuItem.setVisible(true);
if (profileModel.getBlocked()) {
blockMenuItem.setTitle(R.string.unblock);
} else {
blockMenuItem.setTitle(R.string.block);
}
}
}
} else {
if (!profileModel.isReallyPrivate() && restrictMenuItem != null) {
restrictMenuItem.setVisible(true);
if (profileModel.getRestricted()) {
restrictMenuItem.setTitle(R.string.unrestrict);
} else {
restrictMenuItem.setTitle(R.string.restrict);
}
}
fetchStoryAndHighlights(profileId);
}
setupButtons(profileId, myId);
if (!profileId.equals(myId)) {
binding.favCb.setVisibility(View.VISIBLE);
final boolean isFav = Utils.dataBox.getFavorite(username.substring(1), FavoriteType.USER) != null;
@ -529,7 +565,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} else {
binding.favCb.setVisibility(View.GONE);
}
binding.mainProfileImage.setImageURI(profileModel.getSdProfilePic());
binding.mainProfileImage.setImageURI(profileModel.getHdProfilePic());
final long followersCount = profileModel.getFollowersCount();
final long followingCount = profileModel.getFollowingCount();
@ -561,35 +597,40 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
binding.mainFullName.setText(TextUtils.isEmpty(profileModel.getName()) ? profileModel.getUsername()
: profileModel.getName());
CharSequence biography = profileModel.getBiography();
binding.mainBiography.setCaptionIsExpandable(true);
binding.mainBiography.setCaptionIsExpanded(true);
if (TextUtils.hasMentions(biography)) {
biography = TextUtils.getMentionText(biography);
binding.mainBiography.setText(biography, TextView.BufferType.SPANNABLE);
binding.mainBiography.setMentionClickListener(mentionClickListener);
} else {
final String biography = profileModel.getBiography();
if (!TextUtils.isEmpty(biography)) {
binding.mainBiography.setText(biography);
binding.mainBiography.setMentionClickListener(null);
binding.mainBiography.addOnHashtagListener(autoLinkItem -> {
final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle();
final String originalText = autoLinkItem.getOriginalText().trim();
bundle.putString(ARG_HASHTAG, originalText);
navController.navigate(R.id.action_global_hashTagFragment, bundle);
});
binding.mainBiography.addOnMentionClickListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText().trim();
navigateToProfile(originalText);
});
binding.mainBiography.addOnEmailClickListener(autoLinkItem -> Utils.openEmailAddress(getContext(),
autoLinkItem.getOriginalText().trim()));
binding.mainBiography.addOnURLClickListener(autoLinkItem -> Utils.openURL(getContext(), autoLinkItem.getOriginalText().trim()));
binding.mainBiography.setOnLongClickListener(v -> {
if (context != null) Utils.copyText(context, biography);
return true;
});
}
binding.mainBiography.setOnLongClickListener(v -> {
if (context != null) Utils.copyText(context, profileModel.getBiography());
return true;
});
final String url = profileModel.getUrl();
if (TextUtils.isEmpty(url)) {
binding.mainUrl.setVisibility(View.GONE);
} else {
binding.mainUrl.setVisibility(View.VISIBLE);
binding.mainUrl.setText(TextUtils.getSpannableUrl(url));
binding.mainUrl.setMovementMethod(LinkMovementMethod.getInstance());
binding.mainUrl.setText(url);
binding.mainUrl.addOnURLClickListener(autoLinkItem -> Utils.openURL(getContext(), autoLinkItem.getOriginalText().trim()));
binding.mainUrl.setOnLongClickListener(v -> {
if (context != null) Utils.copyText(context, url);
return true;
});
}
binding.mainFullName.setSelected(true);
binding.mainBiography.setEnabled(true);
binding.mainBiography.setClickable(true);
if (!profileModel.isReallyPrivate()) {
binding.mainFollowing.setClickable(true);
binding.mainFollowers.setClickable(true);
@ -608,7 +649,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
binding.swipeRefreshLayout.setRefreshing(true);
binding.mainPosts.setVisibility(View.VISIBLE);
binding.postsRecyclerView.setVisibility(View.VISIBLE);
fetchPosts();
} else {
binding.mainFollowers.setClickable(false);
@ -618,15 +659,97 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
binding.privatePage1.setImageResource(R.drawable.lock);
binding.privatePage2.setText(R.string.priv_acc);
binding.privatePage.setVisibility(View.VISIBLE);
binding.mainPosts.setVisibility(View.GONE);
binding.postsRecyclerView.setVisibility(View.GONE);
}
}
private void setupButtons(final String profileId, final String myId) {
if (isLoggedIn) {
if (profileId.equals(myId)) {
binding.btnTagged.setVisibility(View.VISIBLE);
binding.btnSaved.setVisibility(View.VISIBLE);
binding.btnLiked.setVisibility(View.VISIBLE);
binding.btnDM.setVisibility(View.GONE);
binding.btnSaved.setText(R.string.saved);
return;
}
binding.btnTagged.setVisibility(View.GONE);
binding.btnSaved.setVisibility(View.GONE);
binding.btnLiked.setVisibility(View.GONE);
binding.btnDM.setVisibility(View.VISIBLE); // maybe there is a judgment mechanism?
binding.btnFollow.setVisibility(View.VISIBLE);
if (profileModel.getFollowing()) {
binding.btnFollow.setText(R.string.unfollow);
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
} else if (profileModel.getRequested()) {
binding.btnFollow.setText(R.string.cancel);
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
} else {
binding.btnFollow.setText(R.string.follow);
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_24);
}
if (restrictMenuItem != null) {
restrictMenuItem.setVisible(true);
if (profileModel.getRestricted()) {
restrictMenuItem.setTitle(R.string.unrestrict);
} else {
restrictMenuItem.setTitle(R.string.restrict);
}
}
binding.btnTagged.setVisibility(profileModel.isReallyPrivate() ? View.GONE : View.VISIBLE);
if (blockMenuItem != null) {
blockMenuItem.setVisible(true);
if (profileModel.getBlocked()) {
blockMenuItem.setTitle(R.string.unblock);
} else {
blockMenuItem.setTitle(R.string.block);
}
}
return;
}
if (!profileModel.isReallyPrivate() && restrictMenuItem != null) {
restrictMenuItem.setVisible(true);
if (profileModel.getRestricted()) {
restrictMenuItem.setTitle(R.string.unrestrict);
} else {
restrictMenuItem.setTitle(R.string.restrict);
}
}
}
private void fetchStoryAndHighlights(final String profileId) {
storiesService.getUserStory(profileId,
profileModel.getUsername(),
false,
false,
false,
new ServiceCallback<List<StoryModel>>() {
@Override
public void onSuccess(final List<StoryModel> storyModels) {
if (storyModels != null && !storyModels.isEmpty()) {
binding.mainProfileImage.setStoriesBorder();
hasStories = true;
}
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
}
});
new HighlightsFetcher(profileId,
result -> {
highlightsFetching = false;
if (result != null) {
binding.highlightsList.setVisibility(View.VISIBLE);
highlightsViewModel.getList().postValue(result);
} else binding.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void setupCommonListeners() {
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
// final boolean isSelf = isLoggedIn && profileModel != null && userIdFromCookie != null && userIdFromCookie
// .equals(profileModel.getId());
binding.btnFollow.setOnClickListener(v -> {
if (profileModel.getFollowing() || profileModel.getRequested()) {
friendshipService.unfollow(
@ -636,7 +759,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
new ServiceCallback<FriendshipRepoChangeRootResponse>() {
@Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
Log.d(TAG, "Unfollow success: " + result);
// Log.d(TAG, "Unfollow success: " + result);
onRefresh();
}
@ -653,7 +776,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
new ServiceCallback<FriendshipRepoChangeRootResponse>() {
@Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
Log.d(TAG, "Follow success: " + result);
// Log.d(TAG, "Follow success: " + result);
onRefresh();
}
@ -682,12 +805,10 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
PostItemType.TAGGED);
NavHostFragment.findNavController(this).navigate(action);
});
binding.btnDM.setOnClickListener(v -> {
new CreateThreadAction(cookie, profileModel.getId(), threadId -> {
final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToDMThreadFragment(threadId, profileModel.getUsername());
NavHostFragment.findNavController(this).navigate(action);
}).execute();
});
binding.btnDM.setOnClickListener(v -> new CreateThreadAction(cookie, profileModel.getId(), threadId -> {
final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToDMThreadFragment(threadId, profileModel.getUsername());
NavHostFragment.findNavController(this).navigate(action);
}).execute());
binding.mainProfileImage.setOnClickListener(v -> {
if (!hasStories) {
// show profile pic
@ -760,8 +881,8 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
final ProfilePicDialogFragment fragment = new ProfilePicDialogFragment(profileModel.getId(), username, profileModel.getHdProfilePic());
final FragmentTransaction ft = fragmentManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.add(fragment, "profilePicDialog")
.commit();
.add(fragment, "profilePicDialog")
.commit();
}
}
@ -773,62 +894,20 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
private void setupPosts() {
postsViewModel = new ViewModelProvider(this).get(PostsViewModel.class);
final Context context = getContext();
if (context == null) return;
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110));
binding.mainPosts.setLayoutManager(layoutManager);
binding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
postsAdapter = new PostsAdapter((postModel, position) -> {
if (postsAdapter.isSelecting()) {
if (actionMode == null) return;
final String title = getString(R.string.number_selected,
postsAdapter.getSelectedModels().size());
actionMode.setTitle(title);
return;
}
if (checkAndResetAction()) return;
final List<PostModel> postModels = postsViewModel.getList().getValue();
if (postModels == null || postModels.size() == 0) return;
if (postModels.get(0) == null) return;
final String postId = isLoggedIn ? postModels.get(0).getPostId() : postModels.get(0).getShortCode();
final boolean isId = isLoggedIn && postId != null;
final String[] idsOrShortCodes = new String[postModels.size()];
for (int i = 0; i < postModels.size(); i++) {
idsOrShortCodes[i] = isId ? postModels.get(i).getPostId()
: postModels.get(i).getShortCode();
}
final NavDirections action = ProfileFragmentDirections.actionGlobalPostViewFragment(
position,
idsOrShortCodes,
isId);
NavHostFragment.findNavController(this).navigate(action);
binding.postsRecyclerView.setViewModelStoreOwner(this)
.setLifeCycleOwner(this)
.setPostFetchService(new ProfilePostFetchService(profileModel))
.setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_PROFILE_POSTS_LAYOUT)))
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
.setFeedItemCallback(feedItemCallback)
.setSelectionModeCallback(selectionModeCallback)
.init();
binding.swipeRefreshLayout.setRefreshing(true);
postsSetupDone = true;
}
}, (model, position) -> {
if (!postsAdapter.isSelecting()) {
checkAndResetAction();
return true;
}
if (onBackPressedCallback.isEnabled()) {
return true;
}
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
onBackPressedCallback.setEnabled(true);
actionMode = fragmentActivity.startActionMode(multiSelectAction);
final String title = getString(R.string.number_selected, 1);
actionMode.setTitle(title);
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
return true;
});
postsViewModel.getList().observe(fragmentActivity, postsAdapter::submitList);
binding.mainPosts.setAdapter(postsAdapter);
final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!hasNextPage) return;
binding.swipeRefreshLayout.setRefreshing(true);
fetchPosts();
endCursor = null;
});
binding.mainPosts.addOnScrollListener(lazyLoader);
private void updateSwipeRefreshState() {
binding.swipeRefreshLayout.setRefreshing(binding.postsRecyclerView.isFetching() || highlightsFetching);
}
private void setupHighlights() {
@ -847,36 +926,24 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
private void fetchPosts() {
stopCurrentExecutor();
// stopCurrentExecutor();
binding.swipeRefreshLayout.setRefreshing(true);
currentlyExecuting = new PostsFetcher(profileModel.getId(), PostItemType.MAIN, endCursor, postsFetchListener)
.setUsername(profileModel.getUsername())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
// currentlyExecuting = new PostsFetcher(profileModel.getId(), PostItemType.MAIN, endCursor, postsFetchListener)
// .setUsername(profileModel.getUsername())
// .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public void stopCurrentExecutor() {
if (currentlyExecuting != null) {
try {
currentlyExecuting.cancel(true);
} catch (final Exception e) {
if (logCollector != null) logCollector.appendException(e, LogCollector.LogFile.MAIN_HELPER, "stopCurrentExecutor");
Log.e(TAG, "", e);
}
}
private void navigateToProfile(final String username) {
final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle();
bundle.putString("username", username);
navController.navigate(R.id.action_global_profileFragment, bundle);
}
private boolean checkAndResetAction() {
if (!onBackPressedCallback.isEnabled() && actionMode == null) {
return false;
}
if (onBackPressedCallback.isEnabled()) {
onBackPressedCallback.setEnabled(false);
onBackPressedCallback.remove();
}
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
return true;
private void showPostsLayoutPreferences() {
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(
Constants.PREF_PROFILE_POSTS_LAYOUT,
preferences -> new Handler().postDelayed(() -> binding.postsRecyclerView.setLayoutPreferences(preferences), 200));
fragment.show(getChildFragmentManager(), "posts_layout_preferences");
}
}

View File

@ -26,24 +26,26 @@ public class AboutFragment extends BasePreferencesFragment {
generalCategory.addPreference(getRepoPreference());
generalCategory.addPreference(getFeedbackPreference());
final PreferenceCategory thirdPartyCategory = new PreferenceCategory(context);
screen.addPreference(thirdPartyCategory);
thirdPartyCategory.setTitle(R.string.about_category_3pt);
//thirdPartyCategory.setSummary(R.string.about_category_3pt_summary);
thirdPartyCategory.setIconSpaceReserved(false);
// alphabetical order!!!
thirdPartyCategory.addPreference(getExoPlayerPreference());
thirdPartyCategory.addPreference(getFrescoPreference());
thirdPartyCategory.addPreference(getJsoupPreference());
thirdPartyCategory.addPreference(getMDIPreference());
thirdPartyCategory.addPreference(getRetrofitPreference());
final PreferenceCategory licenseCategory = new PreferenceCategory(context);
screen.addPreference(licenseCategory);
licenseCategory.setTitle(R.string.about_category_license);
licenseCategory.setIconSpaceReserved(false);
licenseCategory.addPreference(getLicensePreference());
licenseCategory.addPreference(getLiabilityPreference());
final PreferenceCategory thirdPartyCategory = new PreferenceCategory(context);
screen.addPreference(thirdPartyCategory);
thirdPartyCategory.setTitle(R.string.about_category_3pt);
//thirdPartyCategory.setSummary(R.string.about_category_3pt_summary);
thirdPartyCategory.setIconSpaceReserved(false);
// alphabetical order!!!
thirdPartyCategory.addPreference(getAutolinkPreference());
thirdPartyCategory.addPreference(getExoPlayerPreference());
thirdPartyCategory.addPreference(getFrescoPreference());
thirdPartyCategory.addPreference(getIcafePreference());
thirdPartyCategory.addPreference(getJsoupPreference());
thirdPartyCategory.addPreference(getMDIPreference());
thirdPartyCategory.addPreference(getRetrofitPreference());
}
private Preference getDocsPreference() {
@ -55,7 +57,7 @@ public class AboutFragment extends BasePreferencesFragment {
preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(p -> {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://instagrabber.austinhuang.me"));
intent.setData(Uri.parse("https://barinsta.austinhuang.me"));
startActivity(intent);
return true;
});
@ -71,7 +73,7 @@ public class AboutFragment extends BasePreferencesFragment {
preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(p -> {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://github.com/austinhuang0131/instagrabber"));
intent.setData(Uri.parse("https://github.com/austinhuang0131/barinsta"));
startActivity(intent);
return true;
});
@ -174,6 +176,38 @@ public class AboutFragment extends BasePreferencesFragment {
return preference;
}
private Preference getAutolinkPreference() {
final Context context = getContext();
if (context == null) return null;
final Preference preference = new Preference(context);
preference.setTitle("AutoLinkTextViewV2");
preference.setSummary("Copyright (C) 2019 Arman Chatikyan. Apache Version 2.0.");
preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(p -> {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://github.com/armcha/AutoLinkTextViewV2"));
startActivity(intent);
return true;
});
return preference;
}
private Preference getIcafePreference() {
final Context context = getContext();
if (context == null) return null;
final Preference preference = new Preference(context);
preference.setTitle("ICAFE");
preference.setSummary("Copyright (C) 2014-2019 Wen Yu. Eclipse Version 2.0.");
preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(p -> {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://github.com/dragon66/icafe"));
startActivity(intent);
return true;
});
return preference;
}
private Preference getLicensePreference() {
final Context context = getContext();
if (context == null) return null;

View File

@ -128,7 +128,7 @@ public class SettingsPreferencesFragment extends BasePreferencesFragment {
for (int i = 0; i < length; i++) {
final int resourceId = mainNavIds.getResourceId(i, -1);
if (resourceId < 0) continue;
values[i] = String.valueOf(resourceId);
values[i] = getResources().getResourceEntryName(resourceId);
}
mainNavIds.recycle();
preference.setKey(Constants.DEFAULT_TAB);

View File

@ -51,7 +51,7 @@ public class ThemePreferencesFragment extends BasePreferencesFragment {
for (int i = 0; i < length; i++) {
final int resourceId = lightThemeValues.getResourceId(i, -1);
if (resourceId < 0) continue;
values[i] = String.valueOf(resourceId);
values[i] = getResources().getResourceEntryName(resourceId);
}
lightThemeValues.recycle();
preference.setKey(Constants.PREF_LIGHT_THEME);
@ -76,7 +76,7 @@ public class ThemePreferencesFragment extends BasePreferencesFragment {
for (int i = 0; i < length; i++) {
final int resourceId = darkThemeValues.getResourceId(i, -1);
if (resourceId < 0) continue;
values[i] = String.valueOf(resourceId);
values[i] = getResources().getResourceEntryName(resourceId);
}
darkThemeValues.recycle();
preference.setKey(Constants.PREF_DARK_THEME);

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